diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index d34ef7c26..e608711a6 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -86,4 +86,50 @@ jobs: uses: actions/upload-artifact@v7 with: name: butterscotch-android - path: src/android/frontend/app/build/outputs/apk/debug/app-debug.apk \ No newline at end of file + path: src/android/frontend/app/build/outputs/apk/debug/app-debug.apk + + build-symbian: + name: Build (Symbian) + runs-on: windows-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + + - uses: actions/cache/restore@v5 + name: Check SDK + id: cache_sdk + with: + path: symbiansdk.zip + key: symbian-sdk + + - name: Download SDK + if: steps.cache_sdk.outputs.cache-hit != 'true' + run: Invoke-WebRequest https://nnproject.cc/dl/symbian3_gcce1520_workflow.zip -OutFile symbiansdk.zip + + - name: Cache SDK + if: steps.cache_sdk.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: symbiansdk.zip + key: symbian-sdk + + - name: Extract SDK + run: Expand-Archive symbiansdk.zip -DestinationPath . + + - name: Compile + run: | + $SDK = "$pwd/SDK/" + $Env:EPOCROOT = "$SDK".Substring(2) + $Env:SBS_GCCE1520BIN = "$pwd/GCCE/bin" + cmd /c "$SDK/epoc32/tools/sbs\bin\sbs.bat -b artifacts/symbian/bld.inf -c armv6_urel_gcce -f - -m $SDK/epoc32/build/Butterscotch/makefile -j 4" + + cd artifacts/symbian + (Get-Content Butterscotch.pkg).Replace('$(EPOCROOT)', "$SDK").Replace('$(PLATFORM)', 'armv6').Replace('$(TARGET)', 'urel') | Set-Content Butterscotch.pkg + cmd /c "$SDK/epoc32/tools/makesis.exe Butterscotch.pkg Butterscotch.sis" + + + - name: Upload SIS + uses: actions/upload-artifact@v7 + with: + name: butterscotch-symbian + path: artifacts/symbian/Butterscotch.sis diff --git a/.gitignore b/.gitignore index fb3756c01..041e1dc14 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,9 @@ CMakeUserPresets.json compat/config.mk /.vs *.log + +# symbian +.project +.cproject +*.sis +*.d diff --git a/artifacts/symbian/Butterscotch.mmp b/artifacts/symbian/Butterscotch.mmp new file mode 100644 index 000000000..e7b4e112f --- /dev/null +++ b/artifacts/symbian/Butterscotch.mmp @@ -0,0 +1,134 @@ +TARGET Butterscotch.exe +TARGETTYPE exe +UID 0x100039CE 0xEC93FBDE + +SYSTEMINCLUDE /epoc32 +SYSTEMINCLUDE /epoc32/include +SYSTEMINCLUDE /epoc32/include/mw +SYSTEMINCLUDE /epoc32/include/app +SYSTEMINCLUDE /epoc32/include/platform +SYSTEMINCLUDE /epoc32/include/platform/mw +SYSTEMINCLUDE /epoc32/include/platform/app +SYSTEMINCLUDE /epoc32/include/stdapis + +LIBRARY euser.lib +// ui +LIBRARY cone.lib +LIBRARY avkon.lib +LIBRARY apparc.lib +LIBRARY eikcore.lib +LIBRARY ws32.lib +// gl +LIBRARY libglesv2.lib +LIBRARY libegl.lib +// pips +LIBRARY libc.lib +LIBRARY libm.lib +LIBRARY libpthread.lib +// audio +LIBRARY mediaclientaudiostream.lib +// volume keys +LIBRARY remconinterfacebase.lib +LIBRARY remconcoreapi.lib + +#ifdef ARMCC +OPTION ARMCC -Otime +OPTION ARMCC --gnu --c99 --diag_suppress 1,61,66,68,111,174,381,1293,1296 +OPTION_REPLACE ARMCC --cpu 6 +OPTION_REPLACE ARMCC --fpu vfpv2 --fpmode fast +OPTION_REPLACE ARMCC -O2 -O3 + +ARMFPU vfpv2 +ALWAYS_BUILD_AS_ARM +#endif + +OPTION GCCE -std=gnu99 +#if defined(GCCE_12) || defined(GCCE_15) +OPTION GCCE -O3 +ARMFPU vfpv2 +ALWAYS_BUILD_AS_ARM +#endif + +OPTION CW -lang c99 -undefine _WIN32 -relax_pointers + +CAPABILITY NetworkServices ReadUserData WriteUserData + +EPOCSTACKSIZE 0x14000 +EPOCHEAPSIZE 0x80000 0x4000000 + +DEBUGGABLE_UDEBONLY +#ifdef _DEBUG +SRCDBG +#endif + +SOURCEPATH . +START RESOURCE butterscotch.rss +HEADER +TARGETPATH \resource\apps +LANG SC +END + +START RESOURCE butterscotch_reg.rss +TARGETPATH \private\10003a3f\import\apps +END + +MACRO SYMBIAN_OE_POSIX_SIGNALS +MACRO ENABLE_MODERN_GL +MACRO USE_MINIAUDIO +MACRO ENABLE_WAD14 +MACRO ENABLE_WAD16 +MACRO ENABLE_WAD17 +MACRO NO_LOG2 +MACRO USE_FLOAT_REALS +MACRO MINIZ_NO_ARCHIVE_APIS +MACRO MINIZ_NO_STDIO + +USERINCLUDE ../../src +USERINCLUDE ../../src/debug_font +USERINCLUDE ../../src/gl +USERINCLUDE ../../src/gl_common +USERINCLUDE ../../src/gl_legacy +USERINCLUDE ../../src/image +USERINCLUDE ../../src/video +USERINCLUDE ../../src/audio/miniaudio +USERINCLUDE ../../src/symbian +USERINCLUDE ../../vendor/base64 +USERINCLUDE ../../vendor/bzip2 +USERINCLUDE ../../vendor/md5 +USERINCLUDE ../../vendor/sha1 +USERINCLUDE ../../vendor/stb/ds +USERINCLUDE ../../vendor/stb/image +USERINCLUDE ../../vendor/stb/vorbis +USERINCLUDE ../../vendor/miniz +USERINCLUDE ../../vendor/miniaudio + +SOURCEPATH ../../src +SOURCE binary_reader.c data_win.c data_win_print.c debug_overlay.c event_table.c gml_array.c gml_method.c ini.c input_recording.c instance.c int_int_hashmap.c int_rvalue_hashmap.c json_reader.c json_writer.c log.c loop.c noop_audio_system.c overlay_file_system.c profiler.c random.c runner.c runner_gamepad.c runner_keyboard.c runner_mouse.c spatial_grid.c stb_ds.c string_builder.c vm.c vm_builtins.c +SOURCEPATH ../../src/video +SOURCE null_video.c +SOURCEPATH ../../src/debug_font +SOURCE debug_font.c debug_font_4bpp.c +SOURCEPATH ../../src/gl_common +SOURCE gl_common.c +SOURCEPATH ../../src/image +SOURCE image_decoder.c +SOURCEPATH ../../vendor/base64 +SOURCE base64.c +SOURCEPATH ../../vendor/bzip2 +SOURCE bz_internal_error.c bzlib.c crctable.c decompress.c huffman.c randtable.c +SOURCEPATH ../../vendor/md5 +SOURCE md5.c +SOURCEPATH ../../vendor/sha1 +SOURCE sha1.c +SOURCEPATH ../../vendor/miniz +SOURCE miniz.c miniz_tdef.c miniz_tinfl.c +SOURCEPATH ../../src/symbian +SOURCE stb_impl.c symbian.cpp symbian_audio.cpp symbian_atomics.c +SOURCEPATH ../../src/gl +SOURCE gl_renderer.c +SOURCEPATH ../../src/audio/miniaudio +SOURCE ma_audio_system.c +SOURCEPATH ../../src/physics/disabled +SOURCE physics_disabled.c +SOURCEPATH ../../src/physics +SOURCE physics.c diff --git a/artifacts/symbian/Butterscotch.pkg b/artifacts/symbian/Butterscotch.pkg new file mode 100644 index 000000000..d42509c71 --- /dev/null +++ b/artifacts/symbian/Butterscotch.pkg @@ -0,0 +1,12 @@ +&EN + +#{"Butterscotch"},(0xEC93FBDE),1,0,0 + +[0x20022E6D], 0, 0, 0, {"S60ProductID"} + +%{"shinovon"} +:"shinovon" + +"$(EPOCROOT)Epoc32\release\$(PLATFORM)\$(TARGET)\Butterscotch.exe" - "!:\sys\bin\Butterscotch.exe" +"$(EPOCROOT)\epoc32\data\z\private\10003a3f\import\apps\butterscotch_reg.rsc" - "!:\private\10003a3f\import\apps\butterscotch_reg.rsc" +"$(EPOCROOT)\epoc32\data\z\resource\apps\butterscotch.rsc" - "!:\resource\apps\butterscotch.rsc" diff --git a/artifacts/symbian/bld.inf b/artifacts/symbian/bld.inf new file mode 100644 index 000000000..bc00e6ea2 --- /dev/null +++ b/artifacts/symbian/bld.inf @@ -0,0 +1,5 @@ +PRJ_PLATFORMS +GCCE ARMV5 ARMV6 + +PRJ_MMPFILES +Butterscotch.mmp diff --git a/artifacts/symbian/butterscotch.rss b/artifacts/symbian/butterscotch.rss new file mode 100644 index 000000000..fd43e3007 --- /dev/null +++ b/artifacts/symbian/butterscotch.rss @@ -0,0 +1,78 @@ +NAME BTSC + +#include +#include +#include +#include +#include + +RESOURCE RSS_SIGNATURE { } +RESOURCE TBUF { buf="Butterscotch"; } + +RESOURCE EIK_APP_INFO { } + +rls_string STRING_app_caption_string "Butterscotch" +rls_string STRING_app_short_caption_string "Butterscotch" + +RESOURCE LOCALISABLE_APP_INFO r_BUTTERSCOTCH_localisable_app_info +{ + short_caption = STRING_app_short_caption_string; + caption_and_icon = CAPTION_AND_ICON_INFO + { + caption = STRING_app_caption_string; + number_of_icons = 1; + icon_file = "\\resource\\apps\\butterscotch_icon.mif"; + }; +} + +RESOURCE DIALOG r_query_dialog +{ + flags = EGeneralQueryFlags | EEikDialogFlagNoBorder | EEikDialogFlagNoShadow; + buttons = R_AVKON_SOFTKEYS_OK_EMPTY; + items = + { + DLG_LINE + { + type = EAknCtPopupHeadingPane; + id = EAknMessageQueryHeaderId; + itemflags = EEikDlgItemNonFocusing; + control = AVKON_HEADING + { + }; + }, + DLG_LINE + { + type = EAknCtMessageQuery; + id = EAknMessageQueryContentId; + control = AVKON_MESSAGE_QUERY + { + }; + } + }; +} + +RESOURCE DIALOG r_text_query +{ + flags = EEikDialogFlagNoDrag | EEikDialogFlagNoTitleBar | EEikDialogFlagCbaButtons; + buttons = R_AVKON_SOFTKEYS_OK_CANCEL; + items = + { + DLG_LINE + { + type = EAknCtQuery; + id = EGeneralQuery; + control = AVKON_DATA_QUERY + { + layout = EDataLayout; + control = EDWIN + { + width = 128; + lines = 1; + maxlength = 510; + default_case = EAknEditorLowerCase; + allowed_case_modes = EAknEditorAllCaseModes; + }; + }; + } + }; +} diff --git a/artifacts/symbian/butterscotch_reg.rss b/artifacts/symbian/butterscotch_reg.rss new file mode 100644 index 000000000..51dec6ac0 --- /dev/null +++ b/artifacts/symbian/butterscotch_reg.rss @@ -0,0 +1,17 @@ +#include +#include + +UID2 KUidAppRegistrationResourceFile +UID3 0xEC93FBDE + +RESOURCE APP_REGISTRATION_INFO +{ + app_file="Butterscotch"; + + localisable_resource_file="\\resource\\apps\\butterscotch"; + localisable_resource_id=R_BUTTERSCOTCH_LOCALISABLE_APP_INFO; + + embeddability=KAppNotEmbeddable; + newfile=KAppDoesNotSupportNewFile; +} + diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 295d2697f..ec158a0e0 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -4,6 +4,14 @@ #include #endif +#ifdef __SYMBIAN32__ +#define MA_BSD +#define MA_NO_RUNTIME_LINKING +#define MA_THREAD_DEFAULT_STACK_SIZE 64*1024 +#define MA_ENABLE_ONLY_SPECIFIC_BACKENDS +#define MA_ENABLE_CUSTOM +#endif + // Include stb_vorbis BEFORE miniaudio so that STB_VORBIS_INCLUDE_STB_VORBIS_H is defined, // which enables miniaudio's built-in OGG Vorbis decoding support. #include "stb_vorbis.c" @@ -11,12 +19,12 @@ #define MA_NO_FLAC #define MA_NO_MP3 #define MINIAUDIO_IMPLEMENTATION -#if defined(__GNUC__) || defined(__clang__) +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__ARMCC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #endif #include "miniaudio.h" -#if defined(__GNUC__) || defined(__clang__) +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__ARMCC__) #pragma GCC diagnostic pop #endif @@ -106,11 +114,26 @@ static char* resolveExternalPath(MaAudioSystem* ma, Sound* sound) { // ===[ Vtable Implementations ]=== +#ifdef __SYMBIAN32__ +ma_context* maSymbianAudioInit(void); +void maSymbianAudioDestroy(void); +#endif + static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem) { MaAudioSystem* ma = (MaAudioSystem*) audio; arrput(ma->base.audioGroups, dataWin); ma->fileSystem = fileSystem; - + +#ifdef __SYMBIAN32__ + ma_context* context = maSymbianAudioInit(); + ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); + deviceConfig.playback.format = ma_format_f32; + deviceConfig.playback.channels = 2; + deviceConfig.sampleRate = 44100; + deviceConfig.dataCallback = ma_engine_data_callback_internal; + deviceConfig.pUserData = &ma->engine; + ma_result deviceResult = ma_device_init(context, &deviceConfig, &ma->device); +#else ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback); deviceConfig.playback.format = ma_format_f32; deviceConfig.playback.channels = 2; @@ -119,6 +142,7 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem) deviceConfig.dataCallback = ma_engine_data_callback_internal; deviceConfig.pUserData = &ma->engine; ma_result deviceResult = ma_device_init(NULL, &deviceConfig, &ma->device); +#endif if (deviceResult != MA_SUCCESS) { logError("Audio: Failed to initialize playback device (error %d)\n", deviceResult); return; @@ -183,6 +207,9 @@ static void maDestroy(AudioSystem* audio) { ma_device_uninit(&ma->device); ma_engine_uninit(&ma->engine); +#ifdef __SYMBIAN32__ + maSymbianAudioDestroy(); +#endif free(ma); } diff --git a/src/binary_reader.c b/src/binary_reader.c index 06a56f538..ef903c045 100644 --- a/src/binary_reader.c +++ b/src/binary_reader.c @@ -138,10 +138,13 @@ void BinaryReader_seek(BinaryReader* reader, size_t position) { if (reader->buffer != nullptr) { if (position < reader->bufferBase || position > reader->bufferBase + reader->bufferSize) { logError("BinaryReader: buffer seek to 0x%zX out of buffer range [0x%zX, 0x%zX]\n", position, reader->bufferBase, reader->bufferBase + reader->bufferSize); - abort(); + // XXX: temporary fix for TGIN until #618 is resolved +// abort(); + reader->buffer = nullptr; + } else { + reader->bufferPos = position - reader->bufferBase; + return; } - reader->bufferPos = position - reader->bufferBase; - return; } if (position > reader->fileSize) { diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index b19aece1d..e890ed878 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -9,6 +9,9 @@ #elif PLATFORM_VITA #include #include "vita_textures.h" +#elif defined(__SYMBIAN32__) +#include +#include #else #include #endif @@ -58,7 +61,7 @@ static const char* baseFragmentShader = // ===[ Runtime OpenGL extension checks ]=== static bool hasFBO() { -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__) +#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__) && !defined(__SYMBIAN32__) return glGenFramebuffers; #else return true; @@ -66,7 +69,9 @@ static bool hasFBO() { } static bool hasVAO() { -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__) +#if defined(__SYMBIAN32__) + return false; +#elif !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(__VITA__) && !defined(__SWITCH__) return glGenVertexArrays; #else return true; @@ -304,9 +309,12 @@ static void flushBatch(GLRenderer* gl) { #endif +#ifndef __SYMBIAN32__ if (hasVAO()) { glBindVertexArray(modernGl->vao); - } else { + } else +#endif + { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modernGl->ebo); int32_t stride = sizeof(GlVertex); @@ -437,9 +445,12 @@ static void glPrimitiveEnd(Renderer* renderer) { GL_DYNAMIC_DRAW ); +#ifndef __SYMBIAN32__ if (hasVAO()) { glBindVertexArray(modernGl->vao); - } else { + } else +#endif + { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, modernGl->ebo); int32_t stride = sizeof(GlVertex); @@ -595,6 +606,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { const char* vertHeader = ""; const char* fragHeader = ""; +#ifndef __SYMBIAN32__ if (modernGl->isGL3) { if (modernGl->isGLES) { vertHeader = "#version 300 es\nprecision highp float;\n"; @@ -621,7 +633,9 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { "in vec2 vTexCoord;\nin vec4 vColor;\nout vec4 fragColor;\n" "#define TEXTURE_2D texture\n#define FRAG_COLOR fragColor\n%s", fragHeader, baseFragmentShader); - } else { + } else +#endif + { if (modernGl->isGLES) { vertHeader = "#version 100\nprecision highp float;\n"; fragHeader = "#version 100\nprecision mediump float;\n"; @@ -720,11 +734,13 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { free(uFogColor); // Create VAO/VBO/EBO +#ifndef __SYMBIAN32__ if (hasVAO()) { glGenVertexArrays(1, &modernGl->vao); glGenVertexArrays(1, &modernGl->vertexBufferVao); glBindVertexArray(modernGl->vao); } +#endif glGenBuffers(1, &modernGl->vbo); glGenBuffers(1, &modernGl->ebo); @@ -746,6 +762,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboSize, indices, GL_STATIC_DRAW); free(indices); +#ifndef __SYMBIAN32__ if (hasVAO()) { // Vertex attributes: pos(2f), texcoord(2f), color(4f) int32_t stride = sizeof(GlVertex); @@ -757,6 +774,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glEnableVertexAttribArray(2); glBindVertexArray(0); } +#endif // Allocate CPU-side vertex buffer #if PLATFORM_VITA @@ -881,10 +899,12 @@ static void glDestroy(Renderer* renderer) { free(modernGl->gmlShaders); freeShader(modernGl->defaultShaderProgram); free(modernGl->defaultShaderProgram); +#ifndef __SYMBIAN32__ if (hasVAO()) { glDeleteVertexArrays(1, &modernGl->vao); glDeleteVertexArrays(1, &modernGl->vertexBufferVao); } +#endif glDeleteBuffers(1, &modernGl->vbo); glDeleteBuffers(1, &modernGl->ebo); @@ -914,7 +934,9 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN GLCommon_beginView(renderer, portX, portY, portW, portH, GL_TEXTURE1, glApplyProjection); +#ifndef __SYMBIAN32__ if (hasVAO()) glBindVertexArray(modernGl->vao); +#endif } static void glEndView(Renderer* renderer) { @@ -934,7 +956,9 @@ static void glBeginGUI(Renderer* renderer, int32_t guiW, int32_t guiH, int32_t p guiW, guiH, portX, portY, portW, portH ); +#ifndef __SYMBIAN32__ if (hasVAO()) glBindVertexArray(modernGl->vao); +#endif } static void glSetGuiProjection(Renderer* renderer, int32_t guiW, int32_t guiH, MAYBE_UNUSED int32_t portW, MAYBE_UNUSED int32_t portH, MAYBE_UNUSED bool renderingToUserSurface) { @@ -954,8 +978,10 @@ static void glEndFrameInit(Renderer* renderer) { GLRenderer* gl = (GLRenderer*) renderer; GLModernRenderer* modernGl = (GLModernRenderer*) gl; +#ifndef __SYMBIAN32__ if (hasVAO()) glBindVertexArray(0); - +#endif + if (renderer->runner->usingAppSurface && !renderer->runner->appSurfaceAutoDraw) { glBindFramebuffer(GL_FRAMEBUFFER, modernGl->hostFramebuffer); return; @@ -971,10 +997,13 @@ static void glEndFrameEnd(Renderer* renderer) { } int32_t appId = gl->base.runner->applicationSurfaceId; +#ifndef __SYMBIAN32__ if (modernGl->isGL3) { GLCommon_beginLetterboxBlit(gl->surfaces[appId], modernGl->hostFramebuffer); GLCommon_endLetterboxBlit(gl->surfaceWidth[appId], gl->surfaceHeight[appId], gl->gameW, gl->gameH, gl->windowW, gl->windowH, modernGl->hostFramebuffer); - } else { + } else +#endif + { glBindFramebuffer(GL_FRAMEBUFFER, modernGl->hostFramebuffer); GLboolean scissorWasEnabled = glIsEnabled(GL_SCISSOR_TEST); if (scissorWasEnabled) glDisable(GL_SCISSOR_TEST); @@ -1018,12 +1047,67 @@ static void glClearScreen(Renderer* renderer, uint32_t color, float alpha) { glClear(GL_COLOR_BUFFER_BIT); } +#ifdef __SYMBIAN32__ +static bool unloadOldestTexture(GLRenderer* gl, uint32_t currentPageId) { + GLModernRenderer* modernGl = (GLModernRenderer*) gl; + uint32_t res = UINT32_MAX; + int min = INT32_MAX; + + for (uint32_t i = 0; i < gl->textureCount && i < gl->originalTexturePageCount; ++i) { + if (i == currentPageId || !gl->textureLoaded[i] || gl->glTextures[i] == modernGl->currentTextureId) + continue; + + int j = gl->textureLastUsed[i]; + if (j >= min) continue; + + min = j; + res = i; + } + + if (res == UINT32_MAX) return false; + + flushBatch(gl); + GLRenderer_unloadTexture(gl, res); + glFlush(); + return true; +} + +#define UNLOAD_THRESHOLD 1000 +static void unloadUnusedTextures(GLRenderer* gl, uint32_t currentPageId) { + if (gl->base.runner->frameCount < UNLOAD_THRESHOLD) return; + GLModernRenderer* modernGl = (GLModernRenderer*) gl; + bool flushed = false; + int threshold = gl->base.runner->frameCount - UNLOAD_THRESHOLD; + + for (uint32_t i = 0; i < gl->textureCount && i < gl->originalTexturePageCount; ++i) { + if (i == currentPageId || !gl->textureLoaded[i] || gl->glTextures[i] == modernGl->currentTextureId) + continue; + if (gl->textureLastUsed[i] > threshold) + continue; + + if (!flushed) { + flushBatch(gl); + flushed = true; + } + GLRenderer_unloadTexture(gl, i); + } + if (flushed) glFlush(); +} +#endif + // Lazily decodes and uploads a TXTR page on first access. // Returns true if the texture is ready, false if it failed to decode. bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) { - if (gl->textureLoaded[pageId]) return (gl->textureWidths[pageId] != 0); + if (gl->textureLoaded[pageId]) { + if (gl->textureWidths[pageId] != 0) { + gl->textureLastUsed[pageId] = gl->base.runner->frameCount; + return true; + } + return false; + } gl->textureLoaded[pageId] = true; + gl->textureLastUsed[pageId] = gl->base.runner->frameCount; #if defined(PLATFORM_VITA) if (VitaTextures_Active()) { @@ -1066,9 +1150,56 @@ bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) { gl->textureWidths[pageId] = w; gl->textureHeights[pageId] = h; - + + if (gl->glTextures[pageId] == 0) { + glGenTextures(1, &gl->glTextures[pageId]); + } + +#ifdef __SYMBIAN32__ + unloadUnusedTextures(gl, pageId); + glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); + + if (gl->textureFormat == GL_RGBA4) { + uint16_t* rgba4444 = (uint16_t*)safeMalloc(w * h * sizeof(uint16_t)); + for (int i = 0; i < w * h; i++) { + rgba4444[i] = (((uint16_t)pixels[i * 4] >> 4) << 12) | + (((uint16_t)pixels[i * 4 + 1] >> 4) << 8) | + (((uint16_t)pixels[i * 4 + 2] >> 4) << 4) | + ((uint16_t)pixels[i * 4 + 3] >> 4); + } + free(pixels); + pixels = (uint8_t*) rgba4444; + } + + while (true) { + while (glGetError() != GL_NO_ERROR); + + if (gl->textureFormat == GL_RGBA4) { + GLint previousUnpackAlignment = 4; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, pixels); + glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } + + if (glGetError() != GL_OUT_OF_MEMORY) break; + logWarn("GL: Failed to upload TXTR page %u (%dx%d), retrying\n", + pageId, gl->textureWidths[pageId], gl->textureHeights[pageId]); + + if (!unloadOldestTexture(gl, pageId)) { + logError("GL: Failed to free VRAM, aborting texture upload\n"); + free(pixels); + return false; + } + + glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); + } +#else glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); glTexImage2D(GL_TEXTURE_2D, 0, gl->textureFormat, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +#endif free(pixels); @@ -1083,6 +1214,21 @@ bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) { return true; } +void GLRenderer_unloadTexture(GLRenderer* gl, uint32_t pageId) { + if (!gl->textureLoaded[pageId]) return; + + if (gl->glTextures[pageId] != 0) { + glDeleteTextures(1, &gl->glTextures[pageId]); + gl->glTextures[pageId] = 0; + } + + gl->textureLoaded[pageId] = false; + gl->textureWidths[pageId] = 0; + gl->textureHeights[pageId] = 0; + + logInfo("GL: Unloaded TXTR page %u\n", pageId); +} + // Resolves a TPAG index to a loaded GL texture. Returns false if drawing should be skipped. static bool resolveSpriteTexture(GLRenderer* gl, int32_t tpagIndex, TexturePageItem** outTpag, GLuint* outTexId, int32_t* outTexW, int32_t* outTexH) { DataWin* dw = gl->base.dataWin; @@ -1830,9 +1976,11 @@ static void glDrawVertexBuffer(MAYBE_UNUSED Renderer* renderer, VertexBuffer* bu GLenum mode = primitiveTypeToGL(primitive); +#ifndef __SYMBIAN32__ if (hasVAO()) { glBindVertexArray(modernGl->vertexBufferVao); } +#endif glBindBuffer(GL_ARRAY_BUFFER, glBuffer->vbo); glBufferData(GL_ARRAY_BUFFER, buffer->size, buffer->data, GL_DYNAMIC_DRAW); @@ -1932,8 +2080,10 @@ static void glDrawVertexBuffer(MAYBE_UNUSED Renderer* renderer, VertexBuffer* bu int vertexCount = buffer->size / buffer->format->stride; if (vertexCount <= 0) { - if (hasVAO()) glBindVertexArray(modernGl->vao); - else for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); +#ifndef __SYMBIAN32__ + if (hasVAO()) glBindVertexArray(modernGl->vao); else +#endif + for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); glBindBuffer(GL_ARRAY_BUFFER, 0); return; } @@ -1951,8 +2101,10 @@ static void glDrawVertexBuffer(MAYBE_UNUSED Renderer* renderer, VertexBuffer* bu } if (number <= 0) { - if (hasVAO()) glBindVertexArray(modernGl->vao); - else for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); +#ifndef __SYMBIAN32__ + if (hasVAO()) glBindVertexArray(modernGl->vao); else +#endif + for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); glBindBuffer(GL_ARRAY_BUFFER, 0); return; } @@ -1963,8 +2115,10 @@ static void glDrawVertexBuffer(MAYBE_UNUSED Renderer* renderer, VertexBuffer* bu number ); - if (hasVAO()) glBindVertexArray(modernGl->vao); - else for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); +#ifndef __SYMBIAN32__ + if (hasVAO()) glBindVertexArray(modernGl->vao); else +#endif + for (int i = 0; i < 4; i++) glDisableVertexAttribArray(i); glBindBuffer(GL_ARRAY_BUFFER, 0); } @@ -2172,8 +2326,8 @@ static void drawText( uint8_t r3 = (uint8_t) BGR_R(c4), g3 = (uint8_t) BGR_G(c4), b3 = (uint8_t) BGR_B(c4); bool drewSuccessfully = false; - if (ch == ' ') { - drewSuccessfully = true; + if (ch == ' ') { + drewSuccessfully = true; } else if (glyph->sourceWidth != 0 && glyph->sourceHeight != 0) { float u0, v0, u1, v1; float localX0, localY0; @@ -2324,6 +2478,7 @@ static uint32_t findOrAllocTexturePageSlot(GLRenderer* gl) { gl->textureWidths = (int32_t *)safeRealloc(gl->textureWidths, gl->textureCount * sizeof(int32_t)); gl->textureHeights = (int32_t *)safeRealloc(gl->textureHeights, gl->textureCount * sizeof(int32_t)); gl->textureLoaded = (bool *)safeRealloc(gl->textureLoaded, gl->textureCount * sizeof(bool)); + gl->textureLastUsed = (int *)safeRealloc(gl->textureLastUsed, gl->textureCount * sizeof(int)); gl->glTextures[newPageId] = 0; gl->textureWidths[newPageId] = 0; gl->textureHeights[newPageId] = 0; @@ -2357,8 +2512,17 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height glGenFramebuffers(1, &gl->surfaces[surfaceIndex]); glGenTextures(1, &gl->surfaceTexture[surfaceIndex]); + glBindTexture(GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex]); +#ifdef __SYMBIAN32__ + if (GLCommon_surfaceInternalFormat(gl) == GL_RGBA4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); +#endif bool isPOT = (width & (width - 1)) == 0 && (height & (height - 1)) == 0; GLint wrapMode = isPOT ? GL_REPEAT : GL_CLAMP_TO_EDGE; @@ -2430,7 +2594,15 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width glGenTextures(1, &gl->surfaceTexture[surfaceID]); glBindTexture(GL_TEXTURE_2D, gl->surfaceTexture[surfaceID]); +#ifdef __SYMBIAN32__ + if (GLCommon_surfaceInternalFormat(gl) == GL_RGBA4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); +#endif 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); @@ -2537,9 +2709,12 @@ static void glSurfaceCopy(Renderer* renderer, int32_t destSurfaceID, int32_t des if (0 > srcSurfaceID || (uint32_t) srcSurfaceID >= gl->surfaceCount || gl->surfaces[srcSurfaceID] == 0) return; if (0 > destSurfaceID || (uint32_t) destSurfaceID >= gl->surfaceCount || gl->surfaces[destSurfaceID] == 0) return; +#ifndef __SYMBIAN32__ if (modernGl->isGL3) { GLCommon_surfaceBlit(gl->surfaces, gl->surfaceWidth, gl->surfaceHeight, gl->surfaceCount, destSurfaceID, destX, destY, srcSurfaceID, srcX, srcY, srcW, srcH, part); - } else { + } else +#endif + { GLint prevBinding = 0; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); Matrix4f prevProj = renderer->gmlMatrices[MATRIX_WORLD_VIEW_PROJECTION]; @@ -2752,7 +2927,27 @@ static int32_t glCreateSpriteFromSurface(Renderer* renderer, int32_t surfaceID, GLuint newTexId; glGenTextures(1, &newTexId); glBindTexture(GL_TEXTURE_2D, newTexId); +#ifdef __SYMBIAN32__ + if (gl->textureFormat == GL_RGBA4) { + GLint previousUnpackAlignment = 4; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + uint16_t* rgba4444 = (uint16_t*)safeMalloc(w * h * sizeof(uint16_t)); + for (int i = 0; i < w * h; i++) { + rgba4444[i] = (((uint16_t)pixels[i * 4] >> 4) << 12) | + (((uint16_t)pixels[i * 4 + 1] >> 4) << 8) | + (((uint16_t)pixels[i * 4 + 2] >> 4) << 4) | + ((uint16_t)pixels[i * 4 + 3] >> 4); + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, rgba4444); + free(rgba4444); + glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, gl->textureFormat, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +#endif GLCommon_applyTexFilter(renderer->texFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index d05b13463..b49c7a7f7 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -10,6 +10,9 @@ #elif PLATFORM_VITA #include #define GL_BOOL 0x8B56 +#elif defined(__SYMBIAN32__) +#include +#include #else #include #endif @@ -71,6 +74,7 @@ typedef struct GLModernRenderer { } GLModernRenderer; bool GLRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId); +void GLRenderer_unloadTexture(GLRenderer* gl, uint32_t pageId); Renderer* GLRenderer_create(void); #endif /* _BS_GL_RENDERER_H_ */ diff --git a/src/gl_common/gl_common.c b/src/gl_common/gl_common.c index 214131d2f..a1f652625 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -39,7 +39,7 @@ void GLCommon_init(Renderer* renderer) { Matrix4f_identity(&world); renderer->gmlMatrices[MATRIX_WORLD] = world; -#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) && !defined(PLATFORM_PS3) +#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) && !defined(PLATFORM_PS3) && !defined(__SYMBIAN32__) gl_init_wrappers(); #endif @@ -67,6 +67,7 @@ void GLCommon_init(Renderer* renderer) { gl->textureWidths = (int32_t *)safeMalloc(gl->textureCount * sizeof(int32_t)); gl->textureHeights = (int32_t *)safeMalloc(gl->textureCount * sizeof(int32_t)); gl->textureLoaded = (bool *)safeMalloc(gl->textureCount * sizeof(bool)); + gl->textureLastUsed = (int *)safeMalloc(gl->textureCount * sizeof(int)); glGenTextures((GLsizei) gl->textureCount, gl->glTextures); @@ -82,7 +83,7 @@ void GLCommon_init(Renderer* renderer) { glGenTextures(1, &gl->whiteTexture); glBindTexture(GL_TEXTURE_2D, gl->whiteTexture); uint8_t whitePixel[4] = {255, 255, 255, 255}; - glTexImage2D(GL_TEXTURE_2D, 0, gl->textureFormat, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, whitePixel); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //I believe the old way this was done was wrong glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -297,6 +298,7 @@ void GLCommon_computeLetterbox(int32_t gameW, int32_t gameH, int32_t windowW, in *outEndY = startY + effH; } +#ifndef __SYMBIAN32__ void GLCommon_beginLetterboxBlit(GLuint fbo, GLuint hostFbo) { glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, hostFbo); @@ -315,6 +317,7 @@ void GLCommon_endLetterboxBlit(int32_t fboWidth, int32_t fboHeight, int32_t game glBlitFramebuffer(0, 0, fboWidth, fboHeight, sx, ey, ex, sy, GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, hostFbo); } +#endif // ===[ Surface arrays ]=== @@ -342,6 +345,7 @@ static bool resolveSurfaceFBO(GLuint* surfaces, int32_t* surfaceWidth, int32_t* return true; } +#ifndef __SYMBIAN32__ void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t dstId, int32_t dstX, int32_t dstY, int32_t srcId, int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH, bool part) { GLuint srcFbo, dstFbo; int32_t srcFboW, srcFboH; @@ -375,6 +379,7 @@ void GLCommon_surfaceBlit(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surf glBindFramebuffer(GL_DRAW_FRAMEBUFFER, originalFramebufferBinding); if (scissorWasEnabled) glEnable(GL_SCISSOR_TEST); } +#endif bool GLCommon_surfaceGetPixels(GLuint* surfaces, int32_t* surfaceWidth, int32_t* surfaceHeight, uint32_t count, int32_t surfaceId, uint8_t* outRGBA) { if (0 > surfaceId || (uint32_t) surfaceId >= count) @@ -498,7 +503,11 @@ GLenum GLCommon_blendModeToEquation(int mode) { case bm_add: return GL_FUNC_ADD; case bm_subtract: return GL_FUNC_ADD; case bm_reverse_subtract: return GL_FUNC_REVERSE_SUBTRACT; +#ifdef GL_MIN case bm_min: return GL_MIN; +#else + case bm_min: return GL_FUNC_ADD; +#endif case bm_max: return GL_FUNC_ADD; } } @@ -652,7 +661,11 @@ bool GLCommon_ensureDebugFontTexture(GLRenderer* gl, GLDebugUIFont* ui) { } glBindTexture(GL_TEXTURE_2D, ui->texture); +#ifdef __SYMBIAN32__ + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, DEBUGFONT_ATLAS_W, DEBUGFONT_ATLAS_W, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); +#else glTexImage2D(GL_TEXTURE_2D, 0, gl->textureFormat, DEBUGFONT_ATLAS_W, DEBUGFONT_ATLAS_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); +#endif glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); diff --git a/src/gl_common/gl_common.h b/src/gl_common/gl_common.h index e29f8722c..1cbfacf53 100644 --- a/src/gl_common/gl_common.h +++ b/src/gl_common/gl_common.h @@ -18,6 +18,9 @@ typedef struct GLRenderer GLRenderer; #include "rsxutil.h" #elif PLATFORM_VITA #include +#elif defined(__SYMBIAN32__) +#include +#include #else #include #endif @@ -188,6 +191,7 @@ struct GLRenderer { int32_t* textureHeights; bool* textureLoaded; // lazy loading: true once PNG decoded and uploaded uint32_t textureCount; + int* textureLastUsed; GLuint whiteTexture; // 1x1 white pixel for drawing primitives (rectangles, lines, etc.) diff --git a/src/gl_common/gl_wrappers.h b/src/gl_common/gl_wrappers.h index ffa92bb10..efb577a53 100644 --- a/src/gl_common/gl_wrappers.h +++ b/src/gl_common/gl_wrappers.h @@ -1,4 +1,4 @@ -#if !defined(_BS_GL_WRAPPERS_H_) && !defined(__EMSCRIPTEN__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__ANDROID__) && !defined(__SWITCH__) +#if !defined(_BS_GL_WRAPPERS_H_) && !defined(__EMSCRIPTEN__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__ANDROID__) && !defined(__SWITCH__) && !defined(__SYMBIAN32__) #define _BS_GL_WRAPPERS_H_ static inline void gl_init_wrappers(void) { diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index f51ae523c..544624cf4 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -524,7 +524,23 @@ bool GLLegacyRenderer_ensureTextureLoaded(GLRenderer* gl, uint32_t pageId) { gl->textureHeights[pageId] = h; glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); +#ifdef __SYMBIAN32__ + if (gl->textureFormat == GL_RGBA4) { + uint16_t* rgba4444 = (uint16_t*)safeMalloc(w * h * sizeof(uint16_t)); + for (int i = 0; i < w * h; i++) { + rgba4444[i] = (((uint16_t)pixels[i * 4] >> 4) << 12) | + (((uint16_t)pixels[i * 4 + 1] >> 4) << 8) | + (((uint16_t)pixels[i * 4 + 2] >> 4) << 4) | + ((uint16_t)pixels[i * 4 + 3] >> 4); + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, rgba4444); + free(rgba4444); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, gl->textureFormat, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); +#endif free(pixels); @@ -1498,6 +1514,7 @@ static uint32_t findOrAllocTexturePageSlot(GLRenderer* gl) { gl->textureWidths = (int32_t *)safeRealloc(gl->textureWidths, gl->textureCount * sizeof(int32_t)); gl->textureHeights = (int32_t *)safeRealloc(gl->textureHeights, gl->textureCount * sizeof(int32_t)); gl->textureLoaded = (bool *)safeRealloc(gl->textureLoaded, gl->textureCount * sizeof(bool)); + gl->textureLastUsed = (int *)safeRealloc(gl->textureLoaded, gl->textureCount * sizeof(int)); gl->glTextures[newPageId] = 0; gl->textureWidths[newPageId] = 0; gl->textureHeights[newPageId] = 0; @@ -1747,7 +1764,15 @@ static int32_t glLegacyCreateSurface(Renderer* renderer, int32_t width, int32_t glGenFramebuffers(1, &gl->surfaces[surfaceIndex]); glGenTextures(1, &gl->surfaceTexture[surfaceIndex]); glBindTexture(GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex]); +#ifdef __SYMBIAN32__ + if (GLCommon_surfaceInternalFormat(gl) == GL_RGBA4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); +#endif GLCommon_applyTexFilter(renderer->texFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); @@ -1824,7 +1849,15 @@ static void glLegacySurfaceResize(Renderer* renderer, int32_t surfaceId, int32_t glGenTextures(1, &gl->surfaceTexture[surfaceId]); glBindTexture(GL_TEXTURE_2D, gl->surfaceTexture[surfaceId]); +#ifdef __SYMBIAN32__ + if (GLCommon_surfaceInternalFormat(gl) == GL_RGBA4) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, nullptr); + } else { + glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, GLCommon_surfaceInternalFormat(gl), texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); +#endif GLCommon_applyTexFilter(renderer->texFilter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); diff --git a/src/loop.c b/src/loop.c index 2059c2aec..fa3c18df9 100644 --- a/src/loop.c +++ b/src/loop.c @@ -28,6 +28,9 @@ #ifndef __wasi__ #include #endif +#ifdef __SYMBIAN32__ +typedef int sig_atomic_t; +#endif #include "runner_keyboard.h" #include "runner.h" @@ -35,7 +38,7 @@ #include "debug_overlay.h" #include "debug_font/debug_font.h" #if (defined(ENABLE_LEGACY_GL) || defined(ENABLE_MODERN_GL) || ((defined(USE_GLFW3) || defined(USE_GLFW2)) && defined(ENABLE_SW_RENDERER))) && \ - !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) + !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) && !defined(PLATFORM_PS3) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) && !defined(__SYMBIAN32__) #define USE_GLAD #include #endif @@ -130,6 +133,8 @@ static size_t get_used_memory(void) { if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &count) == KERN_SUCCESS) { return info.resident_size; } +#elif defined(__SYMBIAN32__) + // TODO #elif defined(_WIN32) typedef BOOL (WINAPI *GetProcessMemoryInfo_t)(HANDLE, BS_PROCESS_MEMORY_COUNTERS*, DWORD); static GetProcessMemoryInfo_t func = NULL; @@ -174,7 +179,7 @@ static bool platformInitGlad(void) { } #endif -#if (defined(ENABLE_MODERN_GL) || defined(ENABLE_LEGACY_GL)) && !defined(NDEBUG) && !defined(PLATFORM_VITA) && !defined(PLATFORM_WEB) +#if (defined(ENABLE_MODERN_GL) || defined(ENABLE_LEGACY_GL)) && !defined(NDEBUG) && !defined(PLATFORM_VITA) && !defined(PLATFORM_WEB) && !defined(__SYMBIAN32__) #define USE_OPENGL_DEBUG static void APIENTRY glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, MAYBE_UNUSED GLsizei length, const GLchar* message, MAYBE_UNUSED const void* userParam) { const char* sourceStr; @@ -463,7 +468,7 @@ void saveInputRecording() { } } -#if !defined(_WIN32) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) && !defined(__wasi__) +#if !defined(_WIN32) && !defined(PLATFORM_VITA) && !defined(__SWITCH__) && !defined(__wasi__) && !defined(__SYMBIAN32__) #define USE_CRASH_SIGNAL_HANDLER typedef struct { int key; struct sigaction value; } PreviousSignalActionEntry; static PreviousSignalActionEntry* previousSignalActions = nullptr; @@ -515,25 +520,60 @@ static void PreProcessedStuff_free(void) { } // ===[ MAIN ]=== -int loop(CommandLineArgs args, const char *argv0) { +static CommandLineArgs args; +static char* currentDataWinPath; +static char** currentGameArgs; + +static bool platformInitialized; +static int32_t inputFrameCount; + +static bool fastForwardActive; +static bool fastForwardTabPrev; +static bool showDebugOverlay; + +static DataWin* dataWin; +static Gen8* gen8; +static VMContext* vm; +static Renderer* renderer; +static Runner* runner; + +static char* dataWinDir; +static OverlayFileSystem* overlayFs; + +static bool debugShowCollisionMasks; +static size_t overlayCachedMemBytes; +static uint64_t overlayLastMemCheck; +static bool freeCamActive; +static bool actuallyShuttingDown; +static bool wasPaused; +static uint64_t lastFrameTime; +static uint64_t lastFrameStartTime; +static bool shouldWindowClose; + +void App_init(CommandLineArgs aArgs, const char *argv0) { #ifdef _WIN32 timeBeginPeriod(1); #endif - char* currentDataWinPath = safeStrdup(args.dataWinPath); - char** currentGameArgs = args.gameArgs; + args = aArgs; + + currentDataWinPath = safeStrdup(args.dataWinPath); + currentGameArgs = args.gameArgs; repeat(arrlen(args.gameArgs), i) { arrput(currentGameArgs, args.gameArgs[i]); } // The first argument will ALWAYS be the argv[0] arrins(currentGameArgs, 0, safeStrdup(argv0 != nullptr ? argv0 : "")); - bool platformInitialized = false; - int32_t inputFrameCount = 0; + platformInitialized = false; + inputFrameCount = 0; - bool fastForwardActive = false; - bool fastForwardTabPrev = false; - bool showDebugOverlay = args.debug; - while (true) { + fastForwardActive = false; + fastForwardTabPrev = false; + showDebugOverlay = args.debug; +} + +int App_begin(void) { + { logInfo("Loading %s...\n", args.dataWinPath); DataWinParserOptions options = {0}; @@ -595,9 +635,9 @@ int loop(CommandLineArgs args, const char *argv0) { options.lazyLoadTextures = args.lazyTextures; options.lazyLoadAudio = args.lazyAudio; options.eagerlyLoadedRooms = args.eagerRooms; - DataWin* dataWin = DataWin_parse(currentDataWinPath, options); + dataWin = DataWin_parse(currentDataWinPath, options); - Gen8* gen8 = &dataWin->gen8; + gen8 = &dataWin->gen8; logInfo("Loaded \"%s\" (%d) successfully! [WAD Version %u / GameMaker version %u.%u.%u.%u]\n", gen8->name, gen8->gameID, gen8->wadVersion, dataWin->detectedFormat.major, dataWin->detectedFormat.minor, dataWin->detectedFormat.release, dataWin->detectedFormat.build); #ifdef HAVE_MALLINFO2 @@ -612,7 +652,7 @@ int loop(CommandLineArgs args, const char *argv0) { snprintf(windowTitle, sizeof(windowTitle), "Butterscotch - %s", gen8->displayName); // Initialize VM - VMContext* vm = VM_create(dataWin); + vm = VM_create(dataWin); Profiler_setEnabled(&vm->profiler, args.profilerFramesBetween > 0); #ifdef ENABLE_VM_OPCODE_PROFILER @@ -810,10 +850,10 @@ int loop(CommandLineArgs args, const char *argv0) { } // Initialize the file system - char* dataWinDir = safeStrdup(args.dataWinPath); + dataWinDir = safeStrdup(args.dataWinPath); bsGetDirname(dataWinDir); const char* savePath = args.saveFolder != nullptr ? args.saveFolder : dataWinDir; - OverlayFileSystem* overlayFs = OverlayFileSystem_create(dataWinDir, savePath); + overlayFs = OverlayFileSystem_create(dataWinDir, savePath); free(dataWinDir); gfx = args.renderer; @@ -893,7 +933,7 @@ int loop(CommandLineArgs args, const char *argv0) { // Initialize the renderer // NOTE: headless mode keeps rendering active (hidden window + normal renderer). // NOOP is a separate renderer that stubs all draw calls. - Renderer* renderer = nullptr; + renderer = nullptr; #ifdef ENABLE_SW_RENDERER if (gfx == SOFTWARE) renderer = SWRenderer_create(); @@ -974,7 +1014,7 @@ int loop(CommandLineArgs args, const char *argv0) { } // Initialize the runner - Runner* runner = Runner_create(dataWin, vm, renderer, (FileSystem*) overlayFs, audioSystem, args.seed); + runner = Runner_create(dataWin, vm, renderer, (FileSystem*) overlayFs, audioSystem, args.seed); if (!args.lazyTextures) { repeat(runner->dataWin->txtr.count, i) { @@ -1048,25 +1088,30 @@ int loop(CommandLineArgs args, const char *argv0) { // Initialize the first room and fire Game Start / Room Start events Runner_initFirstRoom(runner); - // Main loop - bool debugShowCollisionMasks = false; - size_t overlayCachedMemBytes = 0; - uint64_t overlayLastMemCheck = 0; - bool freeCamActive = false; - bool actuallyShuttingDown = false; - bool wasPaused = false; - uint64_t lastFrameTime = nowNanos(); - uint64_t lastFrameStartTime = lastFrameTime; // for delta_time - bool shouldWindowClose = false; - while (true) { + debugShowCollisionMasks = false; + overlayCachedMemBytes = 0; + overlayLastMemCheck = 0; + freeCamActive = false; + actuallyShuttingDown = false; + wasPaused = false; + lastFrameTime = nowNanos(); + lastFrameStartTime = lastFrameTime; // for delta_time + shouldWindowClose = false; + } + return LOOP_CONTINUE; +} + +int App_frame(void) { + { + { if (runner->shouldExit || shouldWindowClose) { actuallyShuttingDown = true; - break; + return 0; } if (runner->pendingWorkingDirectory != nullptr && runner->pendingLaunchParameters != nullptr) { // Break from the game loop, we'll handle this later - break; + return 0; } uint64_t frameStartNow = nowNanos(); @@ -1079,7 +1124,7 @@ int loop(CommandLineArgs args, const char *argv0) { RunnerMouse_beginFrame(runner->mouse); if (platformHandleEvents()) { shouldWindowClose = true; - continue; + return LOOP_CONTINUE; } if (RunnerKeyboard_checkPressed(runner->keyboard, VK_F8)) { @@ -1481,7 +1526,12 @@ int loop(CommandLineArgs args, const char *argv0) { } lastFrameTime = nowNanos(); } + } + return LOOP_CONTINUE; +} +int App_shutdown(void) { + { saveInputRecording(); // Snapshot any pending game_change request before we tear the runner down @@ -1616,6 +1666,23 @@ int loop(CommandLineArgs args, const char *argv0) { arrfree(newArguments); } } + return LOOP_CONTINUE; +} + +int loop(CommandLineArgs args, const char *argv0) { + App_init(args, argv0); + + int ret; + while (true) { + ret = App_begin(); + if (ret != LOOP_CONTINUE) break; + + while (App_frame() == LOOP_CONTINUE); + + ret = App_shutdown(); + if (ret != LOOP_CONTINUE) break; + } + return ret; } void freeCommandLineArgs(CommandLineArgs* args) { diff --git a/src/loop.h b/src/loop.h index f7c6bdd98..b963fb1ab 100644 --- a/src/loop.h +++ b/src/loop.h @@ -7,4 +7,11 @@ char** extractRunnerArguments(char* rawArguments); int loop(CommandLineArgs args, const char *argv0); void freeCommandLineArgs(CommandLineArgs* args); +#define LOOP_CONTINUE -1 + +void App_init(CommandLineArgs args, const char *argv0); +int App_begin(void); +int App_frame(void); +int App_shutdown(void); + #endif diff --git a/src/symbian/stb_impl.c b/src/symbian/stb_impl.c new file mode 100644 index 000000000..fad2849e1 --- /dev/null +++ b/src/symbian/stb_impl.c @@ -0,0 +1,8 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_ONLY_PNG +#define STBI_ONLY_JPEG +#define STBI_ONLY_GIF +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" diff --git a/src/symbian/symbian.cpp b/src/symbian/symbian.cpp new file mode 100644 index 000000000..6c3397ac1 --- /dev/null +++ b/src/symbian/symbian.cpp @@ -0,0 +1,475 @@ +#include "symbian.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "loop.h" +#include "log.h" +#include "gettime.h" +} + +static EGLDisplay eglDisplay; +static EGLContext eglContext; +static EGLSurface eglSurface; +static EGLConfig eglConfig; + +static Runner* g_runner; +static bool shouldExit; +static bool foreground = true; +static State state = EStateInit; + +static ButterscotchContainer* container; +static RArray keyEvents; + +TUid ButterscotchApp::AppDllUid() const { + return TUid::Uid(0xEC93FBDE); +} + +CApaDocument* ButterscotchApp::CreateDocumentL() { + return ButterscotchDocument::NewL(*this); +} + +void ButterscotchAppUi::ConstructL() { + BaseConstructL(CAknAppUi::EAknEnableSkin); + iContainer = new (ELeave) ButterscotchContainer; + iContainer->SetMopParent(this); + iContainer->ConstructL(ClientRect(), this); + container = iContainer; + AddToStackL(iContainer); +} +void ButterscotchAppUi::HandleForegroundEventL(TBool aForeground) { + foreground = aForeground; +} + +ButterscotchAppUi::~ButterscotchAppUi() { + if (iContainer) { + RemoveFromStack(iContainer); + delete iContainer; + } +} +void ButterscotchAppUi::HandleCommandL(TInt aCommand) { + if (aCommand == EAknSoftkeyBack || aCommand == EEikCmdExit) { + shouldExit = true; + } +} + +ButterscotchDocument::ButterscotchDocument(CEikApplication& aApp) : CAknDocument(aApp) {} + +ButterscotchDocument::~ButterscotchDocument() {} + +void ButterscotchDocument::ConstructL() {} + +ButterscotchDocument* ButterscotchDocument::NewL(CEikApplication& aApp) { + ButterscotchDocument* self = new (ELeave) ButterscotchDocument(aApp); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; +} + +// debug builds don't launch without this here, idk why +#if defined(__ARMCC__) && defined(_DEBUG) +#pragma O1 +#endif + +CEikAppUi* ButterscotchDocument::CreateAppUiL() { + return new (ELeave) ButterscotchAppUi; +} + +static CApaApplication* NewApplication() { + return new ButterscotchApp; +} + +#if defined(__ARMCC__) && defined(_DEBUG) +#pragma O0 +#endif + +TInt E32Main() { + User::SetFloatingPointMode(EFpModeRunFast); + return EikStart::RunApplication(NewApplication); +} + +// input mapping + +const uint8_t key_map[] = { + EStdKeyBackspace, VK_BACKSPACE, + EStdKeyTab, VK_TAB, + EStdKeyEnter, VK_ENTER, + EStdKeyEscape, VK_ESCAPE, + EStdKeySpace, VK_SPACE, + EStdKeyHome, VK_HOME, + EStdKeyEnd, VK_END, + EStdKeyPageUp, VK_PAGEUP, + EStdKeyPageDown, VK_PAGEDOWN, + EStdKeyInsert, VK_INSERT, + EStdKeyDelete, VK_DELETE, + EStdKeyLeftShift, VK_SHIFT, + EStdKeyRightShift, VK_SHIFT, + EStdKeyLeftAlt, VK_ALT, + EStdKeyRightAlt, VK_ALT, + EStdKeyLeftCtrl, VK_CONTROL, + EStdKeyRightCtrl, VK_CONTROL, + EStdKeyLeftFunc, VK_ALT, + EStdKeyRightFunc, VK_ALT, + + EStdKeyComma, ',', + EStdKeyFullStop, '.', + EStdKeyForwardSlash, '/', + EStdKeyBackSlash, '\\', + EStdKeySemiColon, ';', + EStdKeySingleQuote, '\'', + EStdKeyHash, '#', + EStdKeySquareBracketLeft, '[', + EStdKeySquareBracketRight, ']', + EStdKeyMinus, '-', + EStdKeyEquals, '+', + + EStdKeyNkpForwardSlash, '/', + EStdKeyNkpAsterisk, '*', + EStdKeyNkpMinus, '-', + EStdKeyNkpPlus, '+', + EStdKeyNkpEnter, VK_ENTER, + EStdKeyNkp1, '1', + EStdKeyNkp2, '2', + EStdKeyNkp3, '3', + EStdKeyNkp4, '4', + EStdKeyNkp5, '5', + EStdKeyNkp6, '6', + EStdKeyNkp7, '7', + EStdKeyNkp8, '8', + EStdKeyNkp9, '9', + EStdKeyNkp0, '0', + EStdKeyNkpFullStop, '.', + + EStdKeyDevice0, VK_F1, + EStdKeyDevice1, VK_ESCAPE, + EStdKeyDevice3, VK_ENTER, +}; + +static int MapScanCode(TInt aScanCode, TInt aModifiers) { + if (aScanCode == EStdKeyLeftArrow) { + if (aModifiers & EModifierRotateBy90) return VK_UP; + if (aModifiers & EModifierRotateBy180) return VK_RIGHT; + if (aModifiers & EModifierRotateBy270) return VK_DOWN; + return VK_LEFT; + } + if (aScanCode == EStdKeyRightArrow) { + if (aModifiers & EModifierRotateBy90) return VK_DOWN; + if (aModifiers & EModifierRotateBy180) return VK_LEFT; + if (aModifiers & EModifierRotateBy270) return VK_UP; + return VK_RIGHT; + } + if (aScanCode == EStdKeyUpArrow) { + if (aModifiers & EModifierRotateBy90) return VK_RIGHT; + if (aModifiers & EModifierRotateBy180) return VK_DOWN; + if (aModifiers & EModifierRotateBy270) return VK_LEFT; + return VK_UP; + } + if (aScanCode == EStdKeyDownArrow) { + if (aModifiers & EModifierRotateBy90) return VK_LEFT; + if (aModifiers & EModifierRotateBy180) return VK_UP; + if (aModifiers & EModifierRotateBy270) return VK_RIGHT; + return VK_DOWN; + } + if (aScanCode >= 'a' && aScanCode <= 'z') return toupper(aScanCode); + if (aScanCode >= '0' && aScanCode <= '9') return aScanCode; + + for (size_t i = 0; i < sizeof(key_map); i += 2) { + if (key_map[i] == aScanCode) { + return key_map[i + 1]; + } + } + + return aScanCode < 256 ? aScanCode : -1; +} + +// container impl + +static TInt LoopCallBack(TAny* p) { + ButterscotchContainer* container = (ButterscotchContainer*) p; + if (state == EStateExit) { + container->iAppUi->Exit(); + return EFalse; + } + + if (state == EStateInit) { + if (App_begin() != LOOP_CONTINUE) { + state = EStateExit; + return ETrue; + } + state = EStateTick; + return ETrue; + } else if (state == EStateTick) { + if (App_frame() != LOOP_CONTINUE) { + state = EStatePreExit; + } + return ETrue; + } else if (state == EStatePreExit) { + if (App_shutdown() != LOOP_CONTINUE) { + state = EStateExit; + return ETrue; + } + state = EStateInit; + return ETrue; + } + + return ETrue; +} + +void ButterscotchContainer::RestartTimerL(TInt aInterval) { + if (iPeriodic) iPeriodic->Cancel(); + else iPeriodic = CPeriodic::NewL(CActive::EPriorityLow); + iPeriodic->Start(aInterval, aInterval, TCallBack(LoopCallBack, this)); +} + +void ButterscotchContainer::ConstructL(const TRect& aRect, CAknAppUi* aAppUi) { + iAppUi = aAppUi; + + CAknWsEventMonitor* monitor = iAppUi->EventMonitor(); + monitor->AddObserverL(this); + monitor->Enable(); + + CreateWindowL(); + iAppUi->SetOrientationL(CAknAppUiBase::EAppUiOrientationLandscape); + SetExtentToWholeScreen(); + + SetFocus(ETrue); + +// Window().EnableAdvancedPointers(); + EnableDragEvents(); + ActivateL(); + + EGLint attribs[] = { + EGL_BUFFER_SIZE, 16, + EGL_DEPTH_SIZE, 16, + EGL_STENCIL_SIZE, 0, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_SAMPLES, 0, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(eglDisplay, NULL, NULL); + eglBindAPI(EGL_OPENGL_ES_API); + + EGLint numConfigs; + eglChooseConfig(eglDisplay, attribs, &eglConfig, 1, &numConfigs); + + EGLint contextAttribs[ 3 ] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs); + + RWindow& window = Window(); + eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, &window, NULL); + + eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); + + setlocale(LC_ALL, ""); + setlocale(LC_CTYPE, "C"); + setlocale(LC_COLLATE, "C"); + setlocale(LC_NUMERIC, "C"); + + CommandLineArgs args = {0}; + + args.exitAtFrame = -1; +#ifdef ENABLE_VM_TRACING + args.traceBytecodeAfterFrame = 0; +#endif + args.speedMultiplier = 1.0; + args.fastForwardSpeed = 0.0; + args.osType = OS_WINDOWS; + args.profilerFramesBetween = 0; + args.loadType = DATAWINLOADTYPE_LOAD_PER_CHUNK; + args.lazyRooms = true; + args.lazyTextures = true; + args.lazyAudio = true; + args.renderer = MODERN_GL; + args.dataWinPath = "E:/butterscotch/data.win"; + args.saveFolder = "E:/butterscotch/"; + + if (strstr((const char*) glGetString(GL_RENDERER), "VideoCore III") != NULL) { + args.glTextureFormat = GL_TEXTURE_FORMAT_RGBA4; + args.glSurfaceFormat = GL_SURFACE_FORMAT_RGBA4; + } + + args.debug = true; + + App_init(args, nullptr); + + RestartTimerL(10000); +} + +void ButterscotchContainer::HandleWsEventL(const TWsEvent &aEvent, CCoeControl *aDestination) { + if (!foreground || iAppUi->IsDisplayingDialog()) return; + + KeyEvent e; + e.type = aEvent.Type(); + + switch (aEvent.Type()) { + case EEventKeyDown: + case EEventKeyUp: { + int key = MapScanCode(aEvent.Key()->iScanCode, aEvent.Key()->iModifiers); + if (key != -1) { + e.value = key; + keyEvents.Append(e); + } + break; + } + case EEventKey: { + int code = aEvent.Key()->iCode; + if (code < ENonCharacterKeyBase || code > ENonCharacterKeyBase + ENonCharacterKeyCount) { + e.value = code; + keyEvents.Append(e); + } + break; + } + default: + break; + } +} + +void ButterscotchContainer::HandleResourceChange(TInt aType) { + switch (aType) { + case KEikDynamicLayoutVariantSwitch: + SetExtentToWholeScreen(); + break; + } +} + +// platform impl + +extern "C" void platformLog(const logType type, const char *format, va_list va) { +#ifdef _DEBUG + const char* textPrefix = ""; + char buffer[1024]; + + switch (type) { + case LOG_TYPE_NORMAL: + break; + case LOG_TYPE_WARNING: + textPrefix = "Warning: "; + break; + case LOG_TYPE_ERROR: + textPrefix = "Error: "; + break; + case LOG_TYPE_DEBUG: + textPrefix = "Debug: "; + break; + } + int written = snprintf(buffer, sizeof(buffer), "%s", textPrefix); + + if (written >= 0 && written < (int)sizeof(buffer)) { + vsnprintf(buffer + written, (int)sizeof(buffer) - written, format, va); + } + buffer[sizeof(buffer) - 1] = '\0'; + + RDebug::Printf("%s", buffer); +// puts(buffer); +#endif +} + +uint64_t nowNanos(void) { + TTime time; + time.HomeTime(); + return (uint64_t)(time.Int64() * 1000); +} + +bool platformInit(int32_t reqW, int32_t reqH, const char *title, bool headless) { + return true; +} + +void platformExit(void) { +} + +static bool platformGetWindowFocus(void) { + return foreground; +} + +bool platformGetWindowSize(int32_t* outW, int32_t* outH) { + if (!outW || !outH || !container) return false; + TSize size = container->Size(); + *outW = size.iWidth; + *outH = size.iHeight; + return true; +} + +void platformInitFunctions(Runner *runner) { + g_runner = runner; + g_runner->setCursor = NULL; + g_runner->windowHasFocus = platformGetWindowFocus; + g_runner->currentCursor = GML_CR_DEFAULT; +} + +bool platformGetScaledWindowSize(int32_t* outW, int32_t* outH) { + return platformGetWindowSize(outW, outH); +} + +void platformSetWindowSize(int32_t width, int32_t height) { +} + +void platformSetWindowTitle(const char* title) { +} + +void platformGetMousePos(double *xPos, double *yPos) { + if (!xPos || !yPos) return; + *xPos = 0.0; + *yPos = 0.0; +} + +void platformSwapBuffers(void) { + if (!foreground) return; + if (eglSwapBuffers(eglDisplay, eglSurface) == EGL_FALSE) { + // TODO handle context loss + } +} + +void *platformGetProcAddress(const char *name) { + return NULL; +} + +bool platformHandleEvents(void) { + const TInt count = keyEvents.Count(); + for (TInt i = 0; i < count; ++i) { + const KeyEvent &e = keyEvents[i]; + switch (e.type) { + case EEventKeyDown: + RunnerKeyboard_onKeyDown(g_runner->keyboard, e.value); + break; + case EEventKeyUp: + RunnerKeyboard_onKeyUp(g_runner->keyboard, e.value); + break; + case EEventKey: + RunnerKeyboard_onCharacter(g_runner->keyboard, e.value); + break; + } + } + keyEvents.Reset(); + + return shouldExit; +} + +void platformSleepUntil(uint64_t time) { + int64_t remaining = (int64_t)time - (int64_t)nowNanos(); + if (remaining > 5000000) { + int64_t micros = (remaining - 3000000) / 1000; + if (micros > 0) User::AfterHighRes(micros); + } + while (nowNanos() < time) { + YIELD(); + } +} + diff --git a/src/symbian/symbian.h b/src/symbian/symbian.h new file mode 100644 index 000000000..d39442b4e --- /dev/null +++ b/src/symbian/symbian.h @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +class ButterscotchDocument: public CAknDocument { +public: + static ButterscotchDocument* NewL(CEikApplication& aApp); + virtual ~ButterscotchDocument(); +protected: + void ConstructL(); +public: + ButterscotchDocument(CEikApplication& aApp); +private: + CEikAppUi* CreateAppUiL(); +}; + +class ButterscotchApp: public CAknApplication { +private: + CApaDocument* CreateDocumentL(); + TUid AppDllUid() const; +}; + +class ButterscotchContainer : public CCoeControl, MAknWsEventObserver { +public: + CAknAppUi* iAppUi; + CPeriodic* iPeriodic; + + void RestartTimerL(TInt aInterval); + void ConstructL(const TRect& aRect, CAknAppUi* aAppUi); + void HandleWsEventL(const TWsEvent &aEvent, CCoeControl *aDestination); + void HandleResourceChange(TInt aType); +}; + +class ButterscotchAppUi : public CAknAppUi { + ButterscotchContainer* iContainer; +public: + void ConstructL(); + void HandleForegroundEventL(TBool aForeground); + + ~ButterscotchAppUi(); + void HandleCommandL(TInt aCommand); +}; + +enum State { + EStateInit, + EStateTick, + EStatePreExit, + EStateExit, +}; + +struct KeyEvent { + int type, value; +}; diff --git a/src/symbian/symbian_atomics.c b/src/symbian/symbian_atomics.c new file mode 100644 index 000000000..f16914603 --- /dev/null +++ b/src/symbian/symbian_atomics.c @@ -0,0 +1,45 @@ +// shadows unimplemented compiler builtins for miniaudio to work + +#include + +TUint32 __sync_fetch_and_add_4(volatile void* a, TUint32 v) { + return __e32_atomic_add_ord32((volatile TAny*)a, v); +} + +TUint32 __sync_fetch_and_sub_4(volatile void* a, TUint32 v) { + return __e32_atomic_add_ord32((volatile TAny*)a, (TUint32)(0u - v)); +} + +TUint32 __sync_fetch_and_and_4(volatile void* a, TUint32 v) { + return __e32_atomic_and_ord32((volatile TAny*)a, v); +} + +TUint32 __sync_fetch_and_or_4(volatile void* a, TUint32 v) { + return __e32_atomic_ior_ord32((volatile TAny*)a, v); +} + +TUint32 __sync_add_and_fetch_4(volatile void* a, TUint32 v) { + return __e32_atomic_add_ord32((volatile TAny*)a, v) + v; +} + +TUint32 __sync_sub_and_fetch_4(volatile void* a, TUint32 v) { + return __e32_atomic_add_ord32((volatile TAny*)a, (TUint32)(0u - v)) - v; +} + +void __sync_lock_release_4(volatile void* a) { + __e32_atomic_store_rel32((volatile TAny*)a, 0); +} + +TUint32 __sync_lock_test_and_set_4(volatile void* a, TUint32 v) { + return __e32_atomic_swp_acq32((volatile TAny*)a, v); +} + +void __sync_synchronize(void) { + __e32_memory_barrier(); +} + +TUint32 __sync_val_compare_and_swap_4(volatile void* a, TUint32 aQ, TUint32 v) { + TUint32 q = aQ; + __e32_atomic_cas_ord32((volatile TAny*)a, &q, v); + return q; +} diff --git a/src/symbian/symbian_audio.cpp b/src/symbian/symbian_audio.cpp new file mode 100644 index 000000000..53033876e --- /dev/null +++ b/src/symbian/symbian_audio.cpp @@ -0,0 +1,306 @@ +// miniaudio custom driver implementation for symbian + +#include +#include +#include +#include +#include +#include +#include +#include "miniaudio.h" + +extern "C" { +#include "log.h" +} + +#define PERIOD_SIZE 2048 +#define BUFFER_SIZE (PERIOD_SIZE * 2 * sizeof(TInt16)) + +#define MAX_VOLUME 100 +#define DEFAULT_VOLUME 50 +#define VOLUME_STEP 10 + +class CAudioStream; + +static ma_context context; +static CAudioStream* stream; + +static int volume = DEFAULT_VOLUME; +static bool volumeChanged; + +static void InitVolumeKeysListenerL(); + +class CAudioStream: public CBase, public MMdaAudioOutputStreamCallback +{ +public: + static CAudioStream* NewL(ma_device* aDevice); + virtual ~CAudioStream(); + virtual void MaoscOpenComplete(TInt aError); + virtual void MaoscBufferCopied(TInt aError, const TDesC8& aBuffer); + virtual void MaoscPlayComplete(TInt aError); + void Open(); + void Stop(); + void Start(); + void Request(); + +protected: + CAudioStream(ma_device* aDevice); + void ConstructL(); + +private: + CMdaAudioOutputStream* iOutputStream; + TMdaAudioDataSettings iAudioSettings; + TPtrC8 iPtr; + ma_device* iDevice; + TBool iOpen; + TBool iStarted; + TUint8 iBuffers[2][BUFFER_SIZE]; + bool iCurrentBuffer; +}; + +CAudioStream::CAudioStream(ma_device* aDevice) : + iOutputStream(NULL), iDevice(aDevice), iOpen(EFalse), iStarted(EFalse) +{ +} + +CAudioStream* CAudioStream::NewL(ma_device* aDevice) { + CAudioStream* self = new (ELeave) CAudioStream(aDevice); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; +} + +void CAudioStream::ConstructL() { + iOutputStream = CMdaAudioOutputStream::NewL(*this); + iCurrentBuffer = 0; +} + +CAudioStream::~CAudioStream() { + if (iOpen) { + Stop(); + } + User::After(100000); + delete iOutputStream; +} + +void CAudioStream::Open() { + iAudioSettings.iCaps = TMdaAudioDataSettings::ERealTime | TMdaAudioDataSettings::ESampleRateFixed; + iOutputStream->Open(&iAudioSettings); +} + +void CAudioStream::Stop() { + iStarted = EFalse; + if (iOpen) { + iOutputStream->Stop(); + iOpen = EFalse; + } +} + +void CAudioStream::Start() { + iStarted = ETrue; + if (iOpen) { + Request(); + } else { + Open(); + } +} + +void CAudioStream::MaoscOpenComplete(TInt aError) { + if (aError != KErrNone) { + logError("Audio: Failed to open audio stream: %d", aError); + return; + } + + iOpen = ETrue; + iOutputStream->SetPriority(EMdaPriorityNormal, EMdaPriorityPreferenceTime); + TRAPD(err, iOutputStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate44100Hz, TMdaAudioDataSettings::EChannelsStereo)); + if (err != KErrNone) { + logError("Audio: Failed to set audio properties: %d", err); + return; + } + + iOutputStream->SetVolume((volume * iOutputStream->MaxVolume()) / MAX_VOLUME); + if (iStarted) { + Request(); + } +} + +void CAudioStream::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer) { + if (aError == KErrNone && iStarted) { + Request(); + } else { + iOutputStream->Stop(); + } +} + +void CAudioStream::MaoscPlayComplete(TInt aError) { + if (iStarted && aError == KErrNone) { + Request(); + } +} + +void CAudioStream::Request() { + if (!iOpen || !iStarted) + return; + + TUint8* buffer = iBuffers[iCurrentBuffer ? 1 : 0]; + if (ma_device_handle_backend_data_callback(iDevice, (ma_uint8*)buffer, NULL, PERIOD_SIZE) != MA_SUCCESS) + return; + + if (volumeChanged) { + volumeChanged = false; + iOutputStream->SetVolume((volume * iOutputStream->MaxVolume()) / 100); + } + + iPtr.Set(buffer, BUFFER_SIZE); + TRAP_IGNORE(iOutputStream->WriteL(iPtr)); + + iCurrentBuffer = !iCurrentBuffer; +} + +static ma_result onDeviceInit(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { + if (pDevice->type != ma_device_type_playback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + pDescriptorPlayback->format = ma_format_s16; + pDescriptorPlayback->channels = 2; + pDescriptorPlayback->sampleRate = 44100; + pDescriptorPlayback->periodSizeInFrames = PERIOD_SIZE; + + TRAPD(err, stream = CAudioStream::NewL(pDevice)); + if (err != KErrNone) { + return MA_FAILED_TO_INIT_BACKEND; + } + + TRAP_IGNORE(InitVolumeKeysListenerL()); + + return MA_SUCCESS; +} + +static ma_result onDeviceUninit(ma_device* pDevice) { + if (stream) { + delete stream; + stream = NULL; + } + return MA_SUCCESS; +} + +static ma_result onDeviceStart(ma_device* pDevice) { + if (stream) { + stream->Start(); + } + return MA_SUCCESS; +} + +static ma_result onDeviceStop(ma_device* pDevice) { + if (stream) { + stream->Stop(); + } + return MA_SUCCESS; +} + +static ma_result onContextEnumerateDevices(ma_context* pContext, ma_enum_devices_callback_proc onDevice, void* pUserData) { + ma_device_info info; + memset(&info, 0, sizeof(info)); + strcpy(info.name, "Symbian"); + info.isDefault = MA_TRUE; + onDevice(pContext, ma_device_type_playback, &info, pUserData); + return MA_SUCCESS; +} + +static ma_result onContextUninit(ma_context* pContext) { + return MA_SUCCESS; +} + +ma_result onContextInit(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + pCallbacks->onContextUninit = onContextUninit; + pCallbacks->onContextEnumerateDevices = onContextEnumerateDevices; + pCallbacks->onDeviceInit = onDeviceInit; + pCallbacks->onDeviceUninit = onDeviceUninit; + pCallbacks->onDeviceStart = onDeviceStart; + pCallbacks->onDeviceStop = onDeviceStop; + + return MA_SUCCESS; +} + +extern "C" ma_context* maSymbianAudioInit(void) { + ma_backend backends[] = { ma_backend_custom }; + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.custom.onContextInit = onContextInit; + + ma_result res = ma_context_init(backends, sizeof(backends) / sizeof(backends[0]), &contextConfig, &context); + if (res != MA_SUCCESS) { + return NULL; + } + + return &context; +} + +extern "C" void maSymbianAudioDestroy(void) { + ma_context_uninit(&context); +} + +// volume keys listener + +class CRemConObserver : public CBase, public MRemConCoreApiTargetObserver +{ +public: + static CRemConObserver* NewL() { + CRemConObserver* self = new (ELeave) CRemConObserver(); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + ~CRemConObserver() { +// if (iRemConTarget) { +// delete iRemConTarget; +// } + if (iRemConSelector) { + delete iRemConSelector; + } + } + +private: + CRemConObserver() {} + + void ConstructL() { + iRemConSelector = CRemConInterfaceSelector::NewL(); + iRemConTarget = CRemConCoreApiTarget::NewL(*iRemConSelector, *this); + iRemConSelector->OpenTargetL(); + } + + void MrccatoCommand(TRemConCoreApiOperationId aOperationId, TRemConCoreApiButtonAction aButtonAct) { + switch (aOperationId) { + case ERemConCoreApiVolumeUp: { + volume += VOLUME_STEP; + if (volume > MAX_VOLUME) volume = MAX_VOLUME; + + volumeChanged = true; + break; + } + case ERemConCoreApiVolumeDown: { + volume -= VOLUME_STEP; + if (volume < 0) volume = 0; + + volumeChanged = true; + break; + } + default: + break; + } + } + +private: + CRemConInterfaceSelector* iRemConSelector; + CRemConCoreApiTarget* iRemConTarget; +}; + +static CRemConObserver* observer = NULL; + +void InitVolumeKeysListenerL() { + if (!observer) observer = CRemConObserver::NewL(); +} diff --git a/src/utils.h b/src/utils.h index 763b9f5ad..05f834de8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -198,30 +198,30 @@ static inline void bsGetDirname(char* path) { } char* lastSlash = strrchr(path, '/'); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__SYMBIAN32__) char* lastBackslash = strrchr(path, '\\'); #endif char* target = nullptr; if (lastSlash != nullptr && (target == nullptr || lastSlash > target)) target = lastSlash; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__SYMBIAN32__) if (lastBackslash != nullptr && (target == nullptr || lastBackslash > target)) target = lastBackslash; #endif -#if defined(_WIN32) || defined(PLATFORM_VITA) +#if defined(_WIN32) || defined(PLATFORM_VITA) || defined(__SYMBIAN32__) if (target == nullptr) target = strrchr(path, ':'); #endif if (target) { -#if defined(_WIN32) || defined(PLATFORM_VITA) +#if defined(_WIN32) || defined(PLATFORM_VITA) || defined(__SYMBIAN32__) if (target[0] == ':') { target[1] = '\0'; } else #endif if (target == path -#if defined(_WIN32) || defined(PLATFORM_VITA) +#if defined(_WIN32) || defined(PLATFORM_VITA) || defined(__SYMBIAN32__) || target[0] == ':' #endif ) { diff --git a/vendor/miniaudio/miniaudio.h b/vendor/miniaudio/miniaudio.h index 5055a95b6..bb2af317c 100644 --- a/vendor/miniaudio/miniaudio.h +++ b/vendor/miniaudio/miniaudio.h @@ -12006,7 +12006,9 @@ static void ma_sleep__posix(ma_uint32 milliseconds) (void)milliseconds; MA_ASSERT(MA_FALSE); /* The Emscripten build should never sleep. */ #else - #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || defined(MA_SWITCH) + #if defined(__SYMBIAN32__) + usleep(milliseconds * 1000); + #elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || defined(MA_SWITCH) struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = milliseconds % 1000 * 1000000; @@ -14237,7 +14239,7 @@ typedef int ma_atomic_memory_order; #define MA_ATOMIC_LEGACY_MSVC_ASM #endif #endif - #elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) || defined(__clang__) + #elif (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) && !defined(__SYMBIAN32__)) || defined(__clang__) #define MA_ATOMIC_MODERN_GCC #else #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) @@ -17589,7 +17591,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority pAttr = &attr; /* We need to set the scheduler policy. Only do this if the OS supports pthread_attr_setschedpolicy() */ - #if !defined(MA_BEOS) + #if !defined(MA_BEOS) && !defined(__SYMBIAN32__) { if (priority == ma_thread_priority_idle) { #ifdef SCHED_IDLE @@ -17624,6 +17626,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority #endif + #if !defined(__SYMBIAN32__) if (scheduler != -1) { int priorityMin = sched_get_priority_min(scheduler); int priorityMax = sched_get_priority_max(scheduler); @@ -17664,6 +17667,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority } } } + #endif } #else /* It's the emscripten build. We'll have a few unused parameters. */ diff --git a/vendor/stb/ds/stb_ds.h b/vendor/stb/ds/stb_ds.h index 8be034fec..1614fbf13 100644 --- a/vendor/stb/ds/stb_ds.h +++ b/vendor/stb/ds/stb_ds.h @@ -692,7 +692,7 @@ enum STBDS_SH_ARENA }; -#if defined(__cplusplus) && (!defined(__GNUC__) || __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8)) +#if defined(__cplusplus) && (!defined(__GNUC__) || __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8)) && !defined(__SYMBIAN32__) // in C we use implicit assignment from these void*-returning functions to T*. // in C++ these templates make the same code work template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { diff --git a/vendor/stb/vorbis/stb_vorbis.c b/vendor/stb/vorbis/stb_vorbis.c index 2f47b56aa..0515198e4 100644 --- a/vendor/stb/vorbis/stb_vorbis.c +++ b/vendor/stb/vorbis/stb_vorbis.c @@ -927,10 +927,29 @@ static int error(vorb *f, enum STBVorbisError e) #define array_size_required(count,size) (count*(sizeof(void *)+(size))) +#ifdef __SYMBIAN32__ +// workaround for missing alloca on symbian +static char temp_mem[128 * 1024]; +static int temp_mem_offset = 0; + +static void *temp_mem_alloc(vorb *f, int sz) { + sz = (sz+7) & ~7; + if (temp_mem_offset + sz > sizeof(temp_mem)) return NULL; + void *p = (char *) temp_mem + temp_mem_offset; + temp_mem_offset += sz; + return p; +} + +#define temp_alloc(f,size) temp_mem_alloc(f, size) +#define temp_free(f,p) (void)0 +#define temp_alloc_save(f) (temp_mem_offset) +#define temp_alloc_restore(f,p) (temp_mem_offset = (p)) +#else #define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) #define temp_free(f,p) (void)0 #define temp_alloc_save(f) ((f)->temp_offset) #define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) +#endif #define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size)