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..a20fdea4e --- /dev/null +++ b/artifacts/symbian/Butterscotch.mmp @@ -0,0 +1,137 @@ +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 ../../src/vita +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 +SOURCEPATH ../../src/vita +SOURCE vita_textures.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 08581f6d5..8df986c35 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 @@ -107,11 +115,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; @@ -120,6 +143,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; @@ -185,6 +209,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 1d5cb68da..da34a932c 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -9,6 +9,10 @@ #elif PLATFORM_VITA #include #include "vita_textures.h" +#elif defined(__SYMBIAN32__) +#include +#include +#include "vita_textures.h" #else #include #endif @@ -69,7 +73,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; @@ -77,7 +81,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; @@ -286,7 +292,7 @@ static void flushBatch(GLRenderer* gl) { GLModernRenderer* modernGl = (GLModernRenderer*) gl; if (modernGl->batchCount == 0) return; - + if (gl->base.currentShader != -1) { GMLShader* shader = &modernGl->gmlShaders[gl->base.currentShader]; @@ -316,9 +322,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); @@ -486,9 +495,12 @@ static void glPrimitiveEnd(Renderer* renderer) { glBindBuffer(GL_ARRAY_BUFFER, modernGl->vbo); glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(count * sizeof(GlVertex)), modernGl->primitiveVertices, 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); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(GlVertex, x)); @@ -637,6 +649,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"; @@ -663,9 +676,15 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { "in vec2 vTexCoord;\nin vec4 vColor;\nin float vFogFactor;\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"; +#ifdef __SYMBIAN32__ + vertHeader = "#version 100\nprecision mediump float;\n"; +#else + vertHeader = "#version 100\nprecision high float;\n"; +#endif fragHeader = "#version 100\nprecision mediump float;\n"; } else { vertHeader = "#version 110\n"; @@ -695,7 +714,9 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { modernGl->defaultShaderProgram = defaultShader; modernGl->uTextureRect = glGetUniformLocation(defaultShader->shaderId, "uTextureRect"); modernGl->uLocalUV = glGetUniformLocation(defaultShader->shaderId, "uLocalUV"); +#ifndef __SYMBIAN32__ modernGl->depthWrite = true; +#endif modernGl->uWorldViewProjection = getShaderUniform(defaultShader, "uWorldViewProjection", GL_FLOAT_MAT4); modernGl->uWorldView = getShaderUniform(defaultShader, "uWorldView", GL_FLOAT_MAT4); @@ -764,11 +785,13 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glUniform2f(modernGl->uFogRange->location, 1.0f, 1.0f); // 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); @@ -790,6 +813,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: XYZ, normalized RGBA8, UV. int32_t stride = sizeof(GlVertex); @@ -801,6 +825,7 @@ static void glInit(Renderer* renderer, DataWin* dataWin) { glEnableVertexAttribArray(2); glBindVertexArray(0); } +#endif // Allocate CPU-side vertex buffer #if PLATFORM_VITA @@ -944,10 +969,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); @@ -959,15 +986,18 @@ static void glDestroy(Renderer* renderer) { free(modernGl->uAlphaTestEnabled); free(modernGl->uTexture); +#ifndef __SYMBIAN32__ for (uint32_t i = 0; i < gl->surfaceCount; i++) { if (modernGl->surfaceDepth[i]) glDeleteRenderbuffers(1, &modernGl->surfaceDepth[i]); } free(modernGl->surfaceDepth); +#endif free(modernGl->primitiveVertices); GLCommon_destroy(renderer); } static void glSetDepthState(Renderer* renderer, bool test, bool write, bool cull) { +#ifndef __SYMBIAN32__ GLModernRenderer* modernGl = (GLModernRenderer*)renderer; if (modernGl->depthTest == test && modernGl->depthWrite == write && modernGl->cull == cull) return; flushBatch((GLRenderer*)renderer); @@ -980,19 +1010,22 @@ static void glSetDepthState(Renderer* renderer, bool test, bool write, bool cull if (cull) glEnable(GL_CULL_FACE); else glDisable(GL_CULL_FACE); glCullFace(GL_BACK); glApplyCullWinding(renderer); +#endif } static void glClearDepthBuffer(Renderer* renderer, float depth) { +#ifndef __SYMBIAN32__ GLModernRenderer* modernGl = (GLModernRenderer*)renderer; flushBatch((GLRenderer*)renderer); glDepthMask(GL_TRUE); -#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(__SWITCH__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(__SWITCH__) || defined(__SYMBIAN32__) glClearDepthf(depth); #else glClearDepth(depth); #endif glClear(GL_DEPTH_BUFFER_BIT); glDepthMask(modernGl->depthWrite ? GL_TRUE : GL_FALSE); +#endif } static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32_t windowW, int32_t windowH) { @@ -1003,10 +1036,12 @@ static void glBeginFrame(Renderer* renderer, int32_t gameW, int32_t gameH, int32 modernGl->currentTextureId = 0; GLCommon_beginFrame(gl, gameW, gameH, windowW, windowH); +#ifndef __SYMBIAN32__ GLboolean scissor = glIsEnabled(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST); glClearDepthBuffer(renderer, 1.0f); if (scissor) glEnable(GL_SCISSOR_TEST); +#endif } static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UNUSED int32_t viewY, MAYBE_UNUSED int32_t viewW, MAYBE_UNUSED int32_t viewH, int32_t portX, int32_t portY, int32_t portW, int32_t portH, MAYBE_UNUSED float viewAngle) { @@ -1018,7 +1053,9 @@ static void glBeginView(Renderer* renderer, MAYBE_UNUSED int32_t viewX, MAYBE_UN VMBuiltins_applyD3DDefaultView(renderer, viewX, viewY, viewW, viewH, viewAngle); glClearDepthBuffer(renderer, 1.0f); +#ifndef __SYMBIAN32__ if (hasVAO()) glBindVertexArray(modernGl->vao); +#endif } static void glEndView(Renderer* renderer) { @@ -1051,7 +1088,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) { @@ -1075,8 +1114,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; @@ -1092,10 +1133,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); @@ -1141,14 +1185,68 @@ static void glClearScreen(Renderer* renderer, uint32_t color, float alpha) { glClearDepthBuffer(renderer, 1.0f); } +#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); + } +} +#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 defined(PLATFORM_VITA) || defined(__SYMBIAN32__) if (VitaTextures_Active()) { glBindTexture(GL_TEXTURE_2D, gl->glTextures[pageId]); if (!VitaTextures_LoadPage(pageId, &gl->textureWidths[pageId], &gl->textureHeights[pageId])) { @@ -1189,9 +1287,55 @@ 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*)pixels; + int n = w * h; + for (int i = 0; i < n; 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); + } + } + + 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); @@ -1206,6 +1350,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; @@ -1319,6 +1478,20 @@ static void drawTexture( uint32_t color, float alpha ) { + if (angleDeg == 0.0f) { + uint8_t r = (uint8_t) BGR_R(color); + uint8_t g = (uint8_t) BGR_G(color); + uint8_t b = (uint8_t) BGR_B(color); + float x0 = x + localX0 * xscale; + float y0 = y + localY0 * yscale; + float x1 = x + localX1 * xscale; + float y1 = y + localY1 * yscale; + + emitTexturedQuad(renderer, textureId, x0, y0, x1, y0, x1, y1, x0, y1, + u0, v0, u1, v1, r, g, b, r, g, b, r, g, b, r, g, b, alpha); + return; + } + // Build 2D transform: T(x,y) * R(-angleDeg) * S(xscale, yscale) // GML rotation is counter-clockwise, OpenGL rotation is counter-clockwise, but // since we have Y-down, we negate the angle to get the correct visual rotation @@ -1955,9 +2128,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); @@ -2057,8 +2232,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; } @@ -2076,8 +2253,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; } @@ -2088,8 +2267,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); } @@ -2297,8 +2478,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; @@ -2449,6 +2630,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; @@ -2478,15 +2660,26 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevBinding); uint32_t surfaceIndex = GLCommon_allocateSurfaceSlot(&gl->surfaces, &gl->surfaceTexture, &gl->surfaceWidth, &gl->surfaceHeight, &gl->surfaceCount); +#ifndef __SYMBIAN32__ GLModernRenderer* modernGl = (GLModernRenderer*)renderer; modernGl->surfaceDepth = (GLuint*)safeRealloc(modernGl->surfaceDepth, gl->surfaceCount * sizeof(GLuint)); modernGl->surfaceDepth[surfaceIndex] = 0; +#endif 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; @@ -2497,6 +2690,7 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceIndex]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceIndex], 0); +#ifndef __SYMBIAN32__ glGenRenderbuffers(1, &modernGl->surfaceDepth[surfaceIndex]); glBindRenderbuffer(GL_RENDERBUFFER, modernGl->surfaceDepth[surfaceIndex]); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); @@ -2507,6 +2701,7 @@ static int32_t glCreateSurface(Renderer* renderer, int32_t width, int32_t height if (scissorEnabled) glEnable(GL_SCISSOR_TEST); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) logError("GL: Incomplete depth-capable surface %u\n", surfaceIndex); +#endif gl->surfaceWidth[surfaceIndex] = width; gl->surfaceHeight[surfaceIndex] = height; @@ -2543,9 +2738,11 @@ static void glSurfaceFree(Renderer* renderer, int32_t surfaceID) { // Freeing the application_surface is a no-op from GML; the runner manages its lifecycle via application_surface_enable. if (surfaceID == renderer->runner->applicationSurfaceId) return; +#ifndef __SYMBIAN32__ GLModernRenderer* modernGl = (GLModernRenderer*)renderer; if (modernGl->surfaceDepth[surfaceID]) glDeleteRenderbuffers(1, &modernGl->surfaceDepth[surfaceID]); modernGl->surfaceDepth[surfaceID] = 0; +#endif if (gl->surfaceTexture[surfaceID] != 0) glDeleteTextures(1, &gl->surfaceTexture[surfaceID]); if (gl->surfaces[surfaceID] != 0) glDeleteFramebuffers(1, &gl->surfaces[surfaceID]); @@ -2571,7 +2768,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); @@ -2579,6 +2784,7 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width glBindFramebuffer(GL_FRAMEBUFFER, gl->surfaces[surfaceID]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl->surfaceTexture[surfaceID], 0); +#ifndef __SYMBIAN32__ GLModernRenderer* modernGl = (GLModernRenderer*)renderer; glBindRenderbuffer(GL_RENDERBUFFER, modernGl->surfaceDepth[surfaceID]); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); @@ -2586,6 +2792,7 @@ static void glSurfaceResize(Renderer* renderer, int32_t surfaceID, int32_t width glDisable(GL_SCISSOR_TEST); glClearDepthBuffer(renderer, 1.0f); if (scissorEnabled) glEnable(GL_SCISSOR_TEST); +#endif gl->surfaceWidth[surfaceID] = width; gl->surfaceHeight[surfaceID] = height; @@ -2679,9 +2886,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]; @@ -2894,7 +3104,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*)pixels; + int n = w * h; + for (int i = 0; i < n; 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, pixels); + 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 49831f97b..0be9e0c39 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 @@ -85,6 +88,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 57986f068..49268769a 100644 --- a/src/gl_common/gl_common.c +++ b/src/gl_common/gl_common.c @@ -12,7 +12,7 @@ #ifdef PLATFORM_PS3 #include "ps3_textures.h" -#elif PLATFORM_VITA +#elif defined(PLATFORM_VITA) || defined(__SYMBIAN32__) #include "vita_textures.h" #endif @@ -40,7 +40,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 @@ -55,7 +55,7 @@ void GLCommon_init(Renderer* renderer) { #ifdef PLATFORM_PS3 // TXTR is empty on PS3; page count comes from TEXTURES.BIN. gl->textureCount = PS3Textures_getPageCount(); -#elif defined(PLATFORM_VITA) +#elif defined(PLATFORM_VITA) || defined(__SYMBIAN32__) if (VitaTextures_Active()) gl->textureCount = VitaTextures_GetPageCount(); else @@ -68,6 +68,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); @@ -83,7 +84,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); @@ -299,6 +300,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); @@ -317,6 +319,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 ]=== @@ -344,6 +347,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; @@ -377,6 +381,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) @@ -500,7 +505,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; } } @@ -654,7 +663,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 9b5ad2302..c3bcc1930 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -1671,6 +1671,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; @@ -1937,7 +1938,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); @@ -2024,7 +2033,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..a2ac855bb 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 @@ -66,7 +69,7 @@ #include "profiler.h" #include "gettime.h" -#ifdef PLATFORM_VITA +#if defined(PLATFORM_VITA) || defined(__SYMBIAN32__) #include "vita_textures.h" #endif @@ -130,6 +133,9 @@ 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__) + extern size_t symbianGetUsedVram(void); + return symbianGetUsedVram(); #elif defined(_WIN32) typedef BOOL (WINAPI *GetProcessMemoryInfo_t)(HANDLE, BS_PROCESS_MEMORY_COUNTERS*, DWORD); static GetProcessMemoryInfo_t func = NULL; @@ -174,7 +180,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 +469,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; @@ -509,31 +515,66 @@ char* collapseNewlines(const char *input) { } static void PreProcessedStuff_free(void) { -#ifdef PLATFORM_VITA +#if defined(PLATFORM_VITA) || defined(__SYMBIAN32__) VitaTextures_Free(); #endif } // ===[ 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}; @@ -559,7 +600,7 @@ int loop(CommandLineArgs args, const char *argv0) { options.parseFunc = true; options.parseStrg = true; options.parseTxtr = true; -#ifdef PLATFORM_VITA +#if defined(PLATFORM_VITA) || defined(__SYMBIAN32__) do { char *texBinDir = safeStrdup(args.dataWinPath); bsGetDirname(texBinDir); @@ -595,9 +636,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 +653,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 +851,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 +934,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 +1015,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 +1089,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 +1125,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 +1527,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 +1667,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..33ed9446f --- /dev/null +++ b/src/symbian/symbian.cpp @@ -0,0 +1,514 @@ +#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, 0, + 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/"; + + 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); +#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(); + } +} + +typedef EGLBoolean (*NOK_resource_profiling)(EGLDisplay, EGLint, EGLint*, EGLint, EGLint*); +static EGLint data_count; +static EGLint* prof_data; +static NOK_resource_profiling eglQueryProfilingData = nullptr; + +extern "C" size_t symbianGetUsedVram(void) { + if (!eglQueryProfilingData) { + eglQueryProfilingData = (NOK_resource_profiling) eglGetProcAddress("eglQueryProfilingDataNOK"); + eglQueryProfilingData(eglDisplay, EGL_PROF_QUERY_GLOBAL_BIT_NOK | EGL_PROF_QUERY_MEMORY_USAGE_BIT_NOK, NULL, 0, &data_count); + prof_data = (EGLint*) User::Alloc(data_count * sizeof(EGLint)); + } + eglQueryProfilingData(eglDisplay, EGL_PROF_QUERY_GLOBAL_BIT_NOK | EGL_PROF_QUERY_MEMORY_USAGE_BIT_NOK, prof_data, data_count, &data_count); + + int i = 0, total = 0, used = 0; + while (i < data_count) { + switch (prof_data[i++]) { + case EGL_PROF_USED_MEMORY_NOK: + used = prof_data[i++]; + break; + case EGL_PROF_TOTAL_MEMORY_NOK: + total = prof_data[i++]; + break; + case EGL_PROF_PROCESS_ID_NOK: + if (sizeof(EGLNativeProcessIdTypeNOK) == 8) { + i += 2; + } else { + i++; + } + break; + case EGL_PROF_PROCESS_USED_PRIVATE_MEMORY_NOK: + break; + case EGL_PROF_PROCESS_USED_SHARED_MEMORY_NOK: + break; + default: + i++; + break; + } + } + + return used; +} + 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/src/vita/vita_textures.c b/src/vita/vita_textures.c index 6daa33548..df99545a3 100644 --- a/src/vita/vita_textures.c +++ b/src/vita/vita_textures.c @@ -5,7 +5,13 @@ #include #include #include + +#ifdef __SYMBIAN32__ +#define vglMalloc safeMalloc +#define vglFree free +#else #include +#endif static FILE* vitaTexBinF = NULL; static int pageCount = 0; @@ -41,11 +47,11 @@ bool VitaTextures_Init(FILE *binFile) { fseek(vitaTexBinF, pageSizes[i], SEEK_CUR); } - sceClibPrintf("Succesfully parsed textures.bin\n"); + logInfo("Succesfully parsed textures.bin\n"); return true; fail: if (vitaTexBinF) fclose(vitaTexBinF); - sceClibPrintf("textures.bin failed to load, bailin tf outta here\n"); + logError("textures.bin failed to load, bailin tf outta here\n"); vitaUsingTexBin = false; vitaTexBinF = NULL; return false; @@ -57,74 +63,86 @@ bool VitaTextures_LoadPage(int pageIdx, int* outWidth, int* outHeight) { int width, height; uint32_t *ext_data; fseek(vitaTexBinF, pageOffsets[pageIdx] + pageSizes[pageIdx], SEEK_SET); - uint32_t size = pageSizes[pageIdx] - 0x34; - uint32_t metadata_size; - fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x08, SEEK_SET); - uint64_t format; - fread(&format, 1, 8, vitaTexBinF); - fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x18, SEEK_SET); - fread(&height, 1, 4, vitaTexBinF); - fread(&width, 1, 4, vitaTexBinF); - fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x30, SEEK_SET); - fread(&metadata_size, 1, 4, vitaTexBinF); - size -= metadata_size; - ext_data = vglMalloc(size); - fseek(vitaTexBinF, metadata_size, SEEK_CUR); - fread(ext_data, 1, size, vitaTexBinF); - - switch (format) { - case 0x00: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, width, height, 0, size, ext_data); - break; - case 0x01: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, width, height, 0, size, ext_data); - break; - case 0x02: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, width, height, 0, size, ext_data); - break; - case 0x03: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, width, height, 0, size, ext_data); - break; - case 0x04: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG, width, height, 0, size, ext_data); - break; - case 0x05: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG, width, height, 0, size, ext_data); - break; - case 0x06: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, size, ext_data); - break; - case 0x07: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, width, height, 0, size, ext_data); - break; - case 0x09: - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, width, height, 0, size, ext_data); - break; - case 0x0B: - if (metadata_size == 4) { // Load DXT5 as pre-swizzled - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); - SceGxmTexture *gxm_tex = vglGetGxmTexture(GL_TEXTURE_2D); - vglFree(vglGetTexDataPointer(GL_TEXTURE_2D)); - void *tex_data = vglForceAlloc(size); - sceClibMemcpy(tex_data, ext_data, size); - sceGxmTextureInitSwizzledArbitrary(gxm_tex, tex_data, SCE_GXM_TEXTURE_FORMAT_UBC3_ABGR, width, height, 0); - vglOverloadTexDataPointer(GL_TEXTURE_2D, tex_data); - } else - glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, size, ext_data); - break; - default: - fseek(vitaTexBinF, pageOffsets[pageIdx], SEEK_SET); - if (isPNG(vitaTexBinF)) { - ext_data = (uint32_t*)stbi_load_from_file(vitaTexBinF, &width, &height, NULL, 4); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ext_data); - } else { - sceClibPrintf("Unsupported externalized texture format (0x%llX).\n", format); + if (isPNG(vitaTexBinF)) { + fseek(vitaTexBinF, pageOffsets[pageIdx], SEEK_SET); + ext_data = (uint32_t*)stbi_load_from_file(vitaTexBinF, &width, &height, NULL, 4); + if (!ext_data) return false; + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ext_data); + } else { + uint32_t size = pageSizes[pageIdx] - 0x34; + uint32_t metadata_size; + fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x08, SEEK_SET); + uint64_t format; + fread(&format, 1, 8, vitaTexBinF); + fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x18, SEEK_SET); + fread(&height, 1, 4, vitaTexBinF); + fread(&width, 1, 4, vitaTexBinF); + fseek(vitaTexBinF, pageOffsets[pageIdx] + 0x30, SEEK_SET); + fread(&metadata_size, 1, 4, vitaTexBinF); + size -= metadata_size; + ext_data = vglMalloc(size); + fseek(vitaTexBinF, metadata_size, SEEK_CUR); + fread(ext_data, 1, size, vitaTexBinF); + + switch (format) { +#ifndef __SYMBIAN32__ + case 0x00: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, width, height, 0, size, ext_data); + break; + case 0x01: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, width, height, 0, size, ext_data); + break; + case 0x02: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, width, height, 0, size, ext_data); + break; + case 0x03: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, width, height, 0, size, ext_data); + break; + case 0x04: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG, width, height, 0, size, ext_data); + break; + case 0x05: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG, width, height, 0, size, ext_data); + break; + case 0x07: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, width, height, 0, size, ext_data); + break; + case 0x09: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, width, height, 0, size, ext_data); + break; + case 0x0B: + if (metadata_size == 4) { // Load DXT5 as pre-swizzled + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + SceGxmTexture *gxm_tex = vglGetGxmTexture(GL_TEXTURE_2D); + vglFree(vglGetTexDataPointer(GL_TEXTURE_2D)); + void *tex_data = vglForceAlloc(size); + sceClibMemcpy(tex_data, ext_data, size); + sceGxmTextureInitSwizzledArbitrary(gxm_tex, tex_data, SCE_GXM_TEXTURE_FORMAT_UBC3_ABGR, width, height, 0); + vglOverloadTexDataPointer(GL_TEXTURE_2D, tex_data); + } else + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, width, height, 0, size, ext_data); + break; +#else + case 0x8033: { + GLint previousUnpackAlignment = 4; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &previousUnpackAlignment); + glPixelStorei(GL_UNPACK_ALIGNMENT, 2); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, ext_data); + glPixelStorei(GL_UNPACK_ALIGNMENT, previousUnpackAlignment); + break; + } +#endif + case 0x06: + glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_ETC1_RGB8_OES, width, height, 0, size, ext_data); + break; + default: + logError("Unsupported externalized texture format (0x%llX).\n", format); *outWidth = 0; *outHeight = 0; vglFree(ext_data); return false; - } - break; + } } vglFree(ext_data); diff --git a/src/vita/vita_textures.h b/src/vita/vita_textures.h index fa0a3f651..f0a347582 100644 --- a/src/vita/vita_textures.h +++ b/src/vita/vita_textures.h @@ -1,7 +1,7 @@ #ifndef _BS_VITA_TEXTURES_H_ #define _BS_VITA_TEXTURES_H_ -#include +#include "gl_common.h" #include #include #include 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)