diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d4157062c..2b07111e86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -730,10 +730,10 @@ if(ANDROID) android/jni/AndroidVulkanContext.cpp android/jni/AndroidVulkanContext.h android/jni/AndroidGraphicsContext.h - android/jni/native_audio.cpp - android/jni/native_audio.h - android/jni/native-audio-so.cpp - android/jni/native-audio-so.h + android/jni/AndroidAudio.cpp + android/jni/AndroidAudio.h + android/jni/OpenSLContext.cpp + android/jni/OpenSLContext.h ) # No target elseif(IOS) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index bb59188e86..20bddb0d1f 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -255,8 +255,6 @@ std::string NativeQueryConfig(std::string query) { int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 1; snprintf(temp, sizeof(temp), "%d", std::min(scale, max_res)); return std::string(temp); - } else if (query == "force44khz") { - return std::string("0"); } else if (query == "androidJavaGL") { // If we're using Vulkan, we say no... need C++ to use Vulkan. if (GetGPUBackend() == GPUBackend::VULKAN) { @@ -805,29 +803,34 @@ static void UIThemeInit() { } void RenderOverlays(UIContext *dc, void *userdata); +bool CreateGlobalPipelines(); bool NativeInitGraphics(GraphicsContext *graphicsContext) { ILOG("NativeInitGraphics"); - _assert_msg_(graphicsContext, "No graphics context!"); // We set this now so any resize during init is processed later. resized = false; - using namespace Draw; Core_SetGraphicsContext(graphicsContext); g_draw = graphicsContext->GetDrawContext(); - _assert_msg_(g_draw, "No draw context available!"); - _assert_msg_(g_draw->GetVshaderPreset(VS_COLOR_2D) != nullptr, "Failed to compile presets"); + + if (!CreateGlobalPipelines()) { + ERROR_LOG(G3D, "Failed to create global pipelines"); + return false; + } // Load the atlas. - size_t atlas_data_size = 0; if (!g_ui_atlas.IsMetadataLoaded()) { const uint8_t *atlas_data = VFSReadFile("ui_atlas.meta", &atlas_data_size); bool load_success = atlas_data != nullptr && g_ui_atlas.Load(atlas_data, atlas_data_size); - _assert_msg_(load_success, "Failed to load ui_atlas.meta"); + if (!load_success) { + ERROR_LOG(G3D, "Failed to load ui_atlas.meta - graphics will be broken."); + // Stumble along with broken visuals instead of dying. + } delete[] atlas_data; } + ui_draw2d.SetAtlas(&g_ui_atlas); ui_draw2d_front.SetAtlas(&g_ui_atlas); @@ -836,43 +839,10 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { uiContext = new UIContext(); uiContext->theme = &ui_theme; - Draw::InputLayout *inputLayout = ui_draw2d.CreateInputLayout(g_draw); - Draw::BlendState *blendNormal = g_draw->CreateBlendState({ true, 0xF, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA }); - Draw::DepthStencilState *depth = g_draw->CreateDepthStencilState({ false, false, Comparison::LESS }); - Draw::RasterState *rasterNoCull = g_draw->CreateRasterState({}); - - PipelineDesc colorDesc{ - Primitive::TRIANGLE_LIST, - { g_draw->GetVshaderPreset(VS_COLOR_2D), g_draw->GetFshaderPreset(FS_COLOR_2D) }, - inputLayout, depth, blendNormal, rasterNoCull, &vsColBufDesc, - }; - PipelineDesc texColorDesc{ - Primitive::TRIANGLE_LIST, - { g_draw->GetVshaderPreset(VS_TEXTURE_COLOR_2D), g_draw->GetFshaderPreset(FS_TEXTURE_COLOR_2D) }, - inputLayout, depth, blendNormal, rasterNoCull, &vsTexColBufDesc, - }; - - colorPipeline = g_draw->CreateGraphicsPipeline(colorDesc); - texColorPipeline = g_draw->CreateGraphicsPipeline(texColorDesc); - - _assert_(colorPipeline); - _assert_(texColorPipeline); - - // Release these now, reference counting should ensure that they get completely released - // once we delete both pipelines. - inputLayout->Release(); - rasterNoCull->Release(); - blendNormal->Release(); - depth->Release(); - ui_draw2d.Init(g_draw, texColorPipeline); ui_draw2d_front.Init(g_draw, texColorPipeline); uiContext->Init(g_draw, texColorPipeline, colorPipeline, &ui_draw2d, &ui_draw2d_front); - RasterStateDesc desc; - desc.cull = CullMode::NONE; - desc.frontFace = Facing::CCW; - if (uiContext->Text()) uiContext->Text()->SetFont("Tahoma", 20, 0); @@ -899,13 +869,54 @@ bool NativeInitGraphics(GraphicsContext *graphicsContext) { g_gameInfoCache = new GameInfoCache(); - if (gpu) + if (gpu) { gpu->DeviceRestore(); + } ILOG("NativeInitGraphics completed"); return true; } +bool CreateGlobalPipelines() { + using namespace Draw; + + InputLayout *inputLayout = ui_draw2d.CreateInputLayout(g_draw); + BlendState *blendNormal = g_draw->CreateBlendState({ true, 0xF, BlendFactor::SRC_ALPHA, BlendFactor::ONE_MINUS_SRC_ALPHA }); + DepthStencilState *depth = g_draw->CreateDepthStencilState({ false, false, Comparison::LESS }); + RasterState *rasterNoCull = g_draw->CreateRasterState({}); + + PipelineDesc colorDesc{ + Primitive::TRIANGLE_LIST, + { g_draw->GetVshaderPreset(VS_COLOR_2D), g_draw->GetFshaderPreset(FS_COLOR_2D) }, + inputLayout, depth, blendNormal, rasterNoCull, &vsColBufDesc, + }; + PipelineDesc texColorDesc{ + Primitive::TRIANGLE_LIST, + { g_draw->GetVshaderPreset(VS_TEXTURE_COLOR_2D), g_draw->GetFshaderPreset(FS_TEXTURE_COLOR_2D) }, + inputLayout, depth, blendNormal, rasterNoCull, &vsTexColBufDesc, + }; + + colorPipeline = g_draw->CreateGraphicsPipeline(colorDesc); + if (!colorPipeline) { + // Something really critical is wrong, don't care much about correct releasing of the states. + return false; + } + + texColorPipeline = g_draw->CreateGraphicsPipeline(texColorDesc); + if (!texColorPipeline) { + // Something really critical is wrong, don't care much about correct releasing of the states. + return false; + } + + // Release these now, reference counting should ensure that they get completely released + // once we delete both pipelines. + inputLayout->Release(); + rasterNoCull->Release(); + blendNormal->Release(); + depth->Release(); + return true; +} + void NativeShutdownGraphics() { screenManager->deviceLost(); @@ -914,12 +925,12 @@ void NativeShutdownGraphics() { ILOG("NativeShutdownGraphics"); -#ifdef _WIN32 +#if PPSSPP_PLATFORM(WINDOWS) delete winAudioBackend; winAudioBackend = nullptr; #endif -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) if (winCamera) { winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::SHUTDOWN, nullptr }); while (!winCamera->isShutDown()) {};// Wait for shutting down. @@ -1088,7 +1099,7 @@ void NativeRender(GraphicsContext *graphicsContext) { screenManager->resized(); // TODO: Move this to the GraphicsContext objects for each backend. -#if !defined(_WIN32) && !defined(ANDROID) +#if !PPSSPP_PLATFORM(WINDOWS) && !defined(ANDROID) PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; NativeMessageReceived("gpu_resized", ""); @@ -1135,7 +1146,7 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { if (msg == "core_powerSaving") { if (value != "false") { auto sy = GetI18NCategory("System"); -#ifdef __ANDROID__ +#if PPSSPP_PLATFORM(ANDROID) osm.Show(sy->T("WARNING: Android battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving"); #else osm.Show(sy->T("WARNING: Battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving"); @@ -1144,7 +1155,7 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { Core_SetPowerSaving(value != "false"); } if (msg == "permission_granted" && value == "storage") { -#ifdef __ANDROID__ +#if PPSSPP_PLATFORM(ANDROID) CreateDirectoriesAndroid(); #endif // We must have failed to load the config before, so load it now to avoid overwriting the old config diff --git a/Windows/GPU/WindowsGLContext.cpp b/Windows/GPU/WindowsGLContext.cpp index 9e5e028fc2..9646cf25e9 100644 --- a/Windows/GPU/WindowsGLContext.cpp +++ b/Windows/GPU/WindowsGLContext.cpp @@ -399,17 +399,23 @@ bool WindowsGLContext::InitFromRenderThread(std::string *error_message) { pauseRequested = false; resumeRequested = false; + CheckGLExtensions(); + draw_ = Draw::T3DCreateGLContext(); + bool success = draw_->CreatePresets(); // if we get this far, there will always be a GLSL compiler capable of compiling these. + if (!success) { + delete draw_; + draw_ = nullptr; + ReleaseGLContext(); + return false; + } + // These are auto-reset events. pauseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); resumeEvent = CreateEvent(NULL, FALSE, FALSE, NULL); - CheckGLExtensions(); - draw_ = Draw::T3DCreateGLContext(); renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); SetGPUBackend(GPUBackend::OPENGL); - bool success = draw_->CreatePresets(); // if we get this far, there will always be a GLSL compiler capable of compiling these. - _assert_msg_(success, "Failed to compile preset shaders"); renderManager_->SetSwapFunction([&]() {::SwapBuffers(hDC); }); if (wglSwapIntervalEXT) { // glew loads wglSwapIntervalEXT if available @@ -430,14 +436,10 @@ void WindowsGLContext::Shutdown() { glslang::FinalizeProcess(); } -void WindowsGLContext::ShutdownFromRenderThread() { - delete draw_; - draw_ = nullptr; - CloseHandle(pauseEvent); - CloseHandle(resumeEvent); +void WindowsGLContext::ReleaseGLContext() { if (hRC) { // Are we able to release the DC and RC contexts? - if (!wglMakeCurrent(NULL,NULL)) { + if (!wglMakeCurrent(NULL, NULL)) { MessageBox(NULL, L"Release of DC and RC failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION); } @@ -458,6 +460,14 @@ void WindowsGLContext::ShutdownFromRenderThread() { hWnd_ = NULL; } +void WindowsGLContext::ShutdownFromRenderThread() { + delete draw_; + draw_ = nullptr; + CloseHandle(pauseEvent); + CloseHandle(resumeEvent); + ReleaseGLContext(); +} + void WindowsGLContext::Resize() { } diff --git a/Windows/GPU/WindowsGLContext.h b/Windows/GPU/WindowsGLContext.h index 1303aece79..df8190f9dc 100644 --- a/Windows/GPU/WindowsGLContext.h +++ b/Windows/GPU/WindowsGLContext.h @@ -34,6 +34,8 @@ public: Draw::DrawContext *GetDrawContext() override { return draw_; } private: + void ReleaseGLContext(); + bool renderThread_; Draw::DrawContext *draw_; GLRenderManager *renderManager_; diff --git a/Windows/PPSSPP.vcxproj b/Windows/PPSSPP.vcxproj index ed8abc3a30..def7547093 100644 --- a/Windows/PPSSPP.vcxproj +++ b/Windows/PPSSPP.vcxproj @@ -600,7 +600,7 @@ true true - + true true true @@ -610,7 +610,7 @@ true true - + true true true @@ -1028,7 +1028,7 @@ - + true true true @@ -1038,7 +1038,7 @@ true true - + true true true diff --git a/Windows/PPSSPP.vcxproj.filters b/Windows/PPSSPP.vcxproj.filters index 451be96f95..ffbdc51429 100644 --- a/Windows/PPSSPP.vcxproj.filters +++ b/Windows/PPSSPP.vcxproj.filters @@ -205,12 +205,6 @@ Other Platforms\Android - - Other Platforms\Android - - - Other Platforms\Android - Other Platforms\iOS @@ -271,6 +265,12 @@ Other Platforms\UWP + + Other Platforms\Android + + + Other Platforms\Android + @@ -430,12 +430,6 @@ Other Platforms\Android - - Other Platforms\Android - - - Other Platforms\Android - Other Platforms\iOS @@ -538,6 +532,12 @@ Other Platforms\UWP + + Other Platforms\Android + + + Other Platforms\Android + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 510c6a0a1d..3df8536529 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -499,8 +499,8 @@ LOCAL_SRC_FILES := \ $(SRC)/android/jni/AndroidEGLContext.cpp \ $(SRC)/android/jni/AndroidJavaGLContext.cpp \ $(SRC)/android/jni/AndroidVulkanContext.cpp \ - $(SRC)/android/jni/native_audio.cpp \ - $(SRC)/android/jni/native-audio-so.cpp \ + $(SRC)/android/jni/AndroidAudio.cpp \ + $(SRC)/android/jni/OpenSLContext.cpp \ $(SRC)/UI/BackgroundAudio.cpp \ $(SRC)/UI/DiscordIntegration.cpp \ $(SRC)/UI/ChatScreen.cpp \ diff --git a/android/jni/native_audio.cpp b/android/jni/AndroidAudio.cpp similarity index 79% rename from android/jni/native_audio.cpp rename to android/jni/AndroidAudio.cpp index a06159a89c..98cf3a87be 100644 --- a/android/jni/native_audio.cpp +++ b/android/jni/AndroidAudio.cpp @@ -1,6 +1,18 @@ #include "base/logging.h" -#include "android/jni/native_audio.h" -#include "android/jni/native-audio-so.h" +#include "android/jni/AndroidAudio.h" +#include "android/jni/OpenSLContext.h" + +AudioContext::AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate) + : audioCallback(cb), framesPerBuffer(_FramesPerBuffer), sampleRate(_SampleRate) { + if (framesPerBuffer == 0) + framesPerBuffer = 256; + if (framesPerBuffer < 32) + framesPerBuffer = 32; + if (framesPerBuffer > 4096) + framesPerBuffer = 4096; + + sampleRate = _SampleRate; +} struct AndroidAudioState { AudioContext *ctx = nullptr; diff --git a/android/jni/native_audio.h b/android/jni/AndroidAudio.h similarity index 55% rename from android/jni/native_audio.h rename to android/jni/AndroidAudio.h index 427f29ca36..a3d1ab8ee8 100644 --- a/android/jni/native_audio.h +++ b/android/jni/AndroidAudio.h @@ -1,8 +1,22 @@ #pragma once -#include "native-audio-so.h" #include +typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); + +class AudioContext { +public: + AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate); + virtual bool Init() { return false; } + virtual ~AudioContext() {} + +protected: + AndroidAudioCallback audioCallback; + + int framesPerBuffer; + int sampleRate; +}; + struct AndroidAudioState; // It's okay for optimalFramesPerBuffer and optimalSampleRate to be 0. Defaults will be used. diff --git a/android/jni/AndroidJavaGLContext.cpp b/android/jni/AndroidJavaGLContext.cpp index afc5de6921..d1b8e17a6a 100644 --- a/android/jni/AndroidJavaGLContext.cpp +++ b/android/jni/AndroidJavaGLContext.cpp @@ -4,6 +4,7 @@ #include "base/display.h" #include "base/NativeApp.h" #include "gfx_es2/gpu_features.h" +#include "Common/Log.h" #include "Core/ConfigValues.h" #include "Core/System.h" @@ -17,12 +18,11 @@ bool AndroidJavaEGLGraphicsContext::InitFromRenderThread(ANativeWindow *wnd, int // OpenGL handles rotated rendering in the driver. g_display_rotation = DisplayRotation::ROTATE_0; g_display_rot_matrix.setIdentity(); - draw_ = Draw::T3DCreateGLContext(); + draw_ = Draw::T3DCreateGLContext(); // Can't fail renderManager_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); renderManager_->SetInflightFrames(g_Config.iInflightFrames); - bool success = draw_->CreatePresets(); - _assert_msg_(success, "Failed to compile preset shaders"); - return success; + draw_->CreatePresets(); + return true; } void AndroidJavaEGLGraphicsContext::ShutdownFromRenderThread() { diff --git a/android/jni/AndroidVulkanContext.cpp b/android/jni/AndroidVulkanContext.cpp index 06b09dae9b..76fc4d3e3c 100644 --- a/android/jni/AndroidVulkanContext.cpp +++ b/android/jni/AndroidVulkanContext.cpp @@ -71,7 +71,6 @@ bool AndroidVulkanContext::InitAPI() { } g_Vulkan->ChooseDevice(physicalDevice); - // Here we can enable device extensions if we like. ILOG("Creating Vulkan device"); if (g_Vulkan->CreateDevice() != VK_SUCCESS) { @@ -82,6 +81,7 @@ bool AndroidVulkanContext::InitAPI() { g_Vulkan = nullptr; return false; } + ILOG("Vulkan device created!"); return true; } diff --git a/android/jni/native-audio-so.cpp b/android/jni/OpenSLContext.cpp similarity index 80% rename from android/jni/native-audio-so.cpp rename to android/jni/OpenSLContext.cpp index 17e665d2de..26965a72d5 100644 --- a/android/jni/native-audio-so.cpp +++ b/android/jni/OpenSLContext.cpp @@ -2,8 +2,7 @@ // // Loosely based on the Android NDK sample code. -#include -#include +#include #include // for native audio @@ -11,19 +10,8 @@ #include #include "base/logging.h" -#include "native-audio-so.h" - -AudioContext::AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate) - : audioCallback(cb), framesPerBuffer(_FramesPerBuffer), sampleRate(_SampleRate) { - if (framesPerBuffer == 0) - framesPerBuffer = 256; - if (framesPerBuffer < 32) - framesPerBuffer = 32; - if (framesPerBuffer > 4096) - framesPerBuffer = 4096; - - sampleRate = _SampleRate; -} +#include "Common/Log.h" +#include "OpenSLContext.h" // This callback handler is called every time a buffer finishes playing. // The documentation available is very unclear about how to best manage buffers. @@ -36,7 +24,7 @@ void OpenSLContext::bqPlayerCallbackWrap(SLAndroidSimpleBufferQueueItf bq, void void OpenSLContext::BqPlayerCallback(SLAndroidSimpleBufferQueueItf bq) { if (bq != bqPlayerBufferQueue) { - ELOG("Wrong bq!"); + ELOG("OpenSL: Wrong bq!"); return; } @@ -53,7 +41,7 @@ void OpenSLContext::BqPlayerCallback(SLAndroidSimpleBufferQueueItf bq) { // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error if (result != SL_RESULT_SUCCESS) { - ELOG("OpenSL ES: Failed to enqueue! %i %i", renderedFrames, sizeInBytes); + ELOG("OpenSL: Failed to enqueue! %i %i", renderedFrames, sizeInBytes); } curBuffer += 1; // Switch buffer @@ -70,24 +58,26 @@ bool OpenSLContext::Init() { // create engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); if (result != SL_RESULT_SUCCESS) { - ELOG("OpenSL ES: Failed to create the engine: %d", (int)result); + ELOG("OpenSL: Failed to create the engine: %d", (int)result); engineObject = nullptr; return false; } result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0); if (result != SL_RESULT_SUCCESS) { - ELOG("OpenSL ES: Failed to create output mix: %d", (int)result); + ELOG("OpenSL: Failed to create output mix: %d", (int)result); (*engineObject)->Destroy(engineObject); engineEngine = nullptr; engineObject = nullptr; return false; } result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); // The constants, such as SL_SAMPLINGRATE_44_1, are just 44100000. SLuint32 sr = (SLuint32)sampleRate * 1000; @@ -114,7 +104,7 @@ bool OpenSLContext::Init() { const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req); if (result != SL_RESULT_SUCCESS) { - ELOG("OpenSL ES: CreateAudioPlayer failed: %d", (int)result); + ELOG("OpenSL: CreateAudioPlayer failed: %d", (int)result); (*outputMixObject)->Destroy(outputMixObject); outputMixObject = nullptr; @@ -126,18 +116,18 @@ bool OpenSLContext::Init() { } result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, &bqPlayerCallbackWrap, this); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); - assert(SL_RESULT_SUCCESS == result); + _assert_(SL_RESULT_SUCCESS == result); // Allocate and enqueue N empty buffers. for (int i = 0; i < NUM_BUFFERS; i++) { @@ -159,7 +149,7 @@ bool OpenSLContext::Init() { // shut down the native audio system OpenSLContext::~OpenSLContext() { if (bqPlayerPlay) { - ILOG("OpenSLWrap_Shutdown - stopping playback"); + ILOG("OpenSL: Shutdown - stopping playback"); SLresult result; result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); if (SL_RESULT_SUCCESS != result) { @@ -167,7 +157,7 @@ OpenSLContext::~OpenSLContext() { } } - ILOG("OpenSLWrap_Shutdown - deleting player object"); + ILOG("OpenSL: Shutdown - deleting player object"); if (bqPlayerObject) { (*bqPlayerObject)->Destroy(bqPlayerObject); @@ -177,14 +167,14 @@ OpenSLContext::~OpenSLContext() { bqPlayerVolume = nullptr; } - ILOG("OpenSLWrap_Shutdown - deleting mix object"); + ILOG("OpenSL: Shutdown - deleting mix object"); if (outputMixObject) { (*outputMixObject)->Destroy(outputMixObject); outputMixObject = nullptr; } - ILOG("OpenSLWrap_Shutdown - deleting engine object"); + ILOG("OpenSL: Shutdown - deleting engine object"); if (engineObject) { (*engineObject)->Destroy(engineObject); @@ -196,6 +186,6 @@ OpenSLContext::~OpenSLContext() { delete[] buffer[i]; buffer[i] = nullptr; } - ILOG("OpenSLWrap_Shutdown - finished"); + ILOG("OpenSL: Shutdown - finished"); } diff --git a/android/jni/native-audio-so.h b/android/jni/OpenSLContext.h similarity index 73% rename from android/jni/native-audio-so.h rename to android/jni/OpenSLContext.h index 9623e51709..3705f66b82 100644 --- a/android/jni/native-audio-so.h +++ b/android/jni/OpenSLContext.h @@ -3,20 +3,7 @@ #include #include -typedef int (*AndroidAudioCallback)(short *buffer, int num_samples); - -class AudioContext { -public: - AudioContext(AndroidAudioCallback cb, int _FramesPerBuffer, int _SampleRate); - virtual bool Init() { return false; } - virtual ~AudioContext() {} - -protected: - AndroidAudioCallback audioCallback; - - int framesPerBuffer; - int sampleRate; -}; +#include "AndroidAudio.h" class OpenSLContext : public AudioContext { public: diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 086371085d..0ac1036011 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -4,7 +4,6 @@ // It calls a set of methods defined in NativeApp.h. These should be implemented // by your game or app. -#include #include #include @@ -63,7 +62,7 @@ struct JNIEnv {}; #include "math/math_util.h" #include "net/resolve.h" #include "util/text/parsers.h" -#include "android/jni/native_audio.h" +#include "android/jni/AndroidAudio.h" #include "gfx/gl_common.h" #include "gfx_es2/gpu_features.h" @@ -594,7 +593,6 @@ retry: ELOG("NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend); g_Config.iGPUBackend = (int)GPUBackend::OPENGL; goto retry; - // Crash(); } if (useCPUThread) { @@ -605,7 +603,7 @@ retry: extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) { sampleRate = optimalSampleRate; - if (NativeQueryConfig("force44khz") != "0" || optimalSampleRate == 0) { + if (optimalSampleRate == 0) { sampleRate = 44100; } if (optimalFramesPerBuffer > 0) { @@ -688,7 +686,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { } // JavaEGL -extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { +extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { // We should be running on the render thread here. std::string errorMessage; if (renderer_inited) { @@ -712,21 +710,34 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, ILOG("Shut down both threads. Now let's bring it up again!"); - graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) { + SystemToast("Graphics initialization failed. Quitting."); + return false; + } + if (useCPUThread) { EmuThreadStart(); } else { - NativeInitGraphics(graphicsContext); + if (!NativeInitGraphics(graphicsContext)) { + // Gonna be in a weird state here, not good. + SystemToast("Failed to initialize graphics."); + return false; + } } + graphicsContext->ThreadStart(); ILOG("Restored."); } else { ILOG("NativeApp.displayInit() first time"); - graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0); + if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) { + SystemToast("Graphics initialization failed. Quitting."); + return false; + } graphicsContext->ThreadStart(); renderer_inited = true; } NativeMessageReceived("recreateviews", ""); + return true; } static void recalculateDpi() { @@ -1156,6 +1167,11 @@ static void ProcessFrameCommands(JNIEnv *env) { } extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { + if (!graphicsContext) { + ELOG("runEGLRenderLoop: Tried to enter without a created graphics context."); + return false; + } + // Needed for Vulkan, even if we're not using the old EGL path. exitRenderLoop = false; @@ -1175,10 +1191,11 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J auto tryInit = [&]() { if (graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) { return true; + } else { + ELOG("Failed to initialize graphics context."); + SystemToast("Failed to initialize graphics context."); + return false; } - - ELOG("Failed to initialize graphics context."); - return false; }; bool initSuccess = tryInit(); @@ -1202,7 +1219,10 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J if (!exitRenderLoop) { if (!useCPUThread) { - NativeInitGraphics(graphicsContext); + if (!NativeInitGraphics(graphicsContext)) { + ELOG("Failed to initialize graphics."); + // Gonna be in a weird state here.. + } } graphicsContext->ThreadStart(); renderer_inited = true; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index ac272d2d75..477d7b42cf 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -18,7 +18,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.database.Cursor; import android.graphics.PixelFormat; -import android.graphics.Point; import android.media.AudioManager; import android.net.Uri; import android.os.Build; @@ -51,11 +50,12 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +@SuppressWarnings("ConstantConditions") public abstract class NativeActivity extends Activity { // Remember to loadLibrary your JNI .so in a static {} block // Adjust these as necessary - private static String TAG = "PPSSPPNativeActivity"; + private static final String TAG = "PPSSPPNativeActivity"; // Allows us to skip a lot of initialization on secondary calls to onCreate. private static boolean initialized = false; @@ -98,7 +98,7 @@ public abstract class NativeActivity extends Activity { // This is to avoid losing the game/menu state etc when we are just // switched-away from or rotated etc. private boolean shuttingDown; - private static int RESULT_LOAD_IMAGE = 1; + private static final int RESULT_LOAD_IMAGE = 1; // Allow for multiple connected gamepads but just consider them the same for now. // Actually this is not entirely true, see the code. @@ -132,10 +132,6 @@ public abstract class NativeActivity extends Activity { public native void registerCallbacks(); public native void unregisterCallbacks(); - public boolean useLowProfileButtons() { - return true; - } - NativeRenderer getRenderer() { return nativeRenderer; } @@ -209,7 +205,7 @@ public abstract class NativeActivity extends Activity { } @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, String [] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_CODE_STORAGE_PERMISSION: if (permissionsGranted(permissions, grantResults)) { @@ -248,7 +244,7 @@ public abstract class NativeActivity extends Activity { } powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (powerManager.isSustainedPerformanceModeSupported()) { + if (powerManager != null && powerManager.isSustainedPerformanceModeSupported()) { sustainedPerfSupported = true; NativeApp.sendMessage("sustained_perf_supported", "1"); } @@ -259,10 +255,10 @@ public abstract class NativeActivity extends Activity { Log.d(TAG, "Landscape: " + landscape); // Get system information - ApplicationInfo appInfo = null; - PackageManager packMgmr = getPackageManager(); String packageName = getPackageName(); + + ApplicationInfo appInfo; try { appInfo = packMgmr.getApplicationInfo(packageName, 0); } catch (NameNotFoundException e) { @@ -345,22 +341,20 @@ public abstract class NativeActivity extends Activity { private void updateSustainedPerformanceMode() { if (sustainedPerfSupported) { // Query the native application on the desired rotation. - int enable = 0; String str = NativeApp.queryConfig("sustainedPerformanceMode"); try { - enable = Integer.parseInt(str); + int enable = Integer.parseInt(str); + getWindow().setSustainedPerformanceMode(enable != 0); } catch (NumberFormatException e) { Log.e(TAG, "Invalid perf mode: " + str); - return; } - getWindow().setSustainedPerformanceMode(enable != 0); } } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private void updateScreenRotation(String cause) { // Query the native application on the desired rotation. - int rot = 0; + int rot; String rotString = NativeApp.queryConfig("screenRotation"); try { rot = Integer.parseInt(rotString); @@ -407,10 +401,7 @@ public abstract class NativeActivity extends Activity { } // Compute our _desired_ systemUiVisibility - int flags = 0; - if (useLowProfileButtons()) { - flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; - } + int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE; if (useImmersive()) { flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; flags |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; @@ -436,7 +427,7 @@ public abstract class NativeActivity extends Activity { } } - private Runnable mEmulationRunner = new Runnable() { + private final Runnable mEmulationRunner = new Runnable() { @Override public void run() { Log.i(TAG, "Starting the render loop: " + mSurface); @@ -616,6 +607,7 @@ public abstract class NativeActivity extends Activity { @Override protected void onDestroy() { super.onDestroy(); + Log.i(TAG, "onDestroy"); if (javaGL) { if (nativeRenderer.isRenderingFrame()) { Log.i(TAG, "Waiting for renderer to finish."); @@ -628,21 +620,23 @@ public abstract class NativeActivity extends Activity { tries--; } while (nativeRenderer.isRenderingFrame() && tries > 0); } - Log.i(TAG, "onDestroy"); mGLSurfaceView.onDestroy(); - // Probably vain attempt to help the garbage collector... mGLSurfaceView = null; - audioFocusChangeListener = null; - audioManager = null; } else { mSurfaceView.onDestroy(); mSurfaceView = null; } + + // Probably vain attempt to help the garbage collector... + audioFocusChangeListener = null; + audioManager = null; + sizeManager.setSurfaceView(null); if (mPowerSaveModeReceiver != null) { mPowerSaveModeReceiver.destroy(this); mPowerSaveModeReceiver = null; } + // TODO: Can we ensure that the GL thread has stopped rendering here? // I've seen crashes that seem to indicate that sometimes it hasn't... NativeApp.audioShutdown(); @@ -697,6 +691,7 @@ public abstract class NativeActivity extends Activity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { updateSystemUiVisibility(); } + // OK, config should be initialized, we can query for screen rotation. if (javaGL || Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { updateScreenRotation("onResume"); @@ -991,30 +986,29 @@ public abstract class NativeActivity extends Activity { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); - String[] filePathColumn = {MediaStore.Images.Media.DATA}; - Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); - cursor.moveToFirst(); - int columnIndex = cursor.getColumnIndex(filePathColumn[0]); - String picturePath = cursor.getString(columnIndex); - cursor.close(); - NativeApp.sendMessage("bgImage_updated", picturePath); + if (selectedImage != null) { + String[] filePathColumn = {MediaStore.Images.Media.DATA}; + Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); + cursor.moveToFirst(); + int columnIndex = cursor.getColumnIndex(filePathColumn[0]); + String picturePath = cursor.getString(columnIndex); + cursor.close(); + NativeApp.sendMessage("bgImage_updated", picturePath); + } } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) - @SuppressWarnings("deprecation") private AlertDialog.Builder createDialogBuilderWithTheme() { return new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - @SuppressWarnings("deprecation") private AlertDialog.Builder createDialogBuilderWithDeviceTheme() { return new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_DARK); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @SuppressWarnings("deprecation") private AlertDialog.Builder createDialogBuilderWithDeviceThemeAndUiVisibility() { AlertDialog.Builder bld = new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_DARK); bld.setOnDismissListener(new DialogInterface.OnDismissListener() { @@ -1065,7 +1059,7 @@ public abstract class NativeActivity extends Activity { else bld = createDialogBuilderNew(); - AlertDialog dlg = bld + AlertDialog.Builder builder = bld .setView(fl) .setTitle(title) .setPositiveButton(defaultAction, new DialogInterface.OnClickListener() { @@ -1081,15 +1075,17 @@ public abstract class NativeActivity extends Activity { NativeApp.sendInputBox(seqID, false, ""); d.cancel(); } - }) - .setOnDismissListener(new DialogInterface.OnDismissListener() { + }); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface d) { NativeApp.sendInputBox(seqID, false, ""); updateSystemUiVisibility(); } - }) - .create(); + }); + } + AlertDialog dlg = builder.create(); dlg.setCancelable(true); dlg.show(); @@ -1188,11 +1184,10 @@ public abstract class NativeActivity extends Activity { inputMethodManager.toggleSoftInputFromWindow(surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("inputbox")) { - String seqID = ""; String title = "Input"; String defString = ""; String[] param = params.split(":@:", 3); - seqID = param[0]; + String seqID = param[0]; if (param.length > 1 && param[1].length() > 0) title = param[1]; if (param.length > 2) @@ -1202,7 +1197,7 @@ public abstract class NativeActivity extends Activity { return true; } else if (command.equals("vibrate")) { int milliseconds = -1; - if (params != "") { + if (!params.equals("")) { try { milliseconds = Integer.parseInt(params); } catch (NumberFormatException e) { diff --git a/android/src/org/ppsspp/ppsspp/NativeRenderer.java b/android/src/org/ppsspp/ppsspp/NativeRenderer.java index 31df096943..58d4de48d8 100644 --- a/android/src/org/ppsspp/ppsspp/NativeRenderer.java +++ b/android/src/org/ppsspp/ppsspp/NativeRenderer.java @@ -2,6 +2,7 @@ package org.ppsspp.ppsspp; import android.opengl.GLSurfaceView; import android.util.Log; +import android.widget.Toast; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; @@ -12,7 +13,8 @@ import javax.microedition.khronos.opengles.GL10; public class NativeRenderer implements GLSurfaceView.Renderer { private static String TAG = "NativeRenderer"; private NativeActivity mActivity; - private boolean inFrame; + private boolean inFrame = false; + private boolean failed = false; NativeRenderer(NativeActivity act) { mActivity = act; @@ -22,6 +24,9 @@ public class NativeRenderer implements GLSurfaceView.Renderer { return inFrame; } + // TODO: Make use of this somehow. + public boolean hasFailedInit() { return failed; } + public void onDrawFrame(GL10 unused /*use GLES20*/) { inFrame = true; displayRender(); @@ -29,6 +34,7 @@ public class NativeRenderer implements GLSurfaceView.Renderer { } public void onSurfaceCreated(GL10 gl, EGLConfig config) { + failed = false; Log.i(TAG, "NativeRenderer: onSurfaceCreated"); EGL10 egl = (EGL10)EGLContext.getEGL(); @@ -47,17 +53,18 @@ public class NativeRenderer implements GLSurfaceView.Renderer { } else { Log.e(TAG, "egl == null"); } - // Log.i(TAG, "onSurfaceCreated - EGL context is new or was lost"); - // Actually, it seems that it is here we should recreate lost GL objects. - displayInit(); + + if (!displayInit()) { + Log.e(TAG, "Display init failed"); + Toast.makeText(mActivity, "Failed to initialize rendering. Close the app and try again.", Toast.LENGTH_LONG); + failed = true; + } } - public void onSurfaceChanged(GL10 unused, int width, int height) { - } + public void onSurfaceChanged(GL10 unused, int width, int height) {} // Note: This also means "device lost" and you should reload // all buffered objects. - public native void displayInit(); - + public native boolean displayInit(); public native void displayRender(); } diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 395687f433..2ead85c2e4 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -136,6 +136,7 @@ enum class Primitive { TRIANGLE_STRIP_ADJ, UNDEFINED, + PRIMITIVE_TYPE_COUNT, }; enum VertexShaderPreset : int { diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index 6f697b251f..61ce579814 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "base/logging.h" #include "math/dataconv.h" @@ -961,6 +960,15 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { ELOG("Pipeline requires at least one shader"); return nullptr; } + if ((int)desc.prim >= (int)Primitive::PRIMITIVE_TYPE_COUNT) { + ELOG("Invalid primitive type"); + return nullptr; + } + if (!desc.depthStencil || !desc.blend || !desc.raster || !desc.inputLayout) { + ELOG("Incomplete prim desciption"); + return nullptr; + } + OpenGLPipeline *pipeline = new OpenGLPipeline(&renderManager_); for (auto iter : desc.shaders) { if (iter) { @@ -1045,9 +1053,21 @@ ShaderModule *OpenGLContext::CreateShaderModule(ShaderStage stage, ShaderLanguag bool OpenGLPipeline::LinkShaders() { std::vector linkShaders; - for (auto iter : shaders) { - linkShaders.push_back(iter->GetShader()); + for (auto shaderModule : shaders) { + if (shaderModule) { + GLRShader *shader = shaderModule->GetShader(); + if (shader) { + linkShaders.push_back(shader); + } else { + ELOG("LinkShaders: Bad shader module"); + return false; + } + } else { + ELOG("LinkShaders: Bad shader in module"); + return false; + } } + std::vector semantics; // Bind all the common vertex data points. Mismatching ones will be ignored. semantics.push_back({ SEM_POSITION, "Position" });