diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index f4f8d876a6..a3083633a5 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -9,6 +9,6 @@ You can remove this stuff - just don't forget to answer the questions. They're ### What *should* happen? -### What hardware, operating system, and PPSSPP version? On desktop, GPU matters for graphical issues. - +### What hardware, operating system, and PPSSPP version? On desktop and mobile phone, GPU matters for graphical issues. +### Please test newest version in https://buildbot.orphis.net/ppsspp/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49d34581f3..b8fddd1a76 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,22 +121,22 @@ jobs: extra: android cc: clang cxx: clang++ - args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a + args: cd android && ./ab.sh -j2 APP_ABI=arm64-v8a UNITTEST=1 HEADLESS=1 - os: ubuntu-latest extra: android cc: clang cxx: clang++ - args: cd android && ./ab.sh -j2 APP_ABI=armeabi-v7a + args: cd android && ./ab.sh -j2 APP_ABI=armeabi-v7a UNITTEST=1 HEADLESS=1 - os: ubuntu-latest extra: android cc: clang cxx: clang++ - args: cd android && ./ab.sh -j2 APP_ABI=x86 + args: cd android && ./ab.sh -j2 APP_ABI=x86 UNITTEST=1 HEADLESS=1 - os: ubuntu-latest extra: android cc: clang cxx: clang++ - args: cd android && ./ab.sh -j2 APP_ABI=x86_64 + args: cd android && ./ab.sh -j2 APP_ABI=x86_64 UNITTEST=1 HEADLESS=1 - os: ubuntu-latest extra: android cc: clang @@ -215,6 +215,7 @@ jobs: cp -r build*/PPSSPPSDL.app ppsspp/ elif [ -e build*/PPSSPPSDL ]; then cp build*/PPSSPPSDL ppsspp/ + cp -r assets ppsspp/assets fi if [ -e build*/$BUILD_CONFIGURATION/PPSSPPHeadless ]; then cp build*/$BUILD_CONFIGURATION/PPSSPPHeadless ppsspp/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e10a35025..4b0b8d7f2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ if(ANDROID OR WIN32 OR (UNIX AND NOT ARM_NO_VULKAN)) endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) -if(NOT (ARM64 AND MACOSX)) +if(NOT ((ARM64 AND MACOSX) OR IOS)) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/sdl) endif() @@ -198,7 +198,7 @@ if(USING_EGL) set(OPENGL_LIBRARIES ${OPENGL_LIBRARIES} ${EGL_LIBRARIES}) endif() -if(NOT LIBRETRO) +if(NOT LIBRETRO AND NOT IOS) find_package(SDL2) endif() include(FindThreads) @@ -281,11 +281,11 @@ if(NOT MSVC) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -D_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -Os -D_NDEBUG") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -D_NDEBUG") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -D_NDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O2 -g -D_NDEBUG") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -D_DEBUG") set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -Os -D_NDEBUG") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -D_NDEBUG") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -D_NDEBUG") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -O2 -g -D_NDEBUG") #TODO: Remove this and include the file properly everywhere it makes sense # First step is too use the macros everywhere @@ -767,15 +767,15 @@ include_directories(ext/cityhash) if(NOT MSVC) # These can be fast even for debug. - set_target_properties(udis86 PROPERTIES COMPILE_FLAGS "-O3") - set_target_properties(cityhash PROPERTIES COMPILE_FLAGS "-O3") + set_target_properties(udis86 PROPERTIES COMPILE_FLAGS "-O2") + set_target_properties(cityhash PROPERTIES COMPILE_FLAGS "-O2") if(NOT ZLIB_FOUND) - set_target_properties(zlib PROPERTIES COMPILE_FLAGS "-O3") + set_target_properties(zlib PROPERTIES COMPILE_FLAGS "-O2") endif() endif() -find_package(LibZip) +find_package(LIBZIP) if(LIBZIP_FOUND AND USE_SYSTEM_LIBZIP) add_definitions(-DSHARED_LIBZIP) else() @@ -1037,7 +1037,7 @@ if(WIN32) target_link_libraries(Common winmm d3d9 dsound) endif() -if(TARGET SDL2::SDL2) +if(TARGET SDL2::SDL2 AND NOT IOS) target_link_libraries(Common SDL2::SDL2) endif() @@ -1492,6 +1492,10 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket/GPURecordSubscriber.h Core/Debugger/WebSocket/HLESubscriber.cpp Core/Debugger/WebSocket/HLESubscriber.h + Core/Debugger/WebSocket/InputBroadcaster.cpp + Core/Debugger/WebSocket/InputBroadcaster.h + Core/Debugger/WebSocket/InputSubscriber.cpp + Core/Debugger/WebSocket/InputSubscriber.h Core/Debugger/WebSocket/LogBroadcaster.cpp Core/Debugger/WebSocket/LogBroadcaster.h Core/Debugger/WebSocket/MemorySubscriber.cpp @@ -2168,11 +2172,12 @@ if(IOS) set_source_files_properties(${RSRC_XIB_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) - if(CMAKE_GENERATOR STREQUAL "Xcode") - set(APP_DIR_NAME "$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)") - else() + #This breaks in modern XCode. Not sure when it worked... + #if(CMAKE_GENERATOR STREQUAL "Xcode") + # set(APP_DIR_NAME "$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)") + #else() set(APP_DIR_NAME "$") - endif() + #endif() add_custom_command(TARGET PPSSPP POST_BUILD COMMAND mkdir -p \"${APP_DIR_NAME}\" COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C \"${APP_DIR_NAME}\" @@ -2185,7 +2190,6 @@ if(IOS) RESOURCE "ios/Settings.bundle" RESOURCE "MoltenVK/iOS/Frameworks" XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} - XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "iPhone/iPad" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES XCODE_ATTRIBUTE_ENABLE_BITCODE NO XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-" diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index 1998c6fa6f..7bd70e2063 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -273,7 +273,7 @@ bool PathBrowser::CanNavigateUp() { } #endif */ - if (path_ == "/") { + if (path_ == "/" || path_ == "") { return false; } return true; diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index 9ae4185c33..9759915345 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -594,6 +594,8 @@ std::string ApplyGLSLPrelude(const std::string &source, uint32_t stage) { if (!gl_extensions.IsGLES && gl_extensions.IsCoreContext) { // We need to add a corresponding #version. Apple drivers fail without an exact match. version = StringFromFormat("#version %d\n", gl_extensions.GLSLVersion()); + } else if (gl_extensions.IsGLES && gl_extensions.GLES3) { + version = StringFromFormat("#version %d es\n", gl_extensions.GLSLVersion()); } if (stage == GL_FRAGMENT_SHADER) { temp = version + glsl_fragment_prelude + source; diff --git a/Common/GPU/Shader.cpp b/Common/GPU/Shader.cpp index 394a6eed32..14b703f544 100644 --- a/Common/GPU/Shader.cpp +++ b/Common/GPU/Shader.cpp @@ -17,6 +17,16 @@ const char *ShaderLanguageAsString(ShaderLanguage lang) { } } +const char *ShaderStageAsString(ShaderStage stage) { + switch (stage) { + case ShaderStage::Fragment: return "Fragment"; + case ShaderStage::Vertex: return "Vertex"; + case ShaderStage::Geometry: return "Geometry"; + case ShaderStage::Compute: return "Compute"; + default: return "(unknown)"; + } +} + ShaderLanguageDesc::ShaderLanguageDesc(ShaderLanguage lang) { Init(lang); } diff --git a/Common/GPU/Shader.h b/Common/GPU/Shader.h index 36bfbe36ce..4fc5ba4f0a 100644 --- a/Common/GPU/Shader.h +++ b/Common/GPU/Shader.h @@ -28,6 +28,9 @@ enum class ShaderStage { Compute, }; +const char *ShaderStageAsString(ShaderStage lang); + + struct ShaderLanguageDesc { ShaderLanguageDesc() {} explicit ShaderLanguageDesc(ShaderLanguage lang); diff --git a/Common/GPU/ShaderTranslation.cpp b/Common/GPU/ShaderTranslation.cpp index 842863a5e5..5d92d2fd7d 100644 --- a/Common/GPU/ShaderTranslation.cpp +++ b/Common/GPU/ShaderTranslation.cpp @@ -53,7 +53,7 @@ extern void init_resources(TBuiltInResource &Resources); -static EShLanguage GetLanguage(const ShaderStage stage) { +static EShLanguage GetShLanguageFromStage(const ShaderStage stage) { switch (stage) { case ShaderStage::Vertex: return EShLangVertex; case ShaderStage::Geometry: return EShLangGeometry; @@ -234,15 +234,16 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan *errorMessage = ""; glslang::TProgram program; - const char *shaderStrings[1]; + const char *shaderStrings[1]{}; - TBuiltInResource Resources; + TBuiltInResource Resources{}; init_resources(Resources); // Don't enable SPIR-V and Vulkan rules when parsing GLSL. Our postshaders are written in oldschool GLES 2.0. EShMessages messages = EShMessages::EShMsgDefault; - EShLanguage shaderStage = GetLanguage(stage); + EShLanguage shaderStage = GetShLanguageFromStage(stage); + glslang::TShader shader(shaderStage); shaderStrings[0] = src.c_str(); @@ -250,7 +251,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan // TODO: Should set settings here based on srcLang. if (!shader.parse(&Resources, 100, EProfile::ECompatibilityProfile, false, false, messages)) { - *errorMessage = std::string("GLSL parser failure: ") + shader.getInfoLog() + shader.getInfoDebugLog(); + *errorMessage = StringFromFormat("%s parser failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog()); return false; // something didn't work } @@ -258,7 +259,7 @@ bool TranslateShader(std::string *dest, ShaderLanguage destLang, const ShaderLan program.addShader(&shader); if (!program.link(messages)) { - *errorMessage = std::string("Linker failure: ") + shader.getInfoLog() + shader.getInfoDebugLog(); + *errorMessage = StringFromFormat("%s linker failure: %s\n%s", ShaderStageAsString(stage), shader.getInfoLog(), shader.getInfoDebugLog()); return false; } diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 1f4fe4433f..3b68fcac48 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -346,8 +346,8 @@ void VulkanRenderManager::DestroyBackbuffers() { StopThread(); vulkan_->WaitUntilQueueIdle(); - for (uint32_t i = 0; i < swapchainImageCount_; i++) { - vulkan_->Delete().QueueDeleteImageView(swapchainImages_[i].view); + for (auto &image : swapchainImages_) { + vulkan_->Delete().QueueDeleteImageView(image.view); } swapchainImages_.clear(); diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index 7193582fa5..1c13d95f90 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -4,13 +4,18 @@ #include "Common/StringUtils.h" #ifndef _WIN32 +#include #include #include #include #include +#include #include #define closesocket close #else +#ifndef NOMINMAX +#define NOMINMAX +#endif #include #include #include diff --git a/Common/Net/HTTPClient.h b/Common/Net/HTTPClient.h index 731ff75dd5..d76e57d1b9 100644 --- a/Common/Net/HTTPClient.h +++ b/Common/Net/HTTPClient.h @@ -7,19 +7,6 @@ #include "Common/Net/Resolve.h" -#ifdef _WIN32 -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include -#else -#include -#include -#include -#include -#endif - #include "Common/Buffer.h" namespace net { diff --git a/Common/Net/URL.cpp b/Common/Net/URL.cpp index 681fc45ff2..1183022ed7 100644 --- a/Common/Net/URL.cpp +++ b/Common/Net/URL.cpp @@ -126,7 +126,7 @@ std::string UriDecode(const std::string & sSrc) const unsigned char * const SRC_END = pSrc + SRC_LEN; const unsigned char * const SRC_LAST_DEC = SRC_END - 2; // last decodable '%' - char * const pStart = new char[SRC_LEN]; + char * const pStart = new char[SRC_LEN]; // Output will be shorter. char * pEnd = pStart; while (pSrc < SRC_LAST_DEC) diff --git a/Common/Render/DrawBuffer.cpp b/Common/Render/DrawBuffer.cpp index f23c753726..94be1bc385 100644 --- a/Common/Render/DrawBuffer.cpp +++ b/Common/Render/DrawBuffer.cpp @@ -236,6 +236,18 @@ void DrawBuffer::DrawImageStretch(ImageID atlas_image, float x1, float y1, float V(x1, y2, color, image->u1, image->v2); } +void DrawBuffer::DrawImageStretchVGradient(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color1, Color color2) { + const AtlasImage *image = atlas->getImage(atlas_image); + if (!image) + return; + V(x1, y1, color1, image->u1, image->v1); + V(x2, y1, color1, image->u2, image->v1); + V(x2, y2, color2, image->u2, image->v2); + V(x1, y1, color1, image->u1, image->v1); + V(x2, y2, color2, image->u2, image->v2); + V(x1, y2, color2, image->u1, image->v2); +} + inline void rot(float *v, float angle, float xc, float yc) { const float x = v[0] - xc; const float y = v[1] - yc; diff --git a/Common/Render/DrawBuffer.h b/Common/Render/DrawBuffer.h index 40d482ef83..6c3f056f5d 100644 --- a/Common/Render/DrawBuffer.h +++ b/Common/Render/DrawBuffer.h @@ -108,6 +108,7 @@ public: bool MeasureImage(ImageID atlas_image, float *w, float *h); void DrawImage(ImageID atlas_image, float x, float y, float scale, Color color = COLOR(0xFFFFFF), int align = ALIGN_TOPLEFT); void DrawImageStretch(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color = COLOR(0xFFFFFF)); + void DrawImageStretchVGradient(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color1, Color color2); void DrawImageStretch(ImageID atlas_image, const Bounds &bounds, Color color = COLOR(0xFFFFFF)) { DrawImageStretch(atlas_image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color); } diff --git a/Common/System/System.h b/Common/System/System.h index 57cee7bd72..a713616a10 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -55,6 +55,12 @@ enum SystemProperty { SYSPROP_CLIPBOARD_TEXT, SYSPROP_GPUDRIVER_VERSION, + // Separate SD cards or similar. + // Need hacky solutions to get at this. + SYSPROP_HAS_ADDITIONAL_STORAGE, + SYSPROP_ADDITIONAL_STORAGE_DIRS, + SYSPROP_TEMP_DIRS, + SYSPROP_HAS_FILE_BROWSER, SYSPROP_HAS_FOLDER_BROWSER, SYSPROP_HAS_IMAGE_BROWSER, @@ -95,6 +101,7 @@ enum SystemProperty { }; std::string System_GetProperty(SystemProperty prop); +std::vector System_GetPropertyStringVec(SystemProperty prop); int System_GetPropertyInt(SystemProperty prop); float System_GetPropertyFloat(SystemProperty prop); bool System_GetPropertyBool(SystemProperty prop); diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index a2dcbf475b..a492c31185 100644 --- a/Common/UI/Context.cpp +++ b/Common/UI/Context.cpp @@ -55,6 +55,7 @@ void UIContext::BeginNoTex() { } void UIContext::BeginPipeline(Draw::Pipeline *pipeline, Draw::SamplerState *samplerState) { + _assert_(pipeline != nullptr); draw_->BindSamplerStates(0, 1, &samplerState); RebindTexture(); UIBegin(pipeline); @@ -236,6 +237,10 @@ void UIContext::FillRect(const UI::Drawable &drawable, const Bounds &bounds) { } } +void UIContext::DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds) { + uidrawbuffer_->DrawImageStretchVGradient(image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), color1, color2); +} + void UIContext::PushTransform(const UITransform &transform) { Flush(); diff --git a/Common/UI/Context.h b/Common/UI/Context.h index 18f18bd515..944d31e4d8 100644 --- a/Common/UI/Context.h +++ b/Common/UI/Context.h @@ -85,6 +85,7 @@ public: void DrawTextShadow(const char *str, float x, float y, uint32_t color, int align = 0); void DrawTextRect(const char *str, const Bounds &bounds, uint32_t color, int align = 0); void FillRect(const UI::Drawable &drawable, const Bounds &bounds); + void DrawImageVGradient(ImageID image, uint32_t color1, uint32_t color2, const Bounds &bounds); // in dps, like dp_xres and dp_yres void SetBounds(const Bounds &b) { bounds_ = b; } diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index ebd0e993e7..2babf3e4ba 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -803,6 +803,10 @@ void TextView::Draw(UIContext &dc) { dc.DrawTextRect(text_.c_str(), bounds_.Offset(1.0f, 1.0f), shadowColor, textAlign_); } dc.DrawTextRect(text_.c_str(), bounds_, textColor, textAlign_); + if (small_) { + // If we changed font style, reset it. + dc.SetFontStyle(dc.theme->uiFont); + } if (clip) { dc.PopScissor(); } diff --git a/Common/UI/View.h b/Common/UI/View.h index 97c5c10513..4f779fa1f4 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -527,6 +527,9 @@ public: paddingW_ = w; paddingH_ = h; } + void SetImageID(ImageID imageID) { + imageID_ = imageID; + } // Needed an extra small button... void SetScale(float f) { diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index e91c514383..060eeb4ddb 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -437,6 +437,8 @@ + + @@ -981,6 +983,8 @@ + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 76fac1fe88..93b6b137ef 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -965,6 +965,12 @@ Core + + Debugger\WebSocket + + + Debugger\WebSocket + @@ -1649,6 +1655,12 @@ Core + + Debugger\WebSocket + + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 9c4cdeef3c..c609e08698 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -45,6 +45,7 @@ // For other events, look inside Core/Debugger/WebSocket/ for details on each event. #include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/InputBroadcaster.h" #include "Core/Debugger/WebSocket/LogBroadcaster.h" #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" @@ -55,6 +56,7 @@ #include "Core/Debugger/WebSocket/GPUBufferSubscriber.h" #include "Core/Debugger/WebSocket/GPURecordSubscriber.h" #include "Core/Debugger/WebSocket/HLESubscriber.h" +#include "Core/Debugger/WebSocket/InputSubscriber.h" #include "Core/Debugger/WebSocket/MemorySubscriber.h" #include "Core/Debugger/WebSocket/SteppingSubscriber.h" @@ -67,6 +69,7 @@ static const std::vector subscribers({ &WebSocketGPUBufferInit, &WebSocketGPURecordInit, &WebSocketHLEInit, + &WebSocketInputInit, &WebSocketMemoryInit, &WebSocketSteppingInit, }); @@ -125,8 +128,9 @@ void HandleDebuggerRequest(const http::Request &request) { UpdateConnected(1); SetupDebuggerLock(); - LogBroadcaster logger; GameBroadcaster game; + LogBroadcaster logger; + InputBroadcaster input; SteppingBroadcaster stepping; std::unordered_map eventHandlers; @@ -175,6 +179,7 @@ void HandleDebuggerRequest(const http::Request &request) { logger.Broadcast(ws); game.Broadcast(ws); stepping.Broadcast(ws); + input.Broadcast(ws); for (size_t i = 0; i < subscribers.size(); ++i) { if (subscriberData[i]) { diff --git a/Core/Debugger/WebSocket/InputBroadcaster.cpp b/Core/Debugger/WebSocket/InputBroadcaster.cpp new file mode 100644 index 0000000000..388000dbbc --- /dev/null +++ b/Core/Debugger/WebSocket/InputBroadcaster.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include "Core/Debugger/WebSocket/InputBroadcaster.h" +#include "Core/Debugger/WebSocket/InputSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceCtrl.h" +#include "Core/HLE/sceDisplay.h" + +// Button press state change (input.buttons) +// +// Sent unexpectedly with these properties: +// - buttons: an object with button names as keys and bool press state as values. +// - changed: same as buttons, but only including changed states. +// +// See input.buttons.send in InputSubscriber for button names. + +// Analog position change (input.analog) +// +// Sent unexpectedly with these properties: +// - stick: "left" or "right". +// - x: number between -1.0 and 1.0, representing horizontal position in a square. +// - y: number between -1.0 and 1.0, representing vertical position in a square. + +std::string InputBroadcaster::Analog::Event(const char *stick) { + JsonWriter j; + j.begin(); + j.writeString("event", "input.analog"); + j.writeString("stick", stick); + j.writeFloat("x", x); + j.writeFloat("y", y); + j.end(); + return j.str(); +} + +static std::string ButtonsEvent(uint32_t lastButtons, uint32_t newButtons) { + uint32_t pressed = newButtons & ~lastButtons; + uint32_t released = ~newButtons & lastButtons; + + JsonWriter j; + j.begin(); + j.writeString("event", "input.buttons"); + j.pushDict("buttons"); + for (auto it : WebSocketInputButtonLookup()) { + j.writeBool(it.first, (newButtons & it.second) != 0); + } + j.pop(); + j.pushDict("changed"); + for (auto it : WebSocketInputButtonLookup()) { + if (pressed & it.second) { + j.writeBool(it.first, true); + } else if (released & it.second) { + j.writeBool(it.first, false); + } + } + j.pop(); + j.end(); + return j.str(); +} + +void InputBroadcaster::Broadcast(net::WebSocketServer *ws) { + int counter = __DisplayGetNumVblanks(); + if (lastCounter_ == counter) + return; + lastCounter_ = counter; + + uint32_t newButtons = __CtrlPeekButtons(); + if (newButtons != lastButtons_) { + ws->Send(ButtonsEvent(lastButtons_, newButtons)); + lastButtons_ = newButtons; + } + + Analog newAnalog; + __CtrlPeekAnalog(CTRL_STICK_LEFT, &newAnalog.x, &newAnalog.y); + if (!lastAnalog_[0].Equals(newAnalog)) { + ws->Send(newAnalog.Event("left")); + lastAnalog_[0].x = newAnalog.x; + lastAnalog_[0].y = newAnalog.y; + } + + __CtrlPeekAnalog(CTRL_STICK_RIGHT, &newAnalog.x, &newAnalog.y); + if (!lastAnalog_[1].Equals(newAnalog)) { + ws->Send(newAnalog.Event("right")); + lastAnalog_[1].x = newAnalog.x; + lastAnalog_[1].y = newAnalog.y; + } +} diff --git a/Core/Debugger/WebSocket/InputBroadcaster.h b/Core/Debugger/WebSocket/InputBroadcaster.h new file mode 100644 index 0000000000..dd8031d902 --- /dev/null +++ b/Core/Debugger/WebSocket/InputBroadcaster.h @@ -0,0 +1,48 @@ +// Copyright (c) 2018- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include + +namespace net { +class WebSocketServer; +} + +struct InputBroadcaster { +public: + InputBroadcaster() { + } + + void Broadcast(net::WebSocketServer *ws); + +private: + struct Analog { + float x = 0.0f; + float y = 0.0f; + + bool Equals(const Analog &other) const { + return x == other.x && y == other.y; + } + std::string Event(const char *stick); + }; + + int lastCounter_ = -1; + uint32_t lastButtons_ = 0; + Analog lastAnalog_[2]; +}; diff --git a/Core/Debugger/WebSocket/InputSubscriber.cpp b/Core/Debugger/WebSocket/InputSubscriber.cpp new file mode 100644 index 0000000000..b35c8413e7 --- /dev/null +++ b/Core/Debugger/WebSocket/InputSubscriber.cpp @@ -0,0 +1,261 @@ +// Copyright (c) 2021- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "Common/StringUtils.h" +#include "Core/Debugger/WebSocket/InputSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceCtrl.h" +#include "Core/HLE/sceDisplay.h" + +// This is also used in InputBroadcaster. +const std::unordered_map buttonLookup = { + { "cross", CTRL_CROSS }, + { "circle", CTRL_CIRCLE }, + { "triangle", CTRL_TRIANGLE }, + { "square", CTRL_SQUARE }, + { "up", CTRL_UP }, + { "down", CTRL_DOWN }, + { "left", CTRL_LEFT }, + { "right", CTRL_RIGHT }, + { "start", CTRL_START }, + { "select", CTRL_SELECT }, + { "home", CTRL_HOME }, + { "screen", CTRL_SCREEN }, + { "note", CTRL_NOTE }, + { "ltrigger", CTRL_LTRIGGER }, + { "rtrigger", CTRL_RTRIGGER }, + { "hold", CTRL_HOLD }, + { "wlan", CTRL_WLAN }, + { "remote_hold", CTRL_REMOTE_HOLD }, + { "vol_up", CTRL_VOL_UP }, + { "vol_down", CTRL_VOL_DOWN }, + { "disc", CTRL_DISC }, + { "memstick", CTRL_MEMSTICK }, + { "forward", CTRL_FORWARD }, + { "back", CTRL_BACK }, + { "playpause", CTRL_PLAYPAUSE }, +}; + +struct WebSocketInputState : public DebuggerSubscriber { + void ButtonsSend(DebuggerRequest &req); + void ButtonsPress(DebuggerRequest &req); + void AnalogSend(DebuggerRequest &req); + + void Broadcast(net::WebSocketServer *ws) override; + +protected: + struct PressInfo { + std::string ticket; + uint32_t button; + uint32_t duration; + + std::string Event(); + }; + + std::vector pressTickets_; + int lastCounter_ = -1; +}; + +std::string WebSocketInputState::PressInfo::Event() { + JsonWriter j; + j.begin(); + j.writeString("event", "input.buttons.press"); + if (!ticket.empty()) { + j.writeRaw("ticket", ticket); + } + j.end(); + return j.str(); +} + +const std::unordered_map &WebSocketInputButtonLookup() { + return buttonLookup; +} + +DebuggerSubscriber *WebSocketInputInit(DebuggerEventHandlerMap &map) { + auto p = new WebSocketInputState(); + map["input.buttons.send"] = std::bind(&WebSocketInputState::ButtonsSend, p, std::placeholders::_1); + map["input.buttons.press"] = std::bind(&WebSocketInputState::ButtonsPress, p, std::placeholders::_1); + map["input.analog.send"] = std::bind(&WebSocketInputState::AnalogSend, p, std::placeholders::_1); + + return p; +} + +// Alter PSP button press flags (input.buttons.send) +// +// Parameters: +// - buttons: object containing button names as string keys, boolean press state as value. +// +// Button names (some are not respected by PPSSPP): +// - cross: button on bottom side of right pad. +// - circle: button on right side of right pad. +// - triangle: button on top side of right pad. +// - square: button on left side of right pad. +// - up: d-pad up button. +// - down: d-pad down button. +// - left: d-pad left button. +// - right: d-pad right button. +// - start: rightmost button at bottom of device. +// - select: second to the right at bottom of device. +// - home: leftmost button at bottom of device. +// - screen: brightness control button at bottom of device. +// - note: mute control button at bottom of device. +// - ltrigger: left shoulder trigger button. +// - rtrigger: right shoulder trigger button. +// - hold: hold setting of power switch. +// - wlan: wireless networking switch. +// - remote_hold: hold switch on headset. +// - vol_up: volume up button next to home at bottom of device. +// - vol_down: volume down button next to home at bottom of device. +// - disc: UMD disc sensor. +// - memstick: memory stick sensor. +// - forward: forward button on headset. +// - back: back button on headset. +// - playpause: play/pause button on headset. +// +// Empty response. +void WebSocketInputState::ButtonsSend(DebuggerRequest &req) { + const JsonNode *jsonButtons = req.data.get("buttons"); + if (!jsonButtons) { + return req.Fail("Missing 'buttons' parameter"); + } + if (jsonButtons->value.getTag() != JSON_OBJECT) { + return req.Fail("Invalid 'buttons' parameter type"); + } + + uint32_t downFlags = 0; + uint32_t upFlags = 0; + + for (const JsonNode *button : jsonButtons->value) { + auto info = buttonLookup.find(button->key); + if (info == buttonLookup.end()) { + return req.Fail(StringFromFormat("Unsupported 'buttons' object key '%s'", button->key)); + } + if (button->value.getTag() == JSON_TRUE) { + downFlags |= info->second; + } else if (button->value.getTag() == JSON_FALSE) { + upFlags |= info->second; + } else if (button->value.getTag() != JSON_NULL) { + return req.Fail(StringFromFormat("Unsupported 'buttons' object type for key '%s'", button->key)); + } + } + + if (downFlags) { + __CtrlButtonDown(downFlags); + } + if (upFlags) { + __CtrlButtonUp(upFlags); + } + + req.Respond(); +} + +// Press and release a button (input.buttons.press) +// +// Parameters: +// - button: required string indicating button name (see input.buttons.send.) +// - duration: optional integer indicating frames to press for, defaults to 1. +// +// Empty response once released. +void WebSocketInputState::ButtonsPress(DebuggerRequest &req) { + std::string button; + if (!req.ParamString("button", &button)) + return; + + PressInfo press; + press.duration = 1; + if (!req.ParamU32("duration", &press.duration, false, DebuggerParamType::OPTIONAL)) + return; + if (press.duration < 0) + return req.Fail("Parameter 'duration' must not be negative"); + const JsonNode *value = req.data.get("ticket"); + press.ticket = value ? json_stringify(value) : ""; + + auto info = buttonLookup.find(button); + if (info == buttonLookup.end()) { + return req.Fail(StringFromFormat("Unsupported button value '%s'", button.c_str())); + } + press.button = info->second; + + __CtrlButtonDown(press.button); + pressTickets_.push_back(press); +} + +void WebSocketInputState::Broadcast(net::WebSocketServer *ws) { + int counter = __DisplayGetNumVblanks(); + if (pressTickets_.empty() || lastCounter_ == counter) + return; + lastCounter_ = counter; + + for (PressInfo &press : pressTickets_) { + press.duration--; + if (press.duration == -1) { + __CtrlButtonUp(press.button); + ws->Send(press.Event()); + } + } + auto negative = [](const PressInfo &press) -> bool { + return press.duration < 0; + }; + pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), negative), pressTickets_.end()); +} + +static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) { + const JsonNode *node = req.data.get(name); + if (!node) { + req.Fail(StringFromFormat("Missing '%s' parameter", name)); + return false; + } + if (node->value.getTag() != JSON_NUMBER) { + req.Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + + double val = node->value.toNumber(); + if (val < 1.0 || val > 1.0) { + req.Fail(StringFromFormat("Parameter '%s' must be between -1.0 and 1.0", name)); + return false; + } + + *value = (float)val; + return true; +} + +// Set coordinates of analog stick (input.analog.send) +// +// Parameters: +// - x: required number from -1.0 to 1.0. +// - y: required number from -1.0 to 1.0. +// - stick: optional string, either "left" (default) or "right". +// +// Empty response. +void WebSocketInputState::AnalogSend(DebuggerRequest &req) { + std::string stick = "left"; + if (!req.ParamString("stick", &stick, DebuggerParamType::OPTIONAL)) + return; + if (stick != "left" && stick != "right") + return req.Fail(StringFromFormat("Parameter 'stick' must be 'left' or 'right', not '%s'", stick.c_str())); + float x, y; + if (!AnalogValue(req, &x, "x") || !AnalogValue(req, &y, "y")) + return; + + __CtrlSetAnalogX(x, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT); + __CtrlSetAnalogY(y, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT); + + req.Respond(); +} diff --git a/Core/Debugger/WebSocket/InputSubscriber.h b/Core/Debugger/WebSocket/InputSubscriber.h new file mode 100644 index 0000000000..65acbe9efe --- /dev/null +++ b/Core/Debugger/WebSocket/InputSubscriber.h @@ -0,0 +1,25 @@ +// Copyright (c) 2021- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +DebuggerSubscriber *WebSocketInputInit(DebuggerEventHandlerMap &map); +const std::unordered_map &WebSocketInputButtonLookup(); diff --git a/Core/Dialog/PSPMsgDialog.cpp b/Core/Dialog/PSPMsgDialog.cpp index d68abaa9b5..8361adbc3e 100755 --- a/Core/Dialog/PSPMsgDialog.cpp +++ b/Core/Dialog/PSPMsgDialog.cpp @@ -149,11 +149,11 @@ int PSPMsgDialog::Init(unsigned int paramAddr) { void PSPMsgDialog::FormatErrorCode(uint32_t code) { - auto di = GetI18NCategory("Dialog"); + auto err = GetI18NCategory("Dialog"); switch (code) { case SCE_UTILITY_SAVEDATA_ERROR_LOAD_DATA_BROKEN: - snprintf(msgText, 512, "%s (%08x)", di->T("MsgErrorSavedataDataBroken", "Save data was corrupt."), code); + snprintf(msgText, 512, "%s (%08x)", err->T("MsgErrorSavedataDataBroken", "Save data was corrupt."), code); break; case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_MS: @@ -161,23 +161,23 @@ void PSPMsgDialog::FormatErrorCode(uint32_t code) { case SCE_UTILITY_SAVEDATA_ERROR_SAVE_NO_MS: case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_MS: case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_MS: - snprintf(msgText, 512, "%s (%08x)", di->T("MsgErrorSavedataNoMS", "Memory stick not inserted."), code); + snprintf(msgText, 512, "%s (%08x)", err->T("MsgErrorSavedataNoMS", "Memory stick not inserted."), code); break; case SCE_UTILITY_SAVEDATA_ERROR_LOAD_NO_DATA: case SCE_UTILITY_SAVEDATA_ERROR_RW_NO_DATA: case SCE_UTILITY_SAVEDATA_ERROR_DELETE_NO_DATA: case SCE_UTILITY_SAVEDATA_ERROR_SIZES_NO_DATA: - snprintf(msgText, 512, "%s (%08x)", di->T("MsgErrorSavedataNoData", "Warning: no save data was found."), code); + snprintf(msgText, 512, "%s (%08x)", err->T("MsgErrorSavedataNoData", "Warning: no save data was found."), code); break; case SCE_UTILITY_SAVEDATA_ERROR_RW_MEMSTICK_FULL: case SCE_UTILITY_SAVEDATA_ERROR_SAVE_MS_NOSPACE: - snprintf(msgText, 512, "%s (%08x)", di->T("MsgErrorSavedataMSFull", "Memory stick full. Check your storage space."), code); + snprintf(msgText, 512, "%s (%08x)", err->T("MsgErrorSavedataMSFull", "Memory stick full. Check your storage space."), code); break; default: - snprintf(msgText, 512, "%s %08x", di->T("MsgErrorCode", "Error code:"), code); + snprintf(msgText, 512, "%s %08x", err->T("MsgErrorCode", "Error code:"), code); } } diff --git a/Core/Dialog/PSPNetconfDialog.cpp b/Core/Dialog/PSPNetconfDialog.cpp index 4e3fe9ae1c..b7126d3348 100644 --- a/Core/Dialog/PSPNetconfDialog.cpp +++ b/Core/Dialog/PSPNetconfDialog.cpp @@ -347,7 +347,7 @@ int PSPNetconfDialog::Update(int animSpeed) { if (g_Config.iWlanAdhocChannel == PSP_SYSTEMPARAM_ADHOC_CHANNEL_AUTOMATIC) channel = "Automatic"; - DisplayMessage(di->T("ConnectingPleaseWait", "Connecting.\nPlease wait..."), di->T("Channel") + std::string(" ") + di->T(channel)); + DisplayMessage(di->T("ConnectingPleaseWait", "Connecting.\nPlease wait..."), di->T("Channel:") + std::string(" ") + di->T(channel)); // Only Join mode is showing Cancel button on KHBBS and the button will fade out before the dialog is fading out, probably because it's already connected thus can't be canceled anymore if (request.netAction == NETCONF_JOIN_ADHOC) diff --git a/Core/Dialog/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index b1d2b11541..354fd3b823 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -1485,19 +1485,20 @@ int SavedataParam::SetPspParam(SceUtilitySavedataParam *param) } const std::string thisSaveName = FixedToString(saveNameListData[i], ARRAY_SIZE(saveNameListData[i])); - DEBUG_LOG(SCEUTILITY, "Name : %s", thisSaveName.c_str()); std::string fileDataDir = savePath + GetGameName(param) + thisSaveName; PSPFileInfo info = GetSaveInfo(fileDataDir); if (info.exists) { SetFileInfo(realCount, info, thisSaveName); - DEBUG_LOG(SCEUTILITY,"%s Exist",fileDataDir.c_str()); + DEBUG_LOG(SCEUTILITY, "Save data exists: %s = %s", thisSaveName.c_str(), fileDataDir.c_str()); realCount++; } else { if (listEmptyFile) { ClearFileInfo(saveDataList[realCount], thisSaveName); - DEBUG_LOG(SCEUTILITY,"Don't Exist"); + DEBUG_LOG(SCEUTILITY, "Listing missing save data: %s = %s", thisSaveName.c_str(), fileDataDir.c_str()); realCount++; + } else { + DEBUG_LOG(SCEUTILITY, "Save data not found: %s = %s", thisSaveName.c_str(), fileDataDir.c_str()); } } } @@ -1513,18 +1514,18 @@ int SavedataParam::SetPspParam(SceUtilitySavedataParam *param) saveDataListCount = 1; // get and stock file info for each file - DEBUG_LOG(SCEUTILITY,"Name : %s",GetSaveName(param).c_str()); - std::string fileDataDir = savePath + GetGameName(param) + GetSaveName(param); PSPFileInfo info = GetSaveInfo(fileDataDir); if (info.exists) { SetFileInfo(0, info, GetSaveName(param)); - ERROR_LOG(SCEUTILITY,"%s Exist",fileDataDir.c_str()); + DEBUG_LOG(SCEUTILITY, "Save data exists: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str()); saveNameListDataCount = 1; } else { if (listEmptyFile) { ClearFileInfo(saveDataList[0], GetSaveName(param)); - DEBUG_LOG(SCEUTILITY,"Don't Exist"); + DEBUG_LOG(SCEUTILITY, "Listing missing save data: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str()); + } else { + DEBUG_LOG(SCEUTILITY, "Save data not found: %s = %s", GetSaveName(param).c_str(), fileDataDir.c_str()); } saveNameListDataCount = 0; return 0; @@ -1631,7 +1632,6 @@ PSPFileInfo SavedataParam::GetSaveInfo(std::string saveDir) { } } } - ERROR_LOG(HLE, "size = %d", info.size); return info; } diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 9e13eecf23..5a0ec169a7 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1203,18 +1203,24 @@ void notifyAdhocctlHandlers(u32 flag, u32 error) { // Note: Must not lock peerlock within this function to prevent race-condition with other thread whos owning peerlock and trying to lock context->eventlock owned by this thread void notifyMatchingHandler(SceNetAdhocMatchingContext * context, ThreadMessage * msg, void * opt, u32_le &bufAddr, u32_le &bufLen, u32_le * args) { // Don't share buffer address space with other mipscall in the queue since mipscalls aren't immediately executed - MatchingArgs argsNew; + MatchingArgs argsNew = { 0 }; u32_le dataBufLen = msg->optlen + 8; //max(bufLen, msg->optlen + 8); u32_le dataBufAddr = userMemory.Alloc(dataBufLen); // We will free this memory after returning from mipscall uint8_t * dataPtr = Memory::GetPointer(dataBufAddr); - memcpy(dataPtr, &msg->mac, sizeof(msg->mac)); - if (msg->optlen > 0) - memcpy(dataPtr + 8, opt, msg->optlen); - argsNew.data[0] = context->id; - argsNew.data[1] = msg->opcode; - argsNew.data[2] = dataBufAddr; - argsNew.data[3] = msg->optlen; - argsNew.data[4] = dataBufAddr + 8; // OptData Addr + if (dataPtr) { + memcpy(dataPtr, &msg->mac, sizeof(msg->mac)); + if (msg->optlen > 0) + memcpy(dataPtr + 8, opt, msg->optlen); + + argsNew.data[1] = msg->opcode; + argsNew.data[2] = dataBufAddr; + argsNew.data[3] = msg->optlen; + argsNew.data[4] = dataBufAddr + 8; // OptData Addr + } + else { + argsNew.data[1] = PSP_ADHOC_MATCHING_EVENT_ERROR; // not sure where to put the error code for EVENT_ERROR tho + } + argsNew.data[0] = context->id; argsNew.data[5] = context->handler.entryPoint; //not part of callback argument, just borrowing a space to store callback address so i don't need to search the context first later // ScheduleEvent_Threadsafe_Immediate seems to get mixed up with interrupt (returning from mipscall inside an interrupt) and getting invalid address before returning from interrupt diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 43b012a606..a60b07e71f 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -33,6 +33,7 @@ #include "Core/HLE/HLETables.h" #include "Core/HLE/Plugins.h" #include "Core/HLE/ReplaceTables.h" +#include "Core/HLE/sceDisplay.h" #include "Core/Reporting.h" #include "Core/Host.h" #include "Core/Loaders.h" @@ -117,6 +118,8 @@ static const char * const blacklistedModules[] = { "sceMD5_Library", }; +struct WriteVarSymbolState; + struct VarSymbolImport { char moduleName[KERNELOBJECT_MAX_NAME_LENGTH + 1]; u32 nid; @@ -150,7 +153,7 @@ struct FuncSymbolExport { u32 nid; }; -void ImportVarSymbol(const VarSymbolImport &var); +void ImportVarSymbol(WriteVarSymbolState &state, const VarSymbolImport &var); void ExportVarSymbol(const VarSymbolExport &var); void UnexportVarSymbol(const VarSymbolExport &var); @@ -378,11 +381,11 @@ public: ImportFuncSymbol(func, reimporting, GetName()); } - void ImportVar(const VarSymbolImport &var) { + void ImportVar(WriteVarSymbolState &state, const VarSymbolImport &var) { // Keep track and actually hook it up if possible. importedVars.push_back(var); impExpModuleNames.insert(var.moduleName); - ImportVarSymbol(var); + ImportVarSymbol(state, var); } void ExportFunc(const FuncSymbolExport &func) { @@ -557,22 +560,22 @@ void __KernelModuleShutdown() // Sometimes there are multiple LO16's or HI16's per pair, even though the ABI says nothing of this. // For multiple LO16's, we need the original (unrelocated) instruction data of the HI16. // For multiple HI16's, we just need to set each one. -struct HI16RelocInfo -{ +struct HI16RelocInfo { u32 addr; u32 data; }; -static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool reverse = false) -{ - // We have to post-process the HI16 part, since it might be +1 or not depending on the LO16 value. - static u32 lastHI16ExportAddress = 0; - static std::vector lastHI16Relocs; - static bool lastHI16Processed = true; +// We have to post-process the HI16 part, since it might be +1 or not depending on the LO16 value. +// For that purpose, we use this state to track HI16s to adjust. +struct WriteVarSymbolState { + u32 lastHI16ExportAddress = 0; + std::vector lastHI16Relocs; + bool lastHI16Processed = true; +}; +static void WriteVarSymbol(WriteVarSymbolState &state, u32 exportAddress, u32 relocAddress, u8 type, bool reverse = false) { u32 relocData = Memory::Read_Instruction(relocAddress, true).encoding; - switch (type) - { + switch (type) { case R_MIPS_NONE: WARN_LOG_REPORT(LOADER, "Var relocation type NONE - %08x => %08x", exportAddress, relocAddress); break; @@ -601,13 +604,13 @@ static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool re */ case R_MIPS_HI16: - if (lastHI16ExportAddress != exportAddress) { - if (!lastHI16Processed && lastHI16Relocs.size() >= 1) { - WARN_LOG_REPORT(LOADER, "Unsafe unpaired HI16 variable relocation @ %08x / %08x", lastHI16Relocs[lastHI16Relocs.size() - 1].addr, relocAddress); + if (state.lastHI16ExportAddress != exportAddress) { + if (!state.lastHI16Processed && !state.lastHI16Relocs.empty()) { + WARN_LOG_REPORT(LOADER, "Unsafe unpaired HI16 variable relocation @ %08x / %08x", state.lastHI16Relocs[state.lastHI16Relocs.size() - 1].addr, relocAddress); } - lastHI16ExportAddress = exportAddress; - lastHI16Relocs.clear(); + state.lastHI16ExportAddress = exportAddress; + state.lastHI16Relocs.clear(); } // After this will be an R_MIPS_LO16. If that addition overflows, we need to account for it in HI16. @@ -615,8 +618,8 @@ static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool re HI16RelocInfo reloc; reloc.addr = relocAddress; reloc.data = Memory::Read_Instruction(relocAddress, true).encoding; - lastHI16Relocs.push_back(reloc); - lastHI16Processed = false; + state.lastHI16Relocs.push_back(reloc); + state.lastHI16Processed = false; break; case R_MIPS_LO16: @@ -632,27 +635,26 @@ static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool re } // The ABI requires that these come in pairs, at least. - if (lastHI16Relocs.empty()) { + if (state.lastHI16Relocs.empty()) { ERROR_LOG_REPORT(LOADER, "LO16 without any HI16 variable import at %08x for %08x", relocAddress, exportAddress); // Try to process at least the low relocation... - } else if (lastHI16ExportAddress != exportAddress) { - ERROR_LOG_REPORT(LOADER, "HI16 and LO16 imports do not match at %08x for %08x (should be %08x)", relocAddress, lastHI16ExportAddress, exportAddress); + } else if (state.lastHI16ExportAddress != exportAddress) { + ERROR_LOG_REPORT(LOADER, "HI16 and LO16 imports do not match at %08x for %08x (should be %08x)", relocAddress, state.lastHI16ExportAddress, exportAddress); } else { // Process each of the HI16. Usually there's only one. - for (auto it = lastHI16Relocs.begin(), end = lastHI16Relocs.end(); it != end; ++it) - { + for (auto &reloc : state.lastHI16Relocs) { if (!reverse) { - full = (it->data << 16) + offsetLo + exportAddress; + full = (reloc.data << 16) + offsetLo + exportAddress; } else { - full = (it->data << 16) + offsetLo - exportAddress; + full = (reloc.data << 16) + offsetLo - exportAddress; } // The low instruction will be a signed add, which means (full & 0x8000) will subtract. // We add 1 in that case so that it ends up the right value. u16 high = (full >> 16) + ((full & 0x8000) ? 1 : 0); - Memory::Write_U32((it->data & ~0xFFFF) | high, it->addr); - currentMIPS->InvalidateICache(it->addr, 4); + Memory::Write_U32((reloc.data & ~0xFFFF) | high, reloc.addr); + currentMIPS->InvalidateICache(reloc.addr, 4); } - lastHI16Processed = true; + state.lastHI16Processed = true; } // With full set above (hopefully), now we just need to correct the low instruction. @@ -668,7 +670,7 @@ static void WriteVarSymbol(u32 exportAddress, u32 relocAddress, u8 type, bool re currentMIPS->InvalidateICache(relocAddress, 4); } -void ImportVarSymbol(const VarSymbolImport &var) { +void ImportVarSymbol(WriteVarSymbolState &state, const VarSymbolImport &var) { if (var.nid == 0) { // TODO: What's the right thing for this? ERROR_LOG_REPORT(LOADER, "Var import with nid = 0, type = %d", var.type); @@ -688,9 +690,9 @@ void ImportVarSymbol(const VarSymbolImport &var) { } // Look for exports currently loaded modules already have. Maybe it's available? - for (auto it = module->exportedVars.begin(), end = module->exportedVars.end(); it != end; ++it) { - if (it->Matches(var)) { - WriteVarSymbol(it->symAddr, var.stubAddr, var.type); + for (const auto &exported : module->exportedVars) { + if (exported.Matches(var)) { + WriteVarSymbol(state, exported.symAddr, var.stubAddr, var.type); return; } } @@ -709,10 +711,11 @@ void ExportVarSymbol(const VarSymbolExport &var) { } // Look for imports currently loaded modules already have, hook it up right away. - for (auto it = module->importedVars.begin(), end = module->importedVars.end(); it != end; ++it) { - if (var.Matches(*it)) { + WriteVarSymbolState state; + for (auto &imported : module->importedVars) { + if (var.Matches(imported)) { INFO_LOG(LOADER, "Resolving var %s/%08x", var.moduleName, var.nid); - WriteVarSymbol(var.symAddr, it->stubAddr, it->type); + WriteVarSymbol(state, var.symAddr, imported.stubAddr, imported.type); } } } @@ -727,10 +730,11 @@ void UnexportVarSymbol(const VarSymbolExport &var) { } // Look for imports modules that are *still* loaded have, and reverse them. - for (auto it = module->importedVars.begin(), end = module->importedVars.end(); it != end; ++it) { - if (var.Matches(*it)) { + WriteVarSymbolState state; + for (auto &imported : module->importedVars) { + if (var.Matches(imported)) { INFO_LOG(LOADER, "Unresolving var %s/%08x", var.moduleName, var.nid); - WriteVarSymbol(var.symAddr, it->stubAddr, it->type, true); + WriteVarSymbol(state, var.symAddr, imported.stubAddr, imported.type, true); } } } @@ -1051,12 +1055,13 @@ static bool KernelImportModuleFuncs(PSPModule *module, u32 *firstImportStubAddr, continue; } + WriteVarSymbolState state; u32_le *varRef = (u32_le *)Memory::GetPointer(varRefsPtr); for (; *varRef != 0; ++varRef) { var.nid = nid; var.stubAddr = (*varRef & 0x03FFFFFF) << 2; var.type = *varRef >> 26; - module->ImportVar(var); + module->ImportVar(state, var); } } } else if (entry->numVars > 0 && !reimporting) { @@ -1876,6 +1881,14 @@ void __KernelGPUReplay() { if (!GPURecord::RunMountedReplay(filename)) { Core_Stop(); } + + if (PSP_CoreParameter().headLess && !PSP_CoreParameter().startBreak) { + PSPPointer topaddr; + u32 linesize = 512; + __DisplayGetFramebuf(&topaddr, &linesize, nullptr, 0); + host->SendDebugScreenshot(topaddr, linesize, 272); + Core_Stop(); + } } int sceKernelLoadExec(const char *filename, u32 paramPtr) diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index f5ea5c6db6..8f4bf88ef0 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1337,32 +1337,21 @@ u32 sceKernelReferThreadRunStatus(u32 threadID, u32 statusPtr) int __KernelGetThreadExitStatus(SceUID threadID) { u32 error; PSPThread *t = kernelObjects.Get(threadID, error); - if (t) - { - if (t->nt.status == THREADSTATUS_DORMANT) // TODO: can be dormant before starting, too, need to avoid that - { - DEBUG_LOG(SCEKERNEL, "sceKernelGetThreadExitStatus(%d)", threadID); - return t->nt.exitStatus; - } - else - { - DEBUG_LOG(SCEKERNEL, "sceKernelGetThreadExitStatus(%d): not dormant", threadID); - return SCE_KERNEL_ERROR_NOT_DORMANT; - } + if (!t) { + return hleLogError(SCEKERNEL, error); } - else - { - ERROR_LOG(SCEKERNEL, "sceKernelGetThreadExitStatus Error %08x", error); - return SCE_KERNEL_ERROR_UNKNOWN_THID; + + // __KernelResetThread and __KernelCreateThread set exitStatus in case it's DORMANT. + if (t->nt.status == THREADSTATUS_DORMANT) { + return hleLogSuccessI(SCEKERNEL, t->nt.exitStatus); } + return hleLogDebug(SCEKERNEL, SCE_KERNEL_ERROR_NOT_DORMANT, "not dormant"); } -int sceKernelGetThreadExitStatus(SceUID threadID) -{ +int sceKernelGetThreadExitStatus(SceUID threadID) { u32 status = __KernelGetThreadExitStatus(threadID); // Seems this is called in a tight-ish loop, maybe awaiting an interrupt - issue #13698 - // Guess based on sceKernelGetThreadId. - hleEatCycles(180); + hleEatCycles(330); return status; } diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 72ef4f0dc9..0b6e97b472 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -103,6 +103,7 @@ u32_le matchingThreadCode[3]; int matchingEventThread(int matchingId); int matchingInputThread(int matchingId); +void sendBulkDataPacket(SceNetAdhocMatchingContext* context, SceNetEtherAddr* mac, int datalen, void* data); int AcceptPtpSocket(int ptpId, int newsocket, sockaddr_in& peeraddr, SceNetEtherAddr* addr, u16_le* port); int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock); int FlushPtpSocket(int socketId); @@ -299,15 +300,16 @@ static void __AdhocctlNotify(u64 userdata, int cyclesLate) { CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, adhocctlNotifyEvent, userdata); return; } - else + else if (req.opcode != OPCODE_LOGIN) result = ERROR_NET_ADHOCCTL_BUSY; } } else result = ERROR_NET_ADHOCCTL_WLAN_SWITCH_OFF; + u32 waitVal = __KernelGetWaitValue(threadID, error); __KernelResumeThreadFromWait(threadID, result); - DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhocctl - State: %d", waitID, error, (int)result, adhocctlState); + DEBUG_LOG(SCENET, "Returning (WaitID: %d, error: %d) Result (%08x) of sceNetAdhocctl - Opcode: %d, State: %d", waitID, error, (int)result, waitVal, adhocctlState); // We are done with this request adhocctlRequests.erase(uid); @@ -5279,7 +5281,7 @@ int sceNetAdhocMatchingSendData(int matchingId, const char *mac, int dataLen, u3 peer->sending = 1; // Send Data to Peer - sendBulkData(context, peer, dataLen, data); + sendBulkDataPacket(context, &peer->mac, dataLen, data); // Return Success return 0; @@ -5518,7 +5520,7 @@ void __NetMatchingCallbacks() //(int matchingId) after->SetData(args[0], args[1], args[2]); hleEnqueueCall(args[5], 5, args, after); matchingEvents.pop_front(); - delayus = (adhocMatchingEventDelay + adhocExtraDelay); // Added an extra delay to prevent I/O Timing method from causing disconnection + delayus = adhocMatchingEventDelay; // Add extra delay to prevent I/O Timing method from causing disconnection, but delaying too long may cause matchingEvents to pile up } // Must be delayed long enough whenever there is a pending callback. Should it be 10-100ms for Matching Events? or Not Less than the delays on sceNetAdhocMatching HLE? diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 77c2fafae0..c79faa2149 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -40,8 +40,14 @@ void RegisterFileLoaderFactory(std::string prefix, std::unique_ptr insts; insts.reserve(in.GetInstructions().size()); @@ -822,6 +835,8 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { insts[check.index] = IRReplaceDestGPR(insts[check.index], check.reg, inst.dest); lastWrittenTo[inst.dest] = check.index; check.reg = inst.dest; + // Update the read by exit flag to match the new reg. + check.readByExit = inst.dest < IRTEMP_0 || inst.dest > IRTEMP_LR_SHIFT; // And swap the args for this mov, since we changed the other dest. We'll optimize this out later. std::swap(inst.dest, inst.src1); } else { @@ -903,6 +918,7 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { } bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { + CONDITIONAL_DISABLE; // This tells us to skip an AND op that has been optimized out. // Maybe we could skip multiple, but that'd slow things down and is pretty uncommon. int nextSkip = -1; @@ -1039,6 +1055,8 @@ static std::vector ReorderLoadStoreOps(std::vector &ops) { } bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { + CONDITIONAL_DISABLE; + bool logBlocks = false; enum class RegState : u8 { @@ -1234,6 +1252,8 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) } bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { + CONDITIONAL_DISABLE; + bool logBlocks = false; auto opsCompatible = [&](const IRInst &a, const IRInst &b, int dist) { diff --git a/Core/MIPS/x86/CompFPU.cpp b/Core/MIPS/x86/CompFPU.cpp index cb1d7be5ed..135af332a1 100644 --- a/Core/MIPS/x86/CompFPU.cpp +++ b/Core/MIPS/x86/CompFPU.cpp @@ -41,7 +41,7 @@ // All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. // Currently known non working ones should have DISABLE. -// #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } +// #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; } #define DISABLE { Comp_Generic(op); return; } diff --git a/Core/MIPS/x86/CompLoadStore.cpp b/Core/MIPS/x86/CompLoadStore.cpp index f094684417..403f613f33 100644 --- a/Core/MIPS/x86/CompLoadStore.cpp +++ b/Core/MIPS/x86/CompLoadStore.cpp @@ -41,7 +41,7 @@ // All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. // Currently known non working ones should have DISABLE. -// #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } +// #define CONDITIONAL_DISABLE(flag) { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE(flag) if (jo.Disabled(JitDisable::flag)) { Comp_Generic(op); return; } #define DISABLE { Comp_Generic(op); return; } diff --git a/Core/System.cpp b/Core/System.cpp index 2be0a5f9f0..16ae2808de 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -493,7 +493,7 @@ void PSP_Shutdown() { // Make sure things know right away that PSP memory, etc. is going away. pspIsQuitting = true; if (coreState == CORE_RUNNING) - Core_UpdateState(CORE_POWERDOWN); + Core_Stop(); #ifndef MOBILE_DEVICE if (g_Config.bFuncHashMap) { diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 17b2e75751..3e0c618b0e 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -28,6 +28,9 @@ #else #include "ext/libzip/zip.h" #endif +#ifdef _WIN32 +#include "Common/CommonWindows.h" +#endif #include "Common/Data/Encoding/Utf8.h" #include "Common/Data/Format/IniFile.h" #include "Common/Log.h" diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index e4afd7868a..430ed80669 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -394,7 +394,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (!strcmp(compat.fragColor0, "fragColor0")) { const char *qualifierColor0 = "out"; - if (compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) { + if (readFramebuffer && compat.lastFragData && !strcmp(compat.lastFragData, compat.fragColor0)) { qualifierColor0 = "inout"; } // Output the output color definitions. @@ -778,10 +778,21 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu // When testing against 0 (common), we can avoid some math. // 0.002 is approximately half of 1.0 / 255.0. if (colorTestFunc == GE_COMP_NOTEQUAL) { - WRITE(p, " if (v.r < 0.002 && v.g < 0.002 && v.b < 0.002) %s\n", discardStatement); + if (compat.shaderLanguage == GLSL_VULKAN) { + // Old workaround for Adreno driver bug. We could make this the main path actually + // since the math is roughly equivalent given the non-negative inputs. + WRITE(p, " if (v.r + v.g + v.b < 0.002) %s\n", discardStatement); + } else { + WRITE(p, " if (v.r < 0.002 && v.g < 0.002 && v.b < 0.002) %s\n", discardStatement); + } } else if (colorTestFunc != GE_COMP_NEVER) { - // Anything else is a test for == 0. - WRITE(p, " if (v.r > 0.002 || v.g > 0.002 || v.b > 0.002) %s\n", discardStatement); + if (compat.shaderLanguage == GLSL_VULKAN) { + // See the GE_COMP_NOTEQUAL case. + WRITE(p, " if (v.r + v.g + v.b > 0.002) %s\n", discardStatement); + } else { + // Anything else is a test for == 0. + WRITE(p, " if (v.r > 0.002 || v.g > 0.002 || v.b > 0.002) %s\n", discardStatement); + } } else { // NEVER has been logged as used by games, although it makes little sense - statically failing. // Maybe we could discard the drawcall, but it's pretty rare. Let's just statically discard here. diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 7deafabad8..2f366bf5b8 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -575,6 +575,9 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo renderHeightFactor = renderHeight / 272.0f; } + _assert_(renderWidthFactor > 0.0); + _assert_(renderHeightFactor > 0.0); + renderX = gstate_c.curRTOffsetX; // Scissor @@ -694,8 +697,11 @@ void ConvertViewportAndScissor(bool useBufferedRendering, float renderWidth, flo out.viewportX = left * renderWidthFactor + displayOffsetX; out.viewportY = top * renderHeightFactor + displayOffsetY; - out.viewportW = (right - left) * renderWidthFactor; - out.viewportH = (bottom - top) * renderHeightFactor; + + // The calculations should end up with zero or positive values, but let's protect against any + // precision issues. See #13921. + out.viewportW = std::max(0.0f, (right - left) * renderWidthFactor); + out.viewportH = std::max(0.0f, (bottom - top) * renderHeightFactor); // The depth viewport parameters are the same, but we handle it a bit differently. // When clipping is enabled, depth is clamped to [0, 65535]. And minz/maxz discard. diff --git a/GPU/Common/PresentationCommon.cpp b/GPU/Common/PresentationCommon.cpp index 571c396846..eb1e8d8cd9 100644 --- a/GPU/Common/PresentationCommon.cpp +++ b/GPU/Common/PresentationCommon.cpp @@ -239,7 +239,8 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha return false; } - std::string vsError, fsError; + std::string vsError; + std::string fsError; // All post shaders are written in GLSL 1.0 so that's what we pass in here as a "from" language. Draw::ShaderModule *vs = CompileShaderModule(ShaderStage::Vertex, GLSL_1xx, vsSourceGLSL, &vsError); @@ -248,7 +249,7 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha // Don't worry, CompileShaderModule makes sure they get freed if one succeeded. if (!fs || !vs) { std::string errorString = vsError + "\n" + fsError; - // DO NOT turn this into a report, as it will pollute our logs with all kinds of + // DO NOT turn this into an ERROR_LOG_REPORT, as it will pollute our logs with all kinds of // user shader experiments. ERROR_LOG(FRAMEBUF, "Failed to build post-processing program from %s and %s!\n%s", shaderInfo->vertexShaderFile.c_str(), shaderInfo->fragmentShaderFile.c_str(), errorString.c_str()); ShowPostShaderError(errorString); @@ -263,6 +264,7 @@ bool PresentationCommon::BuildPostShader(const ShaderInfo *shaderInfo, const Sha { "u_setting", 4, 4, UniformType::FLOAT4, offsetof(PostShaderUniforms, setting) }, { "u_video", 5, 5, UniformType::FLOAT1, offsetof(PostShaderUniforms, video) }, } }; + Draw::Pipeline *pipeline = CreatePipeline({ vs, fs }, true, &postShaderDesc); if (!pipeline) return false; @@ -353,7 +355,7 @@ void PresentationCommon::ShowPostShaderError(const std::string &errorString) { } } if (!firstLine.empty()) { - host->NotifyUserMessage("Post-shader error: " + firstLine + "...", 10.0f, 0xFF3090FF); + host->NotifyUserMessage("Post-shader error: " + firstLine + "...:\n" + errorString, 10.0f, 0xFF3090FF); } else { host->NotifyUserMessage("Post-shader error, see log for details", 10.0f, 0xFF3090FF); } diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index a5c18aa1de..e1097ff210 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -61,7 +61,7 @@ std::string VertexShaderDesc(const VShaderID &id) { return desc.str(); } -void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation) { +void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat) { bool isModeThrough = (vertType & GE_VTYPE_THROUGH) != 0; bool doTexture = gstate.isTextureMapEnabled() && !gstate.isModeClear(); bool doShadeMapping = doTexture && (gstate.getUVGenMode() == GE_TEXMAP_ENVIRONMENT_MAP); @@ -112,7 +112,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform, id.SetBits(VS_BIT_BONES, 3, TranslateNumBones(vertTypeGetNumBoneWeights(vertType)) - 1); // 2 bits. We should probably send in the weight scalefactor as a uniform instead, // or simply preconvert all weights to floats. - id.SetBits(VS_BIT_WEIGHT_FMTSCALE, 2, (vertType & GE_VTYPE_WEIGHT_MASK) >> GE_VTYPE_WEIGHT_SHIFT); + id.SetBits(VS_BIT_WEIGHT_FMTSCALE, 2, weightsAsFloat ? 0 : (vertType & GE_VTYPE_WEIGHT_MASK) >> GE_VTYPE_WEIGHT_SHIFT); } if (gstate.isLightingEnabled()) { diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index c98a914be6..d2e49d0e9e 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -225,7 +225,7 @@ class Bugs; } -void ComputeVertexShaderID(VShaderID *id, uint32_t vertexType, bool useHWTransform, bool useHWTessellation); +void ComputeVertexShaderID(VShaderID *id, uint32_t vertexType, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat); // Generates a compact string that describes the shader. Useful in a list to get an overview // of the current flora of shaders. std::string VertexShaderDesc(const VShaderID &id); diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 0a369204b9..89a4606450 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -471,8 +471,8 @@ void SoftwareTransform::Decode(int prim, u32 vertType, const DecVtxFormat &decVt if (!result->setSafeSize && prim == GE_PRIM_RECTANGLES && maxIndex == 2) { bool clearingColor = gstate.isModeClear() && (gstate.isClearModeColorMask() || gstate.isClearModeAlphaMask()); bool writingColor = gstate.getColorMask() != 0xFFFFFFFF; - bool startsZeroX = transformed[0].x <= 0.0f && transformed[1].x > transformed[0].x; - bool startsZeroY = transformed[0].y <= 0.0f && transformed[1].y > transformed[0].y; + bool startsZeroX = transformed[0].x <= 0.0f && transformed[1].x > 0.0f && transformed[1].x > transformed[0].x; + bool startsZeroY = transformed[0].y <= 0.0f && transformed[1].y > 0.0f && transformed[1].y > transformed[0].y; if (startsZeroX && startsZeroY && (clearingColor || writingColor)) { int scissorX2 = gstate.getScissorX2() + 1; diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index c1e7a9e39a..b0d5cb6463 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -822,9 +822,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // Uncomment this to screw up bone shaders to check the vertex shader software fallback // WRITE(p, "THIS SHOULD ERROR! #error"); if (numBoneWeights == 1 && ShaderLanguageIsOpenGL(compat.shaderLanguage)) - WRITE(p, " %s skinMatrix = w1 * u_bone0", boneMatrix); + WRITE(p, " %s skinMatrix = mul(w1, u_bone0)", boneMatrix); else - WRITE(p, " %s skinMatrix = w1.x * u_bone0", boneMatrix); + WRITE(p, " %s skinMatrix = mul(w1.x, u_bone0)", boneMatrix); for (int i = 1; i < numBoneWeights; i++) { const char *weightAttr = boneWeightAttr[i]; // workaround for "cant do .x of scalar" issue. @@ -832,7 +832,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag if (numBoneWeights == 1 && i == 0) weightAttr = "w1"; if (numBoneWeights == 5 && i == 4) weightAttr = "w2"; } - WRITE(p, " + %s * u_bone%i", weightAttr, i); + WRITE(p, " + mul(%s, u_bone%i)", weightAttr, i); } WRITE(p, ";\n"); diff --git a/GPU/D3D11/DrawEngineD3D11.cpp b/GPU/D3D11/DrawEngineD3D11.cpp index 8c10d6fe73..05537d2eea 100644 --- a/GPU/D3D11/DrawEngineD3D11.cpp +++ b/GPU/D3D11/DrawEngineD3D11.cpp @@ -523,7 +523,7 @@ rotateVBO: D3D11VertexShader *vshader; D3D11FragmentShader *fshader; - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, useHWTransform, useHWTessellation_); + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, useHWTransform, useHWTessellation_, decOptions_.expandAllWeightsToFloat); ID3D11InputLayout *inputLayout = SetupDecFmtForDraw(vshader, dec_->GetDecVtxFmt(), dec_->VertexType()); context_->PSSetShader(fshader->GetShader(), nullptr, 0); context_->VSSetShader(vshader->GetShader(), nullptr, 0); @@ -609,7 +609,7 @@ rotateVBO: D3D11VertexShader *vshader; D3D11FragmentShader *fshader; - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, false, false); + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, false, false, decOptions_.expandAllWeightsToFloat); context_->PSSetShader(fshader->GetShader(), nullptr, 0); context_->VSSetShader(vshader->GetShader(), nullptr, 0); shaderManager_->UpdateUniforms(framebufferManager_->UseBufferedRendering()); diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 51dbaf4018..fbafe04285 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -36,7 +36,6 @@ #include "GPU/Debugger/Stepping.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/PresentationCommon.h" -#include "Common/GPU/ShaderTranslation.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/D3D11/FramebufferManagerD3D11.h" #include "GPU/D3D11/ShaderManagerD3D11.h" @@ -125,15 +124,11 @@ FramebufferManagerD3D11::FramebufferManagerD3D11(Draw::DrawContext *draw) uint32_t nullData[1]{}; context_->UpdateSubresource(nullTexture_, 0, nullptr, nullData, 1, 0); - ShaderTranslationInit(); - presentation_->SetLanguage(HLSL_D3D11); preferredPixelsFormat_ = Draw::DataFormat::B8G8R8A8_UNORM; } FramebufferManagerD3D11::~FramebufferManagerD3D11() { - ShaderTranslationShutdown(); - // Drawing cleanup if (quadVertexShader_) quadVertexShader_->Release(); diff --git a/GPU/D3D11/ShaderManagerD3D11.cpp b/GPU/D3D11/ShaderManagerD3D11.cpp index ddcbc814d5..8d612e2fc9 100644 --- a/GPU/D3D11/ShaderManagerD3D11.cpp +++ b/GPU/D3D11/ShaderManagerD3D11.cpp @@ -177,13 +177,13 @@ void ShaderManagerD3D11::BindUniforms() { context_->PSSetConstantBuffers(0, 1, ps_cbs); } -void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, bool useHWTransform, bool useHWTessellation) { +void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat) { VShaderID VSID; FShaderID FSID; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); - ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation); + ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation, weightsAsFloat); } else { VSID = lastVSID_; } diff --git a/GPU/D3D11/ShaderManagerD3D11.h b/GPU/D3D11/ShaderManagerD3D11.h index fe21710ad9..e0ac257283 100644 --- a/GPU/D3D11/ShaderManagerD3D11.h +++ b/GPU/D3D11/ShaderManagerD3D11.h @@ -85,7 +85,7 @@ public: ShaderManagerD3D11(Draw::DrawContext *draw, ID3D11Device *device, ID3D11DeviceContext *context, D3D_FEATURE_LEVEL featureLevel); ~ShaderManagerD3D11(); - void GetShaders(int prim, u32 vertType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, bool useHWTransform, bool useHWTessellation); + void GetShaders(int prim, u32 vertType, D3D11VertexShader **vshader, D3D11FragmentShader **fshader, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat); void ClearShaders(); void DirtyLastShader() override; diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index c464652318..0f7603e8de 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -324,7 +324,7 @@ void DrawEngineDX9::DoFlush() { GEPrimitiveType prim = prevPrim_; ApplyDrawState(prim); - VSShader *vshader = shaderManager_->ApplyShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_); + VSShader *vshader = shaderManager_->ApplyShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_, decOptions_.expandAllWeightsToFloat); if (vshader->UseHWTransform()) { LPDIRECT3DVERTEXBUFFER9 vb_ = NULL; @@ -505,7 +505,7 @@ rotateVBO: } ApplyDrawStateLate(); - vshader = shaderManager_->ApplyShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_); + vshader = shaderManager_->ApplyShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_, decOptions_.expandAllWeightsToFloat); IDirect3DVertexDeclaration9 *pHardwareVertexDecl = SetupDecFmtForDraw(vshader, dec_->GetDecVtxFmt(), dec_->VertexType()); if (pHardwareVertexDecl) { @@ -568,7 +568,7 @@ rotateVBO: framebufferManager_->SetSafeSize(result.safeWidth, result.safeHeight); ApplyDrawStateLate(); - vshader = shaderManager_->ApplyShader(false, false, lastVType_); + vshader = shaderManager_->ApplyShader(false, false, lastVType_, decOptions_.expandAllWeightsToFloat); if (result.action == SW_DRAW_PRIMITIVES) { if (result.setStencil) { diff --git a/GPU/Directx9/FramebufferManagerDX9.cpp b/GPU/Directx9/FramebufferManagerDX9.cpp index 70db6442b1..f47678456c 100644 --- a/GPU/Directx9/FramebufferManagerDX9.cpp +++ b/GPU/Directx9/FramebufferManagerDX9.cpp @@ -31,7 +31,6 @@ #include "Common/GPU/D3D9/D3D9StateCache.h" #include "GPU/Common/FramebufferManagerCommon.h" #include "GPU/Common/PresentationCommon.h" -#include "Common/GPU/ShaderTranslation.h" #include "GPU/Common/TextureDecoder.h" #include "GPU/Directx9/FramebufferManagerDX9.h" #include "GPU/Directx9/ShaderManagerDX9.h" @@ -114,15 +113,11 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { memset(rect.pBits, 0, 4); nullTex_->UnlockRect(0); - ShaderTranslationInit(); - presentation_->SetLanguage(HLSL_D3D9); preferredPixelsFormat_ = Draw::DataFormat::B8G8R8A8_UNORM; } FramebufferManagerDX9::~FramebufferManagerDX9() { - ShaderTranslationShutdown(); - if (pFramebufferVertexShader) { pFramebufferVertexShader->Release(); pFramebufferVertexShader = nullptr; diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index f29f89eda3..bb34814fcb 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -542,7 +542,7 @@ void ShaderManagerDX9::DirtyLastShader() { // disables vertex arrays lastPShader_ = nullptr; } -VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellation, u32 vertType) { +VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellation, u32 vertType, bool weightsAsFloat) { // Always use software for flat shading to fix the provoking index. bool tess = gstate_c.submitType == SubmitType::HW_BEZIER || gstate_c.submitType == SubmitType::HW_SPLINE; useHWTransform = useHWTransform && (tess || gstate.getShadeMode() != GE_SHADE_FLAT); @@ -550,7 +550,7 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat VShaderID VSID; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); - ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation); + ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation, weightsAsFloat); } else { VSID = lastVSID_; } @@ -599,7 +599,7 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat } delete vs; - ComputeVertexShaderID(&VSID, vertType, false, false); + ComputeVertexShaderID(&VSID, vertType, false, false, weightsAsFloat); // TODO: Look for existing shader with the appropriate ID, use that instead of generating a new one - however, need to make sure // that that shader ID is not used when computing the linked shader ID below, because then IDs won't match diff --git a/GPU/Directx9/ShaderManagerDX9.h b/GPU/Directx9/ShaderManagerDX9.h index d8062ef881..a524912d50 100644 --- a/GPU/Directx9/ShaderManagerDX9.h +++ b/GPU/Directx9/ShaderManagerDX9.h @@ -80,7 +80,7 @@ public: ~ShaderManagerDX9(); void ClearCache(bool deleteThem); // TODO: deleteThem currently not respected - VSShader *ApplyShader(bool useHWTransform, bool useHWTessellation, u32 vertType); + VSShader *ApplyShader(bool useHWTransform, bool useHWTessellation, u32 vertType, bool weightsAsFloat); void DirtyShader(); void DirtyLastShader() override; diff --git a/GPU/GLES/DrawEngineGLES.cpp b/GPU/GLES/DrawEngineGLES.cpp index 740e287e47..a15a179244 100644 --- a/GPU/GLES/DrawEngineGLES.cpp +++ b/GPU/GLES/DrawEngineGLES.cpp @@ -333,7 +333,7 @@ void DrawEngineGLES::DoFlush() { GEPrimitiveType prim = prevPrim_; VShaderID vsid; - Shader *vshader = shaderManager_->ApplyVertexShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_, &vsid); + Shader *vshader = shaderManager_->ApplyVertexShader(CanUseHardwareTransform(prim), useHWTessellation_, lastVType_, decOptions_.expandAllWeightsToFloat, &vsid); GLRBuffer *vertexBuffer = nullptr; GLRBuffer *indexBuffer = nullptr; diff --git a/GPU/GLES/ShaderManagerGLES.cpp b/GPU/GLES/ShaderManagerGLES.cpp index 250757cb06..f6914aecc4 100644 --- a/GPU/GLES/ShaderManagerGLES.cpp +++ b/GPU/GLES/ShaderManagerGLES.cpp @@ -656,10 +656,10 @@ Shader *ShaderManagerGLES::CompileVertexShader(VShaderID VSID) { return new Shader(render_, codeBuffer_, desc, GL_VERTEX_SHADER, useHWTransform, attrMask, uniformMask); } -Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertType, VShaderID *VSID) { +Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertType, bool weightsAsFloat, VShaderID *VSID) { if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); - ComputeVertexShaderID(VSID, vertType, useHWTransform, useHWTessellation); + ComputeVertexShaderID(VSID, vertType, useHWTransform, useHWTessellation, weightsAsFloat); } else { *VSID = lastVSID_; } @@ -690,7 +690,7 @@ Shader *ShaderManagerGLES::ApplyVertexShader(bool useHWTransform, bool useHWTess // Can still work with software transform. VShaderID vsidTemp; - ComputeVertexShaderID(&vsidTemp, vertType, false, false); + ComputeVertexShaderID(&vsidTemp, vertType, false, false, weightsAsFloat); vs = CompileVertexShader(vsidTemp); } diff --git a/GPU/GLES/ShaderManagerGLES.h b/GPU/GLES/ShaderManagerGLES.h index f93c9a4d24..bdaf73a367 100644 --- a/GPU/GLES/ShaderManagerGLES.h +++ b/GPU/GLES/ShaderManagerGLES.h @@ -146,7 +146,7 @@ public: // This is the old ApplyShader split into two parts, because of annoying information dependencies. // If you call ApplyVertexShader, you MUST call ApplyFragmentShader soon afterwards. - Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertType, VShaderID *VSID); + Shader *ApplyVertexShader(bool useHWTransform, bool useHWTessellation, u32 vertType, bool weightsAsFloat, VShaderID *VSID); LinkedShader *ApplyFragmentShader(VShaderID VSID, Shader *vs, u32 vertType, bool useBufferedRendering); void DeviceLost(); diff --git a/GPU/Software/RasterizerRectangle.cpp b/GPU/Software/RasterizerRectangle.cpp index 668a9476e3..828a7d622b 100644 --- a/GPU/Software/RasterizerRectangle.cpp +++ b/GPU/Software/RasterizerRectangle.cpp @@ -96,7 +96,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { int z = pos0.z; float fog = 1.0f; - bool isWhite = v0.color0 == Vec4(255, 255, 255, 255); + bool isWhite = v1.color0 == Vec4(255, 255, 255, 255); if (gstate.isTextureMapEnabled()) { // 1:1 (but with mirror support) texture mapping! @@ -153,7 +153,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { } } else { for (int x = pos0.x; x < pos1.x; x++) { - Vec4 prim_color = v0.color0; + Vec4 prim_color = v1.color0; Vec4 tex_color = Vec4::FromRGBA(nearestFunc(s, t, texptr, texbufw, 0)); prim_color = ModulateRGBA(prim_color, tex_color); if (prim_color.a() > 0) { @@ -171,7 +171,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { int s = s_start; // Not really that fast but faster than triangle. for (int x = pos0.x; x < pos1.x; x++) { - Vec4 prim_color = v0.color0; + Vec4 prim_color = v1.color0; Vec4 tex_color = Vec4::FromRGBA(nearestFunc(s, t, texptr, texbufw, 0)); prim_color = GetTextureFunctionOutput(prim_color, tex_color); DrawingCoords pos(x, y, z); @@ -199,13 +199,13 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { gstate.getTextureFunction() == GE_TEXFUNC_MODULATE && gstate.getColorMask() == 0x000000 && gstate.FrameBufFormat() == GE_FORMAT_5551) { - if (v0.color0.a() == 0) + if (v1.color0.a() == 0) return; for (int y = pos0.y; y < pos1.y; y++) { u16 *pixel = fb.Get16Ptr(pos0.x, y, gstate.FrameBufStride()); for (int x = pos0.x; x < pos1.x; x++) { - Vec4 prim_color = v0.color0; + Vec4 prim_color = v1.color0; DrawSinglePixel5551(pixel, prim_color.ToRGBA()); pixel++; } @@ -213,7 +213,7 @@ void DrawSprite(const VertexData& v0, const VertexData& v1) { } else { for (int y = pos0.y; y < pos1.y; y++) { for (int x = pos0.x; x < pos1.x; x++) { - Vec4 prim_color = v0.color0; + Vec4 prim_color = v1.color0; DrawingCoords pos(x, y, z); DrawSinglePixelNonClear(pos, (u16)z, fog, prim_color); } @@ -259,9 +259,9 @@ bool RectangleFastPath(const VertexData &v0, const VertexData &v1) { g_needsClearAfterDialog = false; // Afterwards, we also need to clear the actual destination. Can do a fast rectfill. gstate.textureMapEnable &= ~1; - VertexData newV0 = v0; - newV0.color0 = Vec4(0, 0, 0, 255); - Rasterizer::DrawSprite(newV0, v1); + VertexData newV1 = v1; + newV1.color0 = Vec4(0, 0, 0, 255); + Rasterizer::DrawSprite(v0, newV1); gstate.textureMapEnable |= 1; } return true; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index cd9d443945..bff4f56be6 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -78,11 +78,9 @@ SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw) presentation_->SetLanguage(draw_->GetShaderLanguageDesc().shaderLanguage); break; case GPUBackend::DIRECT3D9: - ShaderTranslationInit(); presentation_->SetLanguage(HLSL_D3D9); break; case GPUBackend::DIRECT3D11: - ShaderTranslationInit(); presentation_->SetLanguage(HLSL_D3D11); break; case GPUBackend::VULKAN: @@ -119,15 +117,6 @@ SoftGPU::~SoftGPU() { if (presentation_) { delete presentation_; - switch (GetGPUBackend()) { - case GPUBackend::DIRECT3D9: - case GPUBackend::DIRECT3D11: - ShaderTranslationShutdown(); - break; - case GPUBackend::OPENGL: - case GPUBackend::VULKAN: - break; - } } Sampler::Shutdown(); diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index f30e357d30..12897153a9 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -818,7 +818,7 @@ void DrawEngineVulkan::DoFlush() { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); } - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, true, useHWTessellation_); // usehwtransform + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, true, useHWTessellation_, decOptions_.expandAllWeightsToFloat); // usehwtransform _dbg_assert_msg_(vshader->UseHWTransform(), "Bad vshader"); Draw::NativeObject object = framebufferManager_->UseBufferedRendering() ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS; @@ -939,7 +939,7 @@ void DrawEngineVulkan::DoFlush() { sampler = nullSampler_; } if (!lastPipeline_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE) || prim != lastPrim_) { - shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, false, false); // usehwtransform + shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, false, false, decOptions_.expandAllWeightsToFloat); // usehwtransform _dbg_assert_msg_(!vshader->UseHWTransform(), "Bad vshader"); if (prim != lastPrim_ || gstate_c.IsDirty(DIRTY_BLEND_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE)) { ConvertStateToVulkanKey(*framebufferManager_, shaderManager_, prim, pipelineKey_, dynState_); diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index 965f066a23..9151e9299b 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -228,11 +228,11 @@ uint64_t ShaderManagerVulkan::UpdateUniforms(bool useBufferedRendering) { return dirty; } -void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, bool useHWTransform, bool useHWTessellation) { +void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat) { VShaderID VSID; if (gstate_c.IsDirty(DIRTY_VERTEXSHADER_STATE)) { gstate_c.Clean(DIRTY_VERTEXSHADER_STATE); - ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation); + ComputeVertexShaderID(&VSID, vertType, useHWTransform, useHWTessellation, weightsAsFloat); } else { VSID = lastVSID_; } diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index e35a07a103..c7d036ce21 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -88,7 +88,7 @@ public: void DeviceRestore(VulkanContext *vulkan, Draw::DrawContext *draw); - void GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, bool useHWTransform, bool useHWTessellation); + void GetShaders(int prim, u32 vertType, VulkanVertexShader **vshader, VulkanFragmentShader **fshader, bool useHWTransform, bool useHWTessellation, bool weightsAsFloat); void ClearShaders(); void DirtyShader(); void DirtyLastShader() override; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 273043185e..aa3f0f94c0 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -394,6 +394,9 @@ void TextureCacheVulkan::CompileScalingShader() { if (copyCS_ != VK_NULL_HANDLE) vulkan_->Delete().QueueDeleteShaderModule(copyCS_); textureShader_.clear(); + } else if (uploadCS_ || copyCS_) { + // No need to recreate. + return; } if (!g_Config.bTexHardwareScaling) return; diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index 4dfeaebeb5..5be0cefa01 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -149,6 +149,23 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + std::vector result; + switch (prop) { + case SYSPROP_TEMP_DIRS: + if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) + result.push_back(getenv("TMPDIR")); + if (getenv("TMP") && strlen(getenv("TMP")) != 0) + result.push_back(getenv("TMP")); + if (getenv("TEMP") && strlen(getenv("TEMP")) != 0) + result.push_back(getenv("TEMP")); + return result; + + default: + return result; + } +} + int System_GetPropertyInt(SystemProperty prop) { switch (prop) { #if defined(SDL) diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 8d43dfdb66..98b56fe912 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -325,6 +325,24 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + std::vector result; + + switch (prop) { + case SYSPROP_TEMP_DIRS: + if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) + result.push_back(getenv("TMPDIR")); + if (getenv("TMP") && strlen(getenv("TMP")) != 0) + result.push_back(getenv("TMP")); + if (getenv("TEMP") && strlen(getenv("TEMP")) != 0) + result.push_back(getenv("TEMP")); + return result; + + default: + return result; + } +} + int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 65c7e07644..99a3b412d9 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -335,8 +335,10 @@ bool CwCheatScreen::RebuildCheatFile(int index) { return false; } - for (const auto &line : lines) { - fs << line << '\n'; + for (int i = 0; i < lines.size(); ++i) { + fs << lines[i]; + if (i != lines.size()-1) + fs << '\n'; } fs.close(); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 15637baf13..9f55a050e9 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -420,6 +420,8 @@ void EmuScreen::sendMessage(const char *message, const char *value) { PSP_Shutdown(); bootPending_ = true; gamePath_ = value; + // Don't leave it on CORE_POWERDOWN, we'll sometimes aggressively bail. + Core_UpdateState(CORE_POWERUP); } } else if (!strcmp(message, "config_loaded")) { // In case we need to position touch controls differently. diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index d8387b84fa..c6f6011954 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -2,6 +2,14 @@ #include "Common/Data/Text/I18n.h" #include "Common/UI/View.h" #include "Common/GPU/Shader.h" +#include "Common/GPU/ShaderWriter.h" + +const uint32_t textColorOK = 0xFF30FF30; +const uint32_t textColorBAD = 0xFF3030FF; +const uint32_t bgColorOK = 0xFF106010; +const uint32_t bgColorBAD = 0xFF101060; + +// TODO: One day, port these to use ShaderWriter. static const std::vector fsDiscard = { {ShaderLanguage::GLSL_1xx, @@ -45,6 +53,188 @@ static const std::vector fsDiscard = { }, }; +static const std::vector fsAdrenoLogicTest = { + {ShaderLanguage::GLSL_1xx, + R"( + #ifdef GL_ES + precision lowp float; + #endif + #if __VERSION__ >= 130 + #define varying in + #define gl_FragColor fragColor0 + out vec4 fragColor0; + #endif + varying vec4 oColor0; + varying vec2 oTexCoord0; + uniform sampler2D Sampler0; + void main() { + #if __VERSION__ >= 130 + vec4 color = (texture(Sampler0, oTexCoord0) * oColor0).aaaa; + #else + vec4 color = (texture2D(Sampler0, oTexCoord0) * oColor0).aaaa; + #endif + color *= 2.0; + if (color.r < 0.002 && color.g < 0.002 && color.b < 0.002) discard; + gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); + })" + }, + {ShaderLanguage::GLSL_VULKAN, + R"(#version 450 + #extension GL_ARB_separate_shader_objects : enable + #extension GL_ARB_shading_language_420pack : enable + precision lowp float; + layout(location = 0) flat in lowp vec4 oColor0; + layout(location = 1) in highp vec2 oTexCoord0; + layout(location = 0) out vec4 fragColor0; + layout(set = 0, binding = 1) uniform sampler2D Sampler0; + void main() { + vec4 t = texture(Sampler0, oTexCoord0).aaaa; + vec4 p = oColor0; + vec4 v = p * t; + v.rgb = clamp(v.rgb * 2.0, 0.0, 1.0); + if (v.r < 0.2 && v.g < 0.2 && v.b < 0.2) discard; + fragColor0 = vec4(0.0, 1.0, 0.0, 1.0); + })" + }, +}; + +static const std::vector vsAdrenoLogicTest = { + { GLSL_1xx, + "#if __VERSION__ >= 130\n" + "#define attribute in\n" + "#define varying out\n" + "#endif\n" + "attribute vec3 Position;\n" + "attribute vec4 Color0;\n" + "attribute vec2 TexCoord0;\n" + "varying vec4 oColor0;\n" + "varying vec2 oTexCoord0;\n" + "uniform mat4 WorldViewProj;\n" + "void main() {\n" + " gl_Position = WorldViewProj * vec4(Position, 1.0);\n" + " oColor0 = Color0;\n" + " oTexCoord0 = TexCoord0;\n" + "}\n" + }, + { ShaderLanguage::GLSL_VULKAN, + "#version 450\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "#extension GL_ARB_shading_language_420pack : enable\n" + "layout (std140, set = 0, binding = 0) uniform bufferVals {\n" + " mat4 WorldViewProj;\n" + "} myBufferVals;\n" + "layout (location = 0) in vec4 pos;\n" + "layout (location = 1) in vec4 inColor;\n" + "layout (location = 2) in vec2 inTexCoord;\n" + "layout (location = 0) out lowp vec4 outColor;\n" + "layout (location = 1) out highp vec2 outTexCoord;\n" + "out gl_PerVertex { vec4 gl_Position; };\n" + "void main() {\n" + " outColor = inColor;\n" + " outTexCoord = inTexCoord;\n" + " gl_Position = myBufferVals.WorldViewProj * pos;\n" + "}\n" + } +}; + +static const std::vector fsFlat = { + {ShaderLanguage::GLSL_3xx, + "#ifdef GL_ES\n" + "precision lowp float;\n" + "precision highp int;\n" + "#endif\n" + "uniform sampler2D Sampler0;\n" + "flat in lowp vec4 oColor0;\n" + "in mediump vec3 oTexCoord0;\n" + "out vec4 fragColor0;\n" + "void main() {\n" + " vec4 t = texture(Sampler0, oTexCoord0.xy);\n" + " vec4 p = oColor0;\n" + " vec4 v = p * t;\n" + " fragColor0 = v;\n" + "}\n" + }, + {ShaderLanguage::GLSL_1xx, + "#ifdef GL_ES\n" + "precision lowp float;\n" + "#endif\n" + "#if __VERSION__ >= 130\n" + "#define varying in\n" + "#define texture2D texture\n" + "#define gl_FragColor fragColor0\n" + "out vec4 fragColor0;\n" + "#endif\n" + "varying vec4 oColor0;\n" + "varying vec2 oTexCoord0;\n" + "uniform sampler2D Sampler0;\n" + "void main() { gl_FragColor = texture2D(Sampler0, oTexCoord0) * oColor0; }\n" + }, + {ShaderLanguage::GLSL_VULKAN, + "#version 450\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "#extension GL_ARB_shading_language_420pack : enable\n" + "layout(location = 0) flat in lowp vec4 oColor0;\n" + "layout(location = 1) in highp vec2 oTexCoord0;\n" + "layout(location = 0) out vec4 fragColor0;\n" + "layout(set = 0, binding = 1) uniform sampler2D Sampler0;\n" + "void main() { fragColor0 = texture(Sampler0, oTexCoord0) * oColor0; }\n" + } +}; + +static const std::vector vsFlat = { + { GLSL_3xx, + "#version 300 es\n" + "in vec3 Position;\n" + "in vec2 TexCoord0;\n" + "in lowp vec4 Color0;\n" + "uniform mat4 WorldViewProj;\n" + "flat out lowp vec4 oColor0;\n" + "out mediump vec3 oTexCoord0;\n" + "void main() {\n" + " oTexCoord0 = vec3(TexCoord0, 1.0);\n" + " oColor0 = Color0;\n" + " vec4 outPos = WorldViewProj * vec4(Position, 1.0);\n" + " gl_Position = outPos;\n" + "}\n" + }, + { GLSL_1xx, // Doesn't actually repro the problem since flat support isn't guaranteed + "#if __VERSION__ >= 130\n" + "#define attribute in\n" + "#define varying out\n" + "#endif\n" + "attribute vec3 Position;\n" + "attribute vec4 Color0;\n" + "attribute vec2 TexCoord0;\n" + "varying vec4 oColor0;\n" + "varying vec2 oTexCoord0;\n" + "uniform mat4 WorldViewProj;\n" + "void main() {\n" + " gl_Position = WorldViewProj * vec4(Position, 1.0);\n" + " oColor0 = Color0;\n" + " oTexCoord0 = TexCoord0;\n" + "}\n" + }, + { ShaderLanguage::GLSL_VULKAN, + "#version 450\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "#extension GL_ARB_shading_language_420pack : enable\n" + "layout (std140, set = 0, binding = 0) uniform bufferVals {\n" + " mat4 WorldViewProj;\n" + "} myBufferVals;\n" + "layout (location = 0) in vec4 pos;\n" + "layout (location = 1) in vec4 inColor;\n" + "layout (location = 2) in vec2 inTexCoord;\n" + "layout (location = 0) flat out lowp vec4 outColor;\n" + "layout (location = 1) out highp vec2 outTexCoord;\n" + "out gl_PerVertex { vec4 gl_Position; };\n" + "void main() {\n" + " outColor = inColor;\n" + " outTexCoord = inTexCoord;\n" + " gl_Position = myBufferVals.WorldViewProj * pos;\n" + "}\n" + } +}; + GPUDriverTestScreen::GPUDriverTestScreen() { using namespace Draw; } @@ -76,11 +266,35 @@ GPUDriverTestScreen::~GPUDriverTestScreen() { if (discardFragShader_) discardFragShader_->Release(); + + // Shader test + if (adrenoLogicDiscardPipeline_) + adrenoLogicDiscardPipeline_->Release(); + if (flatShadingPipeline_) + flatShadingPipeline_->Release(); + + if (adrenoLogicDiscardFragShader_) + adrenoLogicDiscardFragShader_->Release(); + if (adrenoLogicDiscardVertShader_) + adrenoLogicDiscardVertShader_->Release(); + if (flatFragShader_) + flatFragShader_->Release(); + if (flatVertShader_) + flatVertShader_->Release(); + if (samplerNearest_) samplerNearest_->Release(); } void GPUDriverTestScreen::CreateViews() { + using namespace Draw; + + if (!samplerNearest_) { + DrawContext *draw = screenManager()->getDrawContext(); + SamplerStateDesc nearestDesc{}; + samplerNearest_ = draw->CreateSamplerState(nearestDesc); + } + // Don't bother with views for now. using namespace UI; auto di = GetI18NCategory("Dialog"); @@ -92,6 +306,7 @@ void GPUDriverTestScreen::CreateViews() { tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, false)); anchor->Add(tabHolder_); tabHolder_->AddTab("Discard", new LinearLayout(ORIENT_VERTICAL)); + tabHolder_->AddTab("Shader", new LinearLayout(ORIENT_VERTICAL)); Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(100, WRAP_CONTENT, 10, NONE, NONE, 10)); back->OnClick.Handle(this, &UIScreen::OnBack); @@ -230,9 +445,6 @@ void GPUDriverTestScreen::DiscardTest() { depthTestLessEqual->Release(); depthTestGreater->Release(); rasterNoCull->Release(); - - SamplerStateDesc nearestDesc{}; - samplerNearest_ = draw->CreateSamplerState(nearestDesc); } UIContext &dc = *screenManager()->getUIContext(); @@ -253,11 +465,6 @@ void GPUDriverTestScreen::DiscardTest() { {false, false, true, true}, }; - uint32_t textColorOK = 0xFF30FF30; - uint32_t textColorBAD = 0xFF3030FF; - uint32_t bgColorOK = 0xFF106010; - uint32_t bgColorBAD = 0xFF101060; - // Don't want any fancy font texture stuff going on here, so use FLAG_DYNAMIC_ASCII everywhere! // We draw the background at Z=0.5 and the text at Z=0.9. @@ -328,11 +535,111 @@ void GPUDriverTestScreen::DiscardTest() { dc.Flush(); } +void GPUDriverTestScreen::ShaderTest() { + using namespace Draw; + + UIContext &dc = *screenManager()->getUIContext(); + Draw::DrawContext *draw = dc.GetDrawContext(); + + if (!adrenoLogicDiscardPipeline_) { + adrenoLogicDiscardFragShader_ = CreateShader(draw, ShaderStage::Fragment, fsAdrenoLogicTest); + adrenoLogicDiscardVertShader_ = CreateShader(draw, ShaderStage::Vertex, vsAdrenoLogicTest); + + flatFragShader_ = CreateShader(draw, ShaderStage::Fragment, fsFlat); + flatVertShader_ = CreateShader(draw, ShaderStage::Vertex, vsFlat); + + InputLayout *inputLayout = ui_draw2d.CreateInputLayout(draw); + // No blending used. + BlendState *blendOff = draw->CreateBlendState({ false, 0xF }); + + // No depth or stencil, we only use discard in this test. + DepthStencilStateDesc dsDesc{}; + dsDesc.depthTestEnabled = false; + dsDesc.depthWriteEnabled = false; + DepthStencilState *depthStencilOff = draw->CreateDepthStencilState(dsDesc); + + RasterState *rasterNoCull = draw->CreateRasterState({}); + + PipelineDesc adrenoLogicDiscardDesc{ + Primitive::TRIANGLE_LIST, + { adrenoLogicDiscardVertShader_, adrenoLogicDiscardFragShader_ }, + inputLayout, depthStencilOff, blendOff, rasterNoCull, &vsColBufDesc, + }; + adrenoLogicDiscardPipeline_ = draw->CreateGraphicsPipeline(adrenoLogicDiscardDesc); + + PipelineDesc flatDesc{ + Primitive::TRIANGLE_LIST, + { flatVertShader_, flatFragShader_ }, + inputLayout, depthStencilOff, blendOff, rasterNoCull, &vsColBufDesc, + }; + flatShadingPipeline_ = draw->CreateGraphicsPipeline(flatDesc); + + inputLayout->Release(); + blendOff->Release(); + depthStencilOff->Release(); + rasterNoCull->Release(); + } + + Bounds layoutBounds = dc.GetLayoutBounds(); + + dc.Begin(); + dc.SetFontScale(1.0f, 1.0f); + std::string apiName = screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME); + std::string vendor = screenManager()->getDrawContext()->GetInfoString(InfoField::VENDORSTRING); + std::string driver = screenManager()->getDrawContext()->GetInfoString(InfoField::DRIVER); + dc.DrawText(apiName.c_str(), layoutBounds.centerX(), 20, 0xFFFFFFFF, ALIGN_CENTER); + dc.DrawText(vendor.c_str(), layoutBounds.centerX(), 60, 0xFFFFFFFF, ALIGN_CENTER); + dc.DrawText(driver.c_str(), layoutBounds.centerX(), 100, 0xFFFFFFFF, ALIGN_CENTER); + dc.Flush(); + + float y = layoutBounds.y + 150; + float x = layoutBounds.x + 10; + dc.Begin(); + dc.DrawText("Adreno logic", x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + dc.Flush(); + + float testW = 170.f; + + Bounds bounds = { x + 200, y, testW, 70 }; + + // Draw rectangle that should result in the bg + dc.Begin(); + dc.FillRect(UI::Drawable(bgColorOK), bounds); + dc.Flush(); + + // Draw text on it using the shader. + dc.BeginPipeline(adrenoLogicDiscardPipeline_, samplerNearest_); + dc.DrawTextRect("TEST OK", bounds, textColorOK, ALIGN_HCENTER | ALIGN_VCENTER | FLAG_DYNAMIC_ASCII); + dc.Flush(); + + y += 100; + + dc.Begin(); + dc.DrawText("Flat shaded tex", x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + dc.DrawText("(TEST OK if logo but no gradient!)", x + 400, y, 0xFFFFFFFF, ALIGN_LEFT); + dc.Flush(); + + bounds = { x + 200, y, 100, 100 }; + + // Draw rectangle that should be flat shaded + dc.BeginPipeline(flatShadingPipeline_, samplerNearest_); + // There is a "provoking vertex" difference here between GL and Vulkan when using flat shading. One gets one color, one gets the other. + // Wherever possible we should reconfigure the GL provoking vertex to match Vulkan, probably. + dc.DrawImageVGradient(ImageID("I_ICON"), 0xFFFFFFFF, 0xFF808080, bounds); + dc.Flush(); +} + + void GPUDriverTestScreen::render() { using namespace Draw; UIScreen::render(); - if (tabHolder_->GetCurrentTab() == 0) { + switch (tabHolder_->GetCurrentTab()) { + case 0: DiscardTest(); + break; + case 1: + ShaderTest(); + break; } } diff --git a/UI/GPUDriverTestScreen.h b/UI/GPUDriverTestScreen.h index 7f20ecab82..2eb077c826 100644 --- a/UI/GPUDriverTestScreen.h +++ b/UI/GPUDriverTestScreen.h @@ -20,6 +20,13 @@ public: private: void DiscardTest(); + void ShaderTest(); + + // Common objects + Draw::SamplerState *samplerNearest_ = nullptr; + + // Discard/depth/stencil stuff + // =========================== Draw::ShaderModule *discardFragShader_ = nullptr; Draw::Pipeline *discardWriteDepthStencil_ = nullptr; @@ -38,6 +45,16 @@ private: Draw::Pipeline *drawTestDepthLessEqual_ = nullptr; Draw::Pipeline *drawTestDepthGreater_ = nullptr; - Draw::SamplerState *samplerNearest_ = nullptr; + + // Shader tests + // ============ + + Draw::Pipeline *adrenoLogicDiscardPipeline_ = nullptr; + Draw::ShaderModule *adrenoLogicDiscardFragShader_ = nullptr; + Draw::ShaderModule *adrenoLogicDiscardVertShader_ = nullptr; + Draw::Pipeline *flatShadingPipeline_ = nullptr; + Draw::ShaderModule *flatFragShader_ = nullptr; + Draw::ShaderModule *flatVertShader_ = nullptr; + UI::TabHolder *tabHolder_ = nullptr; }; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 4639eb22f1..d80926bcb3 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -796,7 +796,7 @@ void GameSettingsScreen::CreateViews() { networkingSettingsScroll->Add(networkingSettings); tabHolder->AddTab(ms->T("Networking"), networkingSettingsScroll); - networkingSettings->Add(new ItemHeader(n->T("Networking"))); + networkingSettings->Add(new ItemHeader(ms->T("Networking"))); networkingSettings->Add(new Choice(n->T("Adhoc Multiplayer forum")))->OnClick.Handle(this, &GameSettingsScreen::OnAdhocGuides); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 17f0afd652..308b0daf35 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -15,8 +15,9 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include #include +#include +#include #include "ppsspp_config.h" @@ -33,6 +34,7 @@ #include "Common/Data/Encoding/Utf8.h" #include "Common/File/PathBrowser.h" #include "Common/Math/curves.h" +#include "Common/Net/URL.h" #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" @@ -63,13 +65,6 @@ #include "Core/HLE/sceDisplay.h" #include "Core/HLE/sceUmd.h" -#ifdef _WIN32 -// Unfortunate, for undef DrawText... -#include "Common/CommonWindows.h" -#endif - -#include - bool MainScreen::showHomebrewTab = false; bool LaunchFile(ScreenManager *screenManager, std::string path) { @@ -96,8 +91,13 @@ bool LaunchFile(ScreenManager *screenManager, std::string path) { static bool IsTempPath(const std::string &str) { std::string item = str; +#ifdef _WIN32 + // Normalize slashes. + item = ReplaceAll(str, "/", "\\"); +#endif - const auto testPath = [&](std::string temp) { + std::vector tempPaths = System_GetPropertyStringVec(SYSPROP_TEMP_DIRS); + for (auto temp : tempPaths) { #ifdef _WIN32 temp = ReplaceAll(temp, "/", "\\"); if (!temp.empty() && temp[temp.size() - 1] != '\\') @@ -106,37 +106,9 @@ static bool IsTempPath(const std::string &str) { if (!temp.empty() && temp[temp.size() - 1] != '/') temp += "/"; #endif - return startsWith(item, temp); - }; - - const auto testCPath = [&](const char *temp) { - if (temp && temp[0]) - return testPath(temp); - return false; - }; - -#ifdef _WIN32 - // Normalize slashes. - item = ReplaceAll(str, "/", "\\"); - - std::wstring tempPath(MAX_PATH, '\0'); - size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); - if (sz >= tempPath.size()) { - tempPath.resize(sz); - sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + if (startsWith(item, temp)) + return true; } - // Need to resize off the null terminator either way. - tempPath.resize(sz); - if (testPath(ConvertWStringToUTF8(tempPath))) - return true; -#endif - - if (testCPath(getenv("TMPDIR"))) - return true; - if (testCPath(getenv("TMP"))) - return true; - if (testCPath(getenv("TEMP"))) - return true; return false; } @@ -540,13 +512,29 @@ UI::EventReturn GameBrowser::LastClick(UI::EventParams &e) { return UI::EVENT_DONE; } -UI::EventReturn GameBrowser::HomeClick(UI::EventParams &e) { -#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) || defined(USING_QT_UI) || defined(USING_WIN_UI) || PPSSPP_PLATFORM(UWP) - if (System_GetPropertyBool(SYSPROP_HAS_FOLDER_BROWSER)) { - System_SendMessage("browse_folder", ""); - } else { - SetPath(g_Config.memStickDirectory); +UI::EventReturn GameBrowser::BrowseClick(UI::EventParams &e) { + System_SendMessage("browse_folder", ""); + return UI::EVENT_DONE; +} + +UI::EventReturn GameBrowser::StorageClick(UI::EventParams &e) { + std::vector storageDirs = System_GetPropertyStringVec(SYSPROP_ADDITIONAL_STORAGE_DIRS); + if (storageDirs.empty()) { + // Shouldn't happen - this button shouldn't be clickable. + return UI::EVENT_DONE; } + if (storageDirs.size() == 1) { + SetPath(storageDirs[0]); + } else { + // TODO: We should popup a dialog letting the user choose one. + SetPath(storageDirs[0]); + } + return UI::EVENT_DONE; +} + +UI::EventReturn GameBrowser::HomeClick(UI::EventParams &e) { +#if PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(SWITCH) || defined(USING_WIN_UI) || PPSSPP_PLATFORM(UWP) + SetPath(g_Config.memStickDirectory); #else SetPath(getenv("HOME")); #endif @@ -656,10 +644,12 @@ void GameBrowser::Refresh() { if (browseFlags_ & BrowseFlags::NAVIGATE) { topBar->Add(new Spacer(2.0f)); topBar->Add(new TextView(path_.GetFriendlyPath().c_str(), ALIGN_VCENTER | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); + topBar->Add(new Choice(ImageID("I_HOME"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); + if (System_GetPropertyBool(SYSPROP_HAS_ADDITIONAL_STORAGE)) { + topBar->Add(new Choice(ImageID("I_SDCARD"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::StorageClick); + } if (System_GetPropertyBool(SYSPROP_HAS_FOLDER_BROWSER)) { - topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); - } else { - topBar->Add(new Choice(mm->T("Home"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); + topBar->Add(new Choice(mm->T("Browse", "Browse..."), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::BrowseClick); } } else { topBar->Add(new Spacer(new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); @@ -762,6 +752,7 @@ void GameBrowser::Refresh() { gameList_->Add(new DirButton("..", *gridStyle_, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::FILL_PARENT)))-> OnClick.Handle(this, &GameBrowser::NavigateClick); } + // Add any pinned paths before other directories. auto pinnedPaths = GetPinnedPaths(); for (auto it = pinnedPaths.begin(), end = pinnedPaths.end(); it != end; ++it) { @@ -1073,16 +1064,26 @@ void MainScreen::CreateViews() { //sprintf(versionString, "%s", PPSSPP_GIT_VERSION); sprintf(versionString, "Luna Edition"); rightColumnItems->SetSpacing(0.0f); - LinearLayout *logos = new LinearLayout(ORIENT_HORIZONTAL); - logos->Add(new ImageView(ImageID("I_ICON"), IS_DEFAULT, new AnchorLayoutParams(64, 64, 10, 10, NONE, NONE, false))); - logos->Add(new ImageView(ImageID("I_LOGO"), IS_DEFAULT, new LinearLayoutParams(Margins(-12, 0, 0, 0)))); + AnchorLayout *logos = new AnchorLayout(new AnchorLayoutParams(FILL_PARENT, 60.0f, false)); + + logos->Add(new ImageView(ImageID("I_ICON"), IS_DEFAULT, new AnchorLayoutParams(64, 64, 0, 0, NONE, NONE, false))); + logos->Add(new ImageView(ImageID("I_LOGO"), IS_DEFAULT, new AnchorLayoutParams(180, 64, 64, -5.0f, NONE, NONE, false))); + +#if !defined(MOBILE_DEVICE) + if (!g_Config.bFullScreen) { + fullscreenButton_ = logos->Add(new Button(ImageID(g_Config.bFullScreen ? "I_RESTORE" : "I_FULLSCREEN"), new AnchorLayoutParams(48, 48, NONE, 0, 0, NONE, false))); + fullscreenButton_->OnClick.Handle(this, &MainScreen::OnFullScreenToggle); + } +#endif + rightColumnItems->Add(logos); TextView *ver = rightColumnItems->Add(new TextView(versionString, new LinearLayoutParams(Margins(70, -6, 0, 0)))); ver->SetSmall(true); ver->SetClip(false); -#if defined(USING_WIN_UI) || defined(USING_QT_UI) || PPSSPP_PLATFORM(UWP) - rightColumnItems->Add(new Choice(mm->T("Load","Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); -#endif + + if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { + rightColumnItems->Add(new Choice(mm->T("Load", "Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); + } rightColumnItems->Add(new Choice(mm->T("Game Settings", "Settings")))->OnClick.Handle(this, &MainScreen::OnGameSettings); rightColumnItems->Add(new Choice(mm->T("Credits")))->OnClick.Handle(this, &MainScreen::OnCredits); if (!g_Config.bSimpleUIhide) @@ -1180,9 +1181,38 @@ void MainScreen::sendMessage(const char *message, const char *value) { LaunchFile(screenManager(), std::string(value)); } if (!strcmp(message, "browse_folderSelect")) { + std::string filename; +#if PPSSPP_PLATFORM(ANDROID) + // Hacky way to get a normal path from a Android Storage Framework path. + // Is not gonna work forever, but ship-hack for 1.11. + std::string url = value; + const char *prefix = "content://com.android.externalstorage.documents/tree/"; + const char *primaryPrefix = "/storage/primary/"; + if (startsWith(url, prefix)) { + url = UriDecode(url.substr(strlen(prefix))); + size_t colonPos = url.find(":"); + if (colonPos != std::string::npos) { + url[colonPos] = '/'; + } + url = "/storage/" + url; + if (startsWith(url, primaryPrefix)) { + url = g_Config.memStickDirectory + url.substr(strlen(primaryPrefix)); + } + INFO_LOG(SYSTEM, "Translated '%s' into '%s'", value, url.c_str()); + } else { + // It's not gonna work. + // TODO: Show an error message? + INFO_LOG(SYSTEM, "Failed to parse content string: '%s'", value); + return; + } + filename = url; +#else + filename = value; +#endif + INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str()); int tab = tabHolder_->GetCurrentTab(); if (tab >= 0 && tab < (int)gameBrowsers_.size()) { - gameBrowsers_[tab]->SetPath(value); + gameBrowsers_[tab]->SetPath(filename); } } } @@ -1215,6 +1245,17 @@ UI::EventReturn MainScreen::OnLoadFile(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn MainScreen::OnFullScreenToggle(UI::EventParams &e) { + if (fullscreenButton_) { + fullscreenButton_->SetImageID(ImageID(!g_Config.bFullScreen ? "I_RESTORE" : "I_FULLSCREEN")); + } +#if !defined(MOBILE_DEVICE) + g_Config.bFullScreen = !g_Config.bFullScreen; + System_SendMessage("toggle_fullscreen", ""); +#endif + return UI::EVENT_DONE; +} + void MainScreen::DrawBackground(UIContext &dc) { UIScreenWithBackground::DrawBackground(dc); if (highlightedGamePath_.empty() && prevHighlightedGamePath_.empty()) { @@ -1270,6 +1311,7 @@ bool MainScreen::DrawBackgroundFor(UIContext &dc, const std::string &gamePath, f } UI::EventReturn MainScreen::OnGameSelected(UI::EventParams &e) { + g_Config.Save("MainScreen::OnGameSelected"); #ifdef _WIN32 std::string path = ReplaceAll(e.s, "\\", "/"); #else @@ -1324,6 +1366,7 @@ UI::EventReturn MainScreen::OnGameHighlight(UI::EventParams &e) { } UI::EventReturn MainScreen::OnGameSelectedInstant(UI::EventParams &e) { + g_Config.Save("MainScreen::OnGameSelectedInstant"); #ifdef _WIN32 std::string path = ReplaceAll(e.s, "\\", "/"); #else diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 080aebc2e4..b8a396d157 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -68,6 +68,8 @@ private: UI::EventReturn NavigateClick(UI::EventParams &e); UI::EventReturn LayoutChange(UI::EventParams &e); UI::EventReturn LastClick(UI::EventParams &e); + UI::EventReturn BrowseClick(UI::EventParams &e); + UI::EventReturn StorageClick(UI::EventParams &e); UI::EventReturn HomeClick(UI::EventParams &e); UI::EventReturn PinToggleClick(UI::EventParams &e); UI::EventReturn GridSettingsClick(UI::EventParams &e); @@ -124,9 +126,11 @@ protected: UI::EventReturn OnDownloadUpgrade(UI::EventParams &e); UI::EventReturn OnDismissUpgrade(UI::EventParams &e); UI::EventReturn OnAllowStorage(UI::EventParams &e); + UI::EventReturn OnFullScreenToggle(UI::EventParams &e); UI::LinearLayout *upgradeBar_ = nullptr; UI::TabHolder *tabHolder_ = nullptr; + UI::Button *fullscreenButton_ = nullptr; std::string restoreFocusGamePath_; std::vector gameBrowsers_; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 79e64fe73d..489c732f76 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -75,6 +75,7 @@ #include "Common/MemArena.h" #include "Common/GraphicsContext.h" #include "Common/OSVersion.h" +#include "Common/GPU/ShaderTranslation.h" #include "Core/Config.h" #include "Core/ConfigValues.h" @@ -438,6 +439,8 @@ static void ClearFailedGPUBackends() { void NativeInit(int argc, const char *argv[], const char *savegame_dir, const char *external_dir, const char *cache_dir) { net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times. + ShaderTranslationInit(); + InitFastMath(cpu_info.bNEON); SetupAudioFormats(); @@ -1404,6 +1407,8 @@ void NativeShutdown() { g_Discord.Shutdown(); + ShaderTranslationShutdown(); + // Avoid shutting this down when restarting core. if (!restarting) LogManager::Shutdown(); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 2e46086a0b..8b40a36f2e 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -399,6 +399,8 @@ + + @@ -629,6 +631,8 @@ + + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index ea66a38598..c688cd49f5 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -682,6 +682,12 @@ Debugger\WebSocket + + Debugger\WebSocket + + + Debugger\WebSocket + Debugger\WebSocket @@ -1488,6 +1494,12 @@ Debugger\WebSocket + + Debugger\WebSocket + + + Debugger\WebSocket + Debugger\WebSocket diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index 4248c5fa2b..0ba7fac4b6 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -356,6 +356,35 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + std::vector result; + switch (prop) { + case SYSPROP_TEMP_DIRS: + { + std::wstring tempPath(MAX_PATH, '\0'); + size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + if (sz >= tempPath.size()) { + tempPath.resize(sz); + sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + } + // Need to resize off the null terminator either way. + tempPath.resize(sz); + result.push_back(ConvertWStringToUTF8(tempPath)); + + if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) + result.push_back(getenv("TMPDIR")); + if (getenv("TMP") && strlen(getenv("TMP")) != 0) + result.push_back(getenv("TMP")); + if (getenv("TEMP") && strlen(getenv("TEMP")) != 0) + result.push_back(getenv("TEMP")); + return result; + } + + default: + return result; + } +} + int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: @@ -440,11 +469,16 @@ void System_SendMessage(const char *command, const char *parameter) { }); } else if (!strcmp(command, "toggle_fullscreen")) { auto view = Windows::UI::ViewManagement::ApplicationView::GetForCurrentView(); + bool flag = !view->IsFullScreenMode; if (strcmp(parameter, "0") == 0) { - view->ExitFullScreenMode(); + flag = false; + } else if (strcmp(parameter, "1") == 0){ + flag = true; } - else if (strcmp(parameter, "1") == 0){ + if (flag) { view->TryEnterFullScreenMode(); + } else { + view->ExitFullScreenMode(); } } } diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 36a579a305..00b8db86f4 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -132,6 +132,7 @@ namespace MainWindow static bool inFullscreenResize = false; static bool inResizeMove = false; static bool hasFocus = true; + static bool g_isFullscreen = false; // gross hack bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus @@ -355,6 +356,7 @@ namespace MainWindow ::SetMenu(hWnd, goingFullscreen ? NULL : menu); g_Config.bFullScreen = goingFullscreen; + g_isFullscreen = goingFullscreen; g_IgnoreWM_SIZE = false; @@ -1069,4 +1071,8 @@ namespace MainWindow PostMessage(hwndMain, WM_USER_TOGGLE_FULLSCREEN, fullscreen, 0); } + bool IsFullscreen() { + return g_isFullscreen; + } + } // namespace diff --git a/Windows/MainWindow.h b/Windows/MainWindow.h index c72ae17917..0a35aebdf1 100644 --- a/Windows/MainWindow.h +++ b/Windows/MainWindow.h @@ -77,6 +77,7 @@ namespace MainWindow void ToggleFullscreen(HWND hWnd, bool goingFullscreen); void Minimize(); void SendToggleFullscreen(bool fullscreen); // To be used off-thread + bool IsFullscreen(); void ToggleDebugConsoleVisibility(); void SetInternalResolution(int res = -1); void SetWindowSize(int zoom); diff --git a/Windows/main.cpp b/Windows/main.cpp index 1153376021..4d948cfe5b 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -216,6 +216,35 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + std::vector result; + switch (prop) { + case SYSPROP_TEMP_DIRS: + { + std::wstring tempPath(MAX_PATH, '\0'); + size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + if (sz >= tempPath.size()) { + tempPath.resize(sz); + sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]); + } + // Need to resize off the null terminator either way. + tempPath.resize(sz); + result.push_back(ConvertWStringToUTF8(tempPath)); + + if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0) + result.push_back(getenv("TMPDIR")); + if (getenv("TMP") && strlen(getenv("TMP")) != 0) + result.push_back(getenv("TMP")); + if (getenv("TEMP") && strlen(getenv("TEMP")) != 0) + result.push_back(getenv("TEMP")); + return result; + } + + default: + return result; + } +} + // Ugly! extern WindowsAudioBackend *winAudioBackend; @@ -323,7 +352,7 @@ void System_SendMessage(const char *command, const char *parameter) { } else if (!strcmp(command, "bgImage_browse")) { MainWindow::BrowseBackground(); } else if (!strcmp(command, "toggle_fullscreen")) { - bool flag = !g_Config.bFullScreen; + bool flag = !MainWindow::IsFullscreen(); if (strcmp(parameter, "0") == 0) { flag = false; } else if (strcmp(parameter, "1") == 0) { diff --git a/android/assets/ui_atlas_luna.meta b/android/assets/ui_atlas_luna.meta index ad98a26b10..7b14439328 100644 Binary files a/android/assets/ui_atlas_luna.meta and b/android/assets/ui_atlas_luna.meta differ diff --git a/android/assets/ui_atlas_luna.zim b/android/assets/ui_atlas_luna.zim index b8c156fade..ec911c2bed 100644 Binary files a/android/assets/ui_atlas_luna.zim and b/android/assets/ui_atlas_luna.zim differ diff --git a/android/jni/Android.mk b/android/jni/Android.mk index c527ea2524..e3e9ba19ff 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -17,6 +17,7 @@ ARCH_FILES := \ $(SRC)/Common/x64Emitter.cpp \ $(SRC)/Common/x64Analyzer.cpp \ $(SRC)/Common/CPUDetect.cpp \ + $(SRC)/Common/Math/fast/fast_matrix_sse.c \ $(SRC)/Common/Thunk.cpp \ $(SRC)/Core/MIPS/x86/CompALU.cpp \ $(SRC)/Core/MIPS/x86/CompBranch.cpp \ @@ -39,6 +40,7 @@ ARCH_FILES := \ $(SRC)/Common/x64Emitter.cpp \ $(SRC)/Common/x64Analyzer.cpp \ $(SRC)/Common/CPUDetect.cpp \ + $(SRC)/Common/Math/fast/fast_matrix_sse.c \ $(SRC)/Common/Thunk.cpp \ $(SRC)/Core/MIPS/x86/CompALU.cpp \ $(SRC)/Core/MIPS/x86/CompBranch.cpp \ @@ -62,6 +64,7 @@ ARCH_FILES := \ $(SRC)/Common/ArmEmitter.cpp \ $(SRC)/Common/ArmCPUDetect.cpp \ $(SRC)/Common/ColorConvNEON.cpp.neon \ + $(SRC)/Common/Math/fast/fast_matrix_neon.S.neon \ $(SRC)/Core/MIPS/ARM/ArmCompALU.cpp \ $(SRC)/Core/MIPS/ARM/ArmCompBranch.cpp \ $(SRC)/Core/MIPS/ARM/ArmCompFPU.cpp \ @@ -76,6 +79,9 @@ ARCH_FILES := \ $(SRC)/Core/MIPS/ARM/ArmRegCacheFPU.cpp \ $(SRC)/GPU/Common/VertexDecoderArm.cpp \ $(SRC)/ext/disarm.cpp \ + $(SRC)/ext/libpng17/arm/arm_init.c \ + $(SRC)/ext/libpng17/arm/filter_neon_intrinsics.c \ + $(SRC)/ext/libpng17/arm/filter_neon.S.neon \ ArmEmitterTest.cpp endif @@ -407,6 +413,8 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GPURecordSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/InputBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/InputSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/MemorySubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ @@ -620,18 +628,6 @@ LOCAL_SRC_FILES := \ ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a) LOCAL_CFLAGS := $(LOCAL_CFLAGS) -DARM -LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ - $(SRC)/Common/Math/fast/fast_matrix_neon.S.neon \ - $(SRC)/ext/libpng17/arm/arm_init.c \ - $(SRC)/ext/libpng17/arm/filter_neon_intrinsics.c \ - $(SRC)/ext/libpng17/arm/filter_neon.S.neon - -else ifeq ($(TARGET_ARCH_ABI),x86) -LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ - $(SRC)/Common/Math/fast/fast_matrix_sse.c -else ifeq ($(TARGET_ARCH_ABI),x86_64) -LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ - $(SRC)/Common/Math/fast/fast_matrix_sse.c endif ifneq ($(SKIPAPP),1) @@ -684,6 +680,7 @@ ifeq ($(UNITTEST),1) LOCAL_MODULE := ppsspp_unittest LOCAL_SRC_FILES := \ $(SRC)/unittest/JitHarness.cpp \ + $(SRC)/unittest/TestShaderGenerators.cpp \ $(SRC)/unittest/TestVertexJit.cpp \ $(TESTARMEMITTER_FILE) \ $(SRC)/unittest/UnitTest.cpp diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 82a69e8c27..a5a1724325 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -13,6 +13,8 @@ #include #include +#include + #ifndef _MSC_VER #include #include @@ -122,6 +124,8 @@ std::string langRegion; std::string mogaVersion; std::string boardName; +std::vector g_additionalStorageDirs; + static float left_joystick_x_async; static float left_joystick_y_async; static float right_joystick_x_async; @@ -364,6 +368,17 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + switch (prop) { + case SYSPROP_ADDITIONAL_STORAGE_DIRS: + return g_additionalStorageDirs; + + case SYSPROP_TEMP_DIRS: + default: + return std::vector(); + } +} + int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_SYSTEMVERSION: @@ -410,10 +425,17 @@ bool System_GetPropertyBool(SystemProperty prop) { return androidVersion >= 23; // 6.0 Marshmallow introduced run time permissions. case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE: return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature. + case SYSPROP_HAS_ADDITIONAL_STORAGE: + return !g_additionalStorageDirs.empty(); case SYSPROP_HAS_BACK_BUTTON: return true; case SYSPROP_HAS_IMAGE_BROWSER: return true; + case SYSPROP_HAS_FILE_BROWSER: + return false; // We kind of have but needs more work. + case SYSPROP_HAS_FOLDER_BROWSER: + // Uses OPEN_DOCUMENT_TREE to let you select a folder. + return androidVersion >= 21; case SYSPROP_APP_GOLD: #ifdef GOLD return true; @@ -530,16 +552,19 @@ static void parse_args(std::vector &args, const std::string value) } } +// Need to use raw Android logging before NativeInit. +#define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__) + extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, - jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, + jstring jdataDir, jstring jexternalStorageDir, jstring jadditionalStorageDirs, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, jint jAndroidVersion, jstring jboard) { setCurrentThreadName("androidInit"); // Makes sure we get early permission grants. ProcessFrameCommands(env); - INFO_LOG(SYSTEM, "NativeApp.init() -- begin"); + EARLY_LOG("NativeApp.init() -- begin"); PROFILE_INIT(); renderer_inited = false; @@ -559,9 +584,18 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init systemName = GetJavaString(env, jmodel); langRegion = GetJavaString(env, jlangRegion); - INFO_LOG(SYSTEM, "NativeApp.init(): device name: '%s'", systemName.c_str()); + EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str()); + + std::string externalStorageDir = GetJavaString(env, jexternalStorageDir); + std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs); + + if (!additionalStorageDirsString.empty()) { + SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs); + for (auto &str : g_additionalStorageDirs) { + EARLY_LOG("Additional storage: %s", str.c_str()); + } + } - std::string externalDir = GetJavaString(env, jexternalDir); std::string user_data_path = GetJavaString(env, jdataDir); if (user_data_path.size() > 0) user_data_path += "/"; @@ -570,8 +604,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init std::string cacheDir = GetJavaString(env, jcacheDir); std::string buildBoard = GetJavaString(env, jboard); boardName = buildBoard; - INFO_LOG(SYSTEM, "NativeApp.init(): External storage path: %s", externalDir.c_str()); - INFO_LOG(SYSTEM, "NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str()); + EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str()); + EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str()); std::string app_name; std::string app_nice_name; @@ -600,7 +634,9 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init } } - NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); + NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str()); + + // No need to use EARLY_LOG anymore. retry: // Now that we've loaded config, set javaGL. diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 54ae0f04aa..598bd3849f 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -25,6 +25,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.PowerManager; import android.os.Vibrator; +import android.provider.DocumentsContract; import android.provider.MediaStore; import android.text.InputType; import android.util.Log; @@ -46,6 +47,7 @@ import android.widget.Toast; import java.io.File; import java.lang.reflect.Field; import java.util.List; +import java.util.ArrayList; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -98,7 +100,10 @@ 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 final int RESULT_LOAD_IMAGE = 1; + private static final int RESULT_BROWSE_FILE = 2; + private static final int RESULT_OPEN_DOCUMENT_TREE = 3; // Allow for multiple connected gamepads but just consider them the same for now. // Actually this is not entirely true, see the code. @@ -241,6 +246,108 @@ public abstract class NativeActivity extends Activity { this.shortcutParam = ((shortcutParam == null) ? "" : shortcutParam); } + // Unofficial hacks to get a list of SD cards that are not the main "external storage". + private static ArrayList getSdCardPaths(final Context context) { + // Q is the last version that will support normal file access. + ArrayList list = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { + Log.i(TAG, "getSdCardPaths: Trying KitKat method"); + list = getSdCardPaths19(context); + } + + if (list == null) { + Log.i(TAG, "getSdCardPaths: Attempting fallback"); + // Try another method. + String removableStoragePath; + list = new ArrayList(); + File fileList[] = new File("/storage/").listFiles(); + if (fileList != null) { + for (File file : fileList) { + if (!file.getAbsolutePath().equalsIgnoreCase(Environment.getExternalStorageDirectory().getAbsolutePath()) && file.isDirectory() && file.canRead()) { + list.add(file.getAbsolutePath()); + } + } + } + } + + if (list == null) { + String[] varNames = { "EXTERNAL_SDCARD_STORAGE", "SECONDARY_STORAGE" }; + for (String var : varNames) { + Log.i(TAG, "getSdCardPaths: Checking env " + var); + String secStore = System.getenv("SECONDARY_STORAGE"); + if (secStore != null && secStore.length() > 0) { + list = new ArrayList(); + list.add(secStore); + break; + } + } + } + + if (list == null) { + return new ArrayList(); + } else { + return list; + } + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static ArrayList getSdCardPaths19(final Context context) + { + final File[] externalCacheDirs = context.getExternalCacheDirs(); + if (externalCacheDirs == null || externalCacheDirs.length==0) + return null; + if (externalCacheDirs.length == 1) { + if (externalCacheDirs[0] == null) + return null; + final String storageState = Environment.getStorageState(externalCacheDirs[0]); + if (!Environment.MEDIA_MOUNTED.equals(storageState)) + return null; + if (Environment.isExternalStorageEmulated()) + return null; + } + final ArrayList result = new ArrayList<>(); + if (externalCacheDirs.length == 1) + result.add(getRootOfInnerSdCardFolder(externalCacheDirs[0])); + for (int i = 1; i < externalCacheDirs.length; ++i) + { + final File file = externalCacheDirs[i]; + if (file == null) + continue; + final String storageState = Environment.getStorageState(file); + if (Environment.MEDIA_MOUNTED.equals(storageState)) { + String root = getRootOfInnerSdCardFolder(externalCacheDirs[i]); + if (root != null) { + result.add(root); + } + } + } + if (result.isEmpty()) + return null; + return result; + } + + /** Given any file/folder inside an sd card, this will return the path of the sd card */ + private static String getRootOfInnerSdCardFolder(File file) + { + if (file == null) + return null; + final long totalSpace = file.getTotalSpace(); + if (totalSpace <= 0) { + return null; + } + while (true) { + final File parentFile = file.getParentFile(); + if (parentFile == null || !parentFile.canRead()) { + break; + } + if (parentFile.getTotalSpace() != totalSpace) { + break; + } + file = parentFile; + } + return file.getAbsolutePath(); + } + public void Initialize() { // Initialize audio classes. Do this here since detectOptimalAudioSettings() // needs audioManager @@ -292,9 +399,34 @@ public abstract class NativeActivity extends Activity { isXperiaPlay = IsXperiaPlay(); String libraryDir = getApplicationLibraryDir(appInfo); - File sdcard = Environment.getExternalStorageDirectory(); - String externalStorageDir = sdcard.getAbsolutePath(); + String extStorageState = Environment.getExternalStorageState(); + String extStorageDir = Environment.getExternalStorageDirectory().getAbsolutePath(); + + Log.i(TAG, "Ext storage: " + extStorageState + " " + extStorageDir); + + String additionalStorageDirs = ""; + try { + ArrayList sdCards = getSdCardPaths(this); + + // String.join doesn't exist on old devices (???). + StringBuilder s = new StringBuilder(); + for (int i = 0; i < sdCards.size(); i++) { + String sdCard = sdCards.get(i); + Log.i(TAG, "SD card: " + sdCard); + s.append(sdCard); + if (i != sdCards.size() - 1) { + s.append(":"); + } + } + additionalStorageDirs = s.toString(); + } + catch (Exception e) { + Log.e(TAG, "Failed to get SD storage dirs: " + e.toString()); + } + + Log.i(TAG, "End of storage paths"); + File filesDir = this.getFilesDir(); String dataDir = null; if (filesDir != null) { @@ -310,7 +442,7 @@ public abstract class NativeActivity extends Activity { overrideShortcutParam = null; NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate); - NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcut, Build.VERSION.SDK_INT, Build.BOARD); + NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, extStorageDir, additionalStorageDirs, libraryDir, cacheDir, shortcut, Build.VERSION.SDK_INT, Build.BOARD); // Allow C++ to tell us to use JavaGL or not. javaGL = "true".equalsIgnoreCase(NativeApp.queryConfig("androidJavaGL")); @@ -993,7 +1125,10 @@ public abstract class NativeActivity extends Activity { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { + if (resultCode != RESULT_OK || data == null) { + return; + } + if (requestCode == RESULT_LOAD_IMAGE) { Uri selectedImage = data.getData(); if (selectedImage != null) { String[] filePathColumn = {MediaStore.Images.Media.DATA}; @@ -1004,6 +1139,20 @@ public abstract class NativeActivity extends Activity { cursor.close(); NativeApp.sendMessage("bgImage_updated", picturePath); } + } else if (requestCode == RESULT_BROWSE_FILE) { + Uri selectedFile = data.getData(); + if (selectedFile != null) { + // NativeApp.sendMessage("br"); + Log.i(TAG, "Browse file finished:" + selectedFile.toString()); + } + } else if (requestCode == RESULT_OPEN_DOCUMENT_TREE) { + Uri selectedFile = data.getData(); + if (selectedFile != null) { + // Convert URI to normal path. (This might not be possible in Android 12+) + String path = selectedFile.toString(); + Log.i(TAG, "Browse folder finished: " + path); + NativeApp.sendMessage("browse_folderSelect", path); + } } } @@ -1135,6 +1284,30 @@ public abstract class NativeActivity extends Activity { Log.e(TAG, e.toString()); return false; } + } else if (command.equals("browse_file")) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + //intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); + startActivityForResult(intent, RESULT_BROWSE_FILE); + } catch (Exception e) { + Log.e(TAG, e.toString()); + return false; + } + } else if (command.equals("browse_folder")) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // not yet used properly + intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); // Only allow local folders. + startActivityForResult(intent, RESULT_OPEN_DOCUMENT_TREE); + return true; + } catch (Exception e) { + Log.e(TAG, e.toString()); + return false; + } } else if (command.equals("sharejpeg")) { try { Intent share = new Intent(Intent.ACTION_SEND); diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index 632c625bed..4a72d4c6a9 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -12,7 +12,7 @@ public class NativeApp { public static final int DEVICE_TYPE_TV = 1; public static final int DEVICE_TYPE_DESKTOP = 2; - public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, String board); + public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalStorageDir, String additionalStorageDirs, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, String board); public static native void audioInit(); public static native void audioShutdown(); public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate); diff --git a/assets/compat.ini b/assets/compat.ini index 256f188050..0b5286fad5 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -520,12 +520,18 @@ ULUS10216 = true ULES00629 = true # Digimon Adventure NPJH50686 = true +ULJS00541 = true +ULAS42340 = true # Danganronpa +ULJS00337 = true +ULJS19060 = true NPJH50372 = true NPJH50515 = true # Danganronpa Demo NPJH90164 = true # Super Danganronpa 2 +ULJS00521 = true +ULJS00522 = true NPJH50631 = true # The Simpsons Game ULUS10295 = true @@ -537,19 +543,46 @@ ULES00976 = true # Jeanne d'Arc UCUS98700 = true UCJS10048 = true -# Jeanne d'Arc Senkou Taikenban Demo +# Jeanne d'Arc Senkou Taikenban (Demo) UCJX90019 = true +# Dante's Inferno +ULUS10469 = true +ULES01384 = true +ULJM05621 = true +NPJH50220 = true +ULJM05799 = true +ULKS46248 = true +# Test Drive Unlimited +ULUS10249 = true +ULES00637 = true +ULKS46126 = true +# Silent Hill: Shattered Memories +ULUS10450 = true +ULES01352 = true +ULJM05564 = true +NPJH50148 = true +ULAS42189 = true +# Silent Hill: Origins +ULUS10285 = true +ULES00869 = true +ULJM05281 = true +NPJH50051 = true +# Manhunt 2 +ULUS10280 = true +ULES00756 = true +# LEGO Star Wars II: The Original Trilogy +ULUS10155 = true +ULES00479 = true +# LEGO Indiana Jones: The Original Adventures +ULUS10365 = true +ULES01086 = true +# LEGO Batman: The Videogame +ULUS10380 = true +ULES01151 = true # TODO: There are many more. [RequireBlockTransfer] # Warn the user that the game will have issue graphic, if simulate block transfer is not enabled. -# Ys Seven need it to fix minimap. See issues #2928 -ULUS10551 = true -ULJM05475 = true -NPEH00065 = true -NPJH50350 = true -ULJM08041 = true -NPEH00065 = true # The Legend of Heroes: Trails in the Sky need it to fix graphical glitch in menu screen. See issues #8053 ULUS10540 = true ULUS10578 = true @@ -563,17 +596,10 @@ NPUH10197 = true ULJS00394 = true ULJS19068 = true NPJH50518 = true -# TODO: Will add some games in the future -NPJH50686 = true # Digimon Adventures (JP, and English patches...) -ULJS00078 = true # MotoGP -ULUS10153 = true # MotoGP -UCES00373 = true # MotoGP -ULUS10551 = true # Ys Seven -ULJM05475 = true # Ys Seven JP -ULUS10549 = true # Ys Seven -NPEH00065 = true # Ys Seven -NPJH50350 = true # Ys Seven -ULJM08041 = true # Ys Seven +# Gradius Collection (see #13887) +ULUS10103 = true +ULES00381 = true +ULJM05091 = true [DisableAccurateDepth] # Midnight Club: LA Remix @@ -627,8 +653,13 @@ NPJG90095 = true NPEG90035 = true NPUG70125 = true # Tekken 6 -ULES01376 = true ULUS10466 = true +ULES01376 = true +ULJS00224 = true +NPUH10047 = true +ULAS42214 = true +ULJS19054 = true +NPJH50184 = true [MGS2AcidHack] ULUS10006 = true # Metal Gear Acid @@ -686,16 +717,23 @@ NPJH90157 = true # Infinity demo [BlockTransferAllowCreateFB] -NPJH50686 = true # Digimon Adventures (JP, and English patches...) -ULJS00078 = true # MotoGP -ULUS10153 = true # MotoGP -UCES00373 = true # MotoGP -ULUS10551 = true # Ys Seven -ULJM05475 = true # Ys Seven JP -ULUS10549 = true # Ys Seven -NPEH00065 = true # Ys Seven -NPJH50350 = true # Ys Seven -ULJM08041 = true # Ys Seven +# Digimon Adventure (JP and English patches) +NPJH50686 = true +ULJS00541 = true +ULAS42340 = true + +# MotoGP +ULUS10153 = true +UCES00373 = true +ULJS00078 = true +UCAS40104 = true + +# Ys Seven +ULUS10551 = true +ULJM05475 = true +ULJM05475 = true +NPEH00065 = true +NPJH50350 = true # Burnout Legends ULES00125 = true @@ -723,11 +761,30 @@ ULKS46126 = true # Naruto Shippuden: Ultimate Ninja Impact ULUS10582 = true ULES01537 = true +ULJS00390 = true +ULAS42297 = true +ULJS19071 = true NPJH50435 = true +NPJH50435 = true + +# Gradius Collection +ULUS10103 = true +ULES00381 = true +ULJM05091 = true +ULJM05222 = true +ULAS42040 = true +ULKS46064 = true [IntraVRAMBlockTransferAllowCreateFB] # Final Fantasy - Type 0 +ULJM05900 = true +ULJM05901 = true NPJH50443 = true +NPJH50444 = true +ULJM06217 = true +ULJM06218 = true +UCAS40346 = true +UCAS40347 = true # Note! This whole flag is disabled temporarily by appending "Disabled" to its name). See 7914 [YugiohSaveFixDisabled] @@ -848,8 +905,13 @@ ULES01287 = true ULUS10433 = true # Tekken 6 -ULES01376 = true ULUS10466 = true +ULES01376 = true +ULJS00224 = true +NPUH10047 = true +ULAS42214 = true +ULJS19054 = true +NPJH50184 = true # Hot Wheels - Ultimate Racing (issue #12604, this is a workaround) ULUS10239 = true @@ -868,7 +930,6 @@ ULUS10287 = true # Super Collapse 3 ULES01027 = true # Super Collapse 3 ULUS10203 = true # Platypus ULES01029 = true # Platypus -ULUS10201 = true # Luxor: The Wrath of Set ULUS10227 = true # 7 Wonders of the Ancient World ULES01037 = true # 7 Wonders of the Ancient World @@ -879,6 +940,18 @@ ULUS10132 = true # Homebrew Kurok (we don't normally add homebrew here but it has a distinctive GameID) KURO00767 = true +# Scooby-Doo! Who's Watching Who? +ULUS10168 = true +ULES00571 = true + +# Tron Evolution +ULUS10548 = true +ULES01494 = true +ULES01495 = true + +# Atari Classics Evolved +ULUS10325 = true + [JitInvalidationHack] # This is an absolutely awful hack that somehow prevents issues when clearing the JIT, # if the game has copied code with EmuHack opcodes or something. Hopefully will be able @@ -992,6 +1065,12 @@ ULAS42073 = true ULJM05265 = true ULJM05366 = true +# Kingdom Hearts (see #11223) +ULUS10505 = true +ULES01441 = true +ULJM05600 = true +ULJM05775 = true + [ShaderColorBitmask] # Outrun 2006: Coast to Coast - issue #11358 ULES00262 = true diff --git a/assets/flash0/font/jpn0.pgf b/assets/flash0/font/jpn0.pgf index db594b6c5e..05d560c691 100644 Binary files a/assets/flash0/font/jpn0.pgf and b/assets/flash0/font/jpn0.pgf differ diff --git a/assets/flash0/font/kr0.pgf b/assets/flash0/font/kr0.pgf index 94d2a78baa..9f8cce0390 100644 Binary files a/assets/flash0/font/kr0.pgf and b/assets/flash0/font/kr0.pgf differ diff --git a/assets/flash0/font/ltn0.pgf b/assets/flash0/font/ltn0.pgf index 4de80003c8..4edba14734 100644 Binary files a/assets/flash0/font/ltn0.pgf and b/assets/flash0/font/ltn0.pgf differ diff --git a/assets/flash0/font/ltn1.pgf b/assets/flash0/font/ltn1.pgf index 866bd102e2..55da7bfe5c 100644 Binary files a/assets/flash0/font/ltn1.pgf and b/assets/flash0/font/ltn1.pgf differ diff --git a/assets/flash0/font/ltn10.pgf b/assets/flash0/font/ltn10.pgf index 406960c329..8adc5a95c5 100644 Binary files a/assets/flash0/font/ltn10.pgf and b/assets/flash0/font/ltn10.pgf differ diff --git a/assets/flash0/font/ltn11.pgf b/assets/flash0/font/ltn11.pgf index 4ef50a0a42..80d674b896 100644 Binary files a/assets/flash0/font/ltn11.pgf and b/assets/flash0/font/ltn11.pgf differ diff --git a/assets/flash0/font/ltn12.pgf b/assets/flash0/font/ltn12.pgf index 5d134bab99..770d9c591b 100644 Binary files a/assets/flash0/font/ltn12.pgf and b/assets/flash0/font/ltn12.pgf differ diff --git a/assets/flash0/font/ltn13.pgf b/assets/flash0/font/ltn13.pgf index 6421f56420..a9eb280d85 100644 Binary files a/assets/flash0/font/ltn13.pgf and b/assets/flash0/font/ltn13.pgf differ diff --git a/assets/flash0/font/ltn14.pgf b/assets/flash0/font/ltn14.pgf index 9d099a91ec..706dc11c5e 100644 Binary files a/assets/flash0/font/ltn14.pgf and b/assets/flash0/font/ltn14.pgf differ diff --git a/assets/flash0/font/ltn15.pgf b/assets/flash0/font/ltn15.pgf index 63fcb15100..818bec4500 100644 Binary files a/assets/flash0/font/ltn15.pgf and b/assets/flash0/font/ltn15.pgf differ diff --git a/assets/flash0/font/ltn2.pgf b/assets/flash0/font/ltn2.pgf index e493d57f43..eee46a6012 100644 Binary files a/assets/flash0/font/ltn2.pgf and b/assets/flash0/font/ltn2.pgf differ diff --git a/assets/flash0/font/ltn3.pgf b/assets/flash0/font/ltn3.pgf index 089fb1f721..646945f84c 100644 Binary files a/assets/flash0/font/ltn3.pgf and b/assets/flash0/font/ltn3.pgf differ diff --git a/assets/flash0/font/ltn4.pgf b/assets/flash0/font/ltn4.pgf index 395340d2fc..94e9e28f50 100644 Binary files a/assets/flash0/font/ltn4.pgf and b/assets/flash0/font/ltn4.pgf differ diff --git a/assets/flash0/font/ltn5.pgf b/assets/flash0/font/ltn5.pgf index f15c6b7380..dff1364168 100644 Binary files a/assets/flash0/font/ltn5.pgf and b/assets/flash0/font/ltn5.pgf differ diff --git a/assets/flash0/font/ltn6.pgf b/assets/flash0/font/ltn6.pgf index 4c09094fdd..3782f74815 100644 Binary files a/assets/flash0/font/ltn6.pgf and b/assets/flash0/font/ltn6.pgf differ diff --git a/assets/flash0/font/ltn7.pgf b/assets/flash0/font/ltn7.pgf index 64e3eedaf7..13c86ca1ea 100644 Binary files a/assets/flash0/font/ltn7.pgf and b/assets/flash0/font/ltn7.pgf differ diff --git a/assets/flash0/font/ltn8.pgf b/assets/flash0/font/ltn8.pgf index ad4c48dd7e..1076299b5b 100644 Binary files a/assets/flash0/font/ltn8.pgf and b/assets/flash0/font/ltn8.pgf differ diff --git a/assets/flash0/font/ltn9.pgf b/assets/flash0/font/ltn9.pgf index f0d14f62fa..d86906ef1c 100644 Binary files a/assets/flash0/font/ltn9.pgf and b/assets/flash0/font/ltn9.pgf differ diff --git a/assets/ui_atlas_luna.meta b/assets/ui_atlas_luna.meta index ad98a26b10..7b14439328 100644 Binary files a/assets/ui_atlas_luna.meta and b/assets/ui_atlas_luna.meta differ diff --git a/assets/ui_atlas_luna.zim b/assets/ui_atlas_luna.zim index b8c156fade..ec911c2bed 100644 Binary files a/assets/ui_atlas_luna.zim and b/assets/ui_atlas_luna.zim differ diff --git a/atlasscript.txt b/atlasscript.txt index 6926aac64d..6e558dbbc9 100644 --- a/atlasscript.txt +++ b/atlasscript.txt @@ -46,3 +46,7 @@ image I_FLAG_EU source_assets/image/flag_eu.png copy image I_FLAG_HK source_assets/image/flag_hk.png copy image I_FLAG_AS source_assets/image/flag_as.png copy image I_FLAG_KO source_assets/image/flag_ko.png copy +image I_FULLSCREEN source_assets/image/fullscreen.png copy +image I_RESTORE source_assets/image/restore.png copy +image I_SDCARD source_assets/image/sdcard.png copy +image I_HOME source_assets/image/home.png copy \ No newline at end of file diff --git a/b.sh b/b.sh index a707c92f79..55ab6e642d 100755 --- a/b.sh +++ b/b.sh @@ -16,6 +16,9 @@ do --ios) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/ios.cmake ${CMAKE_ARGS}" TARGET_OS=iOS ;; + --ios-xcode) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/ios.cmake -DIOS_PLATFORM=OS -GXcode ${CMAKE_ARGS}" + TARGET_OS=iOS-xcode + ;; --rpi-armv6) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/raspberry.armv6.cmake ${CMAKE_ARGS}" ;; diff --git a/buildatlas.sh b/buildatlas.sh index 96e478d37c..122d6f862c 100755 --- a/buildatlas.sh +++ b/buildatlas.sh @@ -1,3 +1,10 @@ -./ext/native/tools/build/atlastool atlasscript.txt ui 8888 && cp ui_atlas_luna.zim ui_atlas_luna.meta assets && cp ui_atlas_luna.zim ui_atlas_luna.meta android/assets && rm ui_atlas_luna.cpp ui_atlas_luna.h +./ext/native/tools/build/atlastool atlasscript.txt ui 8888 && cp ui_atlas.zim ui_atlas.meta assets && cp ui_atlas.zim ui_atlas.meta android/assets && rm ui_atlas.cpp ui_atlas.h -rm ui_atlas_luna.zim ui_atlas_luna.meta +mv assets/ui_atlas.zim assets/ui_atlas_luna.zim +mv assets/ui_atlas.meta assets/ui_atlas_luna.meta +mv android/assets/ui_atlas.zim android/assets/ui_atlas_luna.zim +mv android/assets/ui_atlas.meta android/assets/ui_atlas_luna.meta + +rm ui_atlas.zim ui_atlas.meta +pause +$SHELL diff --git a/cmake/Modules/FindLibZip.cmake b/cmake/Modules/FindLIBZIP.cmake similarity index 99% rename from cmake/Modules/FindLibZip.cmake rename to cmake/Modules/FindLIBZIP.cmake index 778da584f0..25adb25b10 100644 --- a/cmake/Modules/FindLibZip.cmake +++ b/cmake/Modules/FindLIBZIP.cmake @@ -19,7 +19,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see - # Locate libzip # This module defines # LIBZIP_LIBRARY diff --git a/ffmpeg b/ffmpeg index 0b28335ace..4738685f4a 160000 --- a/ffmpeg +++ b/ffmpeg @@ -1 +1 @@ -Subproject commit 0b28335acea4f429ae798c5e75232e54881bf164 +Subproject commit 4738685f4ac27c1775a238d1e602f399627b5e6f diff --git a/headless/Compare.cpp b/headless/Compare.cpp index e9fa38be78..5c96a7cce4 100644 --- a/headless/Compare.cpp +++ b/headless/Compare.cpp @@ -15,22 +15,25 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "headless/Compare.h" - -#include "Common/ColorConv.h" -#include "Common/File/FileUtil.h" -#include "Core/Host.h" - -#include "GPU/GPUState.h" -#include "GPU/Common/GPUDebugInterface.h" -#include "GPU/Common/TextureDecoder.h" - #include #include #include #include #include #include +#include "headless/Compare.h" + +#include "Common/ColorConv.h" +#include "Common/Data/Format/PNGLoad.h" +#include "Common/File/FileUtil.h" +#include "Common/StringUtils.h" +#include "Core/Host.h" +#include "Core/Loaders.h" + +#include "GPU/GPUState.h" +#include "GPU/Common/GPUDebugInterface.h" +#include "GPU/Common/TextureDecoder.h" + bool teamCityMode = false; std::string currentTestName = ""; @@ -184,14 +187,24 @@ protected: std::ifstream &in_; }; -std::string ExpectedFromFilename(const std::string &bootFilename) -{ - return bootFilename.substr(0, bootFilename.length() - 4) + ".expected"; +std::string ExpectedFromFilename(const std::string &bootFilename) { + size_t pos = bootFilename.find_last_of('.'); + if (pos == bootFilename.npos) { + return bootFilename + ".expected"; + } + return bootFilename.substr(0, pos) + ".expected"; } -std::string ExpectedScreenshotFromFilename(const std::string &bootFilename) -{ - return bootFilename.substr(0, bootFilename.length() - 4) + ".expected.bmp"; +std::string ExpectedScreenshotFromFilename(const std::string &bootFilename) { + size_t pos = bootFilename.find_last_of('.'); + if (pos == bootFilename.npos) { + return bootFilename + ".bmp"; + } + // Let's use pngs as the default for ppdmp tests. + if (bootFilename.substr(pos) == ".ppdmp") { + return bootFilename.substr(0, pos) + ".png"; + } + return bootFilename.substr(0, pos) + ".expected.bmp"; } static std::string ChopFront(std::string s, std::string front) @@ -221,14 +234,16 @@ std::string GetTestName(const std::string &bootFilename) return ChopEnd(ChopFront(ChopFront(bootFilename, "tests/"), "pspautotests/tests/"), ".prx"); } -bool CompareOutput(const std::string &bootFilename, const std::string &output, bool verbose) -{ +bool CompareOutput(const std::string &bootFilename, const std::string &output, bool verbose) { std::string expect_filename = ExpectedFromFilename(bootFilename); - std::ifstream expect_f; - expect_f.open(expect_filename.c_str(), std::ios::in); - if (!expect_f.fail()) - { - BufferedLineReaderFile expected(expect_f); + std::unique_ptr expect_loader(ConstructFileLoader(expect_filename)); + + if (expect_loader->Exists()) { + std::string expect_results; + expect_results.resize(expect_loader->FileSize()); + expect_results.resize(expect_loader->ReadAt(0, expect_loader->FileSize(), &expect_results[0])); + + BufferedLineReader expected(expect_results); BufferedLineReader actual(output); bool failed = false; @@ -267,7 +282,6 @@ bool CompareOutput(const std::string &bootFilename, const std::string &output, b printf("+ %s\n", actual.Consume().c_str()); } - expect_f.close(); if (verbose) { @@ -290,13 +304,29 @@ bool CompareOutput(const std::string &bootFilename, const std::string &output, b } return !failed; - } - else - { - fprintf(stderr, "Expectation file %s not found\n", expect_filename.c_str()); - TeamCityPrint("testIgnored name='%s' message='Expects file missing'", currentTestName.c_str()); - GitHubActionsPrint("error", "Expected file missing for %s", currentTestName.c_str()); - return false; + } else { + std::unique_ptr screenshot(ConstructFileLoader(ExpectedScreenshotFromFilename(bootFilename))); + bool failed = true; + if (screenshot->Exists()) { + // Okay, just a screenshot then. Allow a pass with no output (i.e. screenshot match.) + failed = output.find_first_not_of(" \r\n\t") != output.npos; + if (failed) { + TeamCityPrint("testFailed name='%s' message='Output different from expected file'", currentTestName.c_str()); + GitHubActionsPrint("error", "Incorrect output for %s", currentTestName.c_str()); + } + } else { + fprintf(stderr, "Expectation file %s not found\n", expect_filename.c_str()); + TeamCityPrint("testIgnored name='%s' message='Expects file missing'", currentTestName.c_str()); + GitHubActionsPrint("error", "Expected file missing for %s", currentTestName.c_str()); + } + + if (verbose || (screenshot->Exists() && failed)) { + BufferedLineReader actual(output); + while (actual.HasLines()) { + printf("+ %s\n", actual.Consume().c_str()); + } + } + return !failed; } } @@ -370,29 +400,66 @@ double CompareScreenshot(const std::vector &pixels, u32 stride, u32 w, u32 } // We assume the bitmap is the specified size, not including whatever stride. - u32 *reference = (u32 *) calloc(stride * h, sizeof(u32)); + u32 *reference = nullptr; + bool asBitmap = false; - FILE *bmp = File::OpenCFile(screenshotFilename, "rb"); - if (bmp) - { - // The bitmap header is 14 + 40 bytes. We could validate it but the test would fail either way. - fseek(bmp, 14 + 40, SEEK_SET); - if (fread(reference, sizeof(u32), stride * h, bmp) != stride * h) + std::unique_ptr loader(ConstructFileLoader(screenshotFilename)); + if (loader->Exists()) { + uint8_t header[2]; + if (loader->ReadAt(0, 2, header) != 2) { error = "Unable to read screenshot data: " + screenshotFilename; - fclose(bmp); - } - else - { + return -1.0f; + } + + if (header[0] == 'B' && header[1] == 'M') { + reference = (u32 *)calloc(stride * h, sizeof(u32)); + asBitmap = true; + // The bitmap header is 14 + 40 bytes. We could validate it but the test would fail either way. + if (reference && loader->ReadAt(14 + 40, sizeof(u32), stride * h, reference) != stride * h) { + error = "Unable to read screenshot data: " + screenshotFilename; + return -1.0f; + } + } else { + // For now, assume a PNG otherwise. + std::vector compressed; + compressed.resize(loader->FileSize()); + if (loader->ReadAt(0, compressed.size(), &compressed[0]) != compressed.size()) { + error = "Unable to read screenshot data: " + screenshotFilename; + return -1.0f; + } + + int width, height; + if (!pngLoadPtr(&compressed[0], compressed.size(), &width, &height, (unsigned char **)&reference)) { + error = "Unable to read screenshot data: " + screenshotFilename; + return -1.0f; + } + } + } else { error = "Unable to read screenshot: " + screenshotFilename; - free(reference); + return -1.0f; + } + + if (!reference) { + error = "Unable to allocate screenshot data: " + screenshotFilename; return -1.0f; } u32 errors = 0; - for (u32 y = 0; y < h; ++y) - { - for (u32 x = 0; x < w; ++x) - errors += ComparePixel(pixels[y * stride + x], reference[y * stride + x]); + if (asBitmap) { + // The reference is flipped and BGRA by default for the common BMP compare case. + for (u32 y = 0; y < h; ++y) { + u32 yoff = y * stride; + for (u32 x = 0; x < w; ++x) + errors += ComparePixel(pixels[y * stride + x], reference[yoff + x]); + } + } else { + // Just convert to BGRA for simplicity. + ConvertRGBA8888ToBGRA8888(reference, reference, h * stride); + for (u32 y = 0; y < h; ++y) { + u32 yoff = (h - y - 1) * stride; + for (u32 x = 0; x < w; ++x) + errors += ComparePixel(pixels[y * stride + x], reference[yoff + x]); + } } free(reference); diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 14fd7e38cc..f0d240e2f1 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -2,10 +2,11 @@ // See headless.txt. // To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version. +#include "ppsspp_config.h" #include #include #include -#if defined(ANDROID) +#if PPSSPP_PLATFORM(ANDROID) #include #endif @@ -23,6 +24,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/System.h" +#include "Core/WebServer.h" #include "Core/HLE/sceUtility.h" #include "Core/Host.h" #include "Core/SaveState.h" @@ -38,7 +40,7 @@ #include "SDLHeadlessHost.h" #endif -#if defined(ANDROID) +#if PPSSPP_PLATFORM(ANDROID) JNIEnv *getEnv() { return nullptr; } @@ -46,6 +48,9 @@ JNIEnv *getEnv() { jclass findClass(const char *name) { return nullptr; } + +bool audioRecording_Available() { return false; } +bool audioRecording_State() { return false; } #endif class PrintfLogger : public LogListener { @@ -81,15 +86,11 @@ void NativeRender(GraphicsContext *graphicsContext) { } void NativeResized() { } std::string System_GetProperty(SystemProperty prop) { return ""; } -int System_GetPropertyInt(SystemProperty prop) { - return -1; -} -float System_GetPropertyFloat(SystemProperty prop) { - return -1; -} -bool System_GetPropertyBool(SystemProperty prop) { - return false; -} +std::vector System_GetPropertyStringVec(SystemProperty prop) { return std::vector(); } +int System_GetPropertyInt(SystemProperty prop) { return -1; } +float System_GetPropertyFloat(SystemProperty prop) { return -1.0f; } +bool System_GetPropertyBool(SystemProperty prop) { return false; } + void System_SendMessage(const char *command, const char *parameter) {} void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function cb) { cb(false, ""); } void System_AskForPermission(SystemPermission permission) {} @@ -106,6 +107,7 @@ int printUsage(const char *progname, const char *reason) fprintf(stderr, " -m, --mount umd.cso mount iso on umd1:\n"); fprintf(stderr, " -r, --root some/path mount path on host0: (elfs must be in here)\n"); fprintf(stderr, " -l, --log full log output, not just emulated printfs\n"); + fprintf(stderr, " --debugger=PORT enable websocket debugger and break at start\n"); #if defined(HEADLESSHOST_CLASS) { @@ -177,8 +179,8 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool if (coreParameter.graphicsContext && coreParameter.graphicsContext->GetDrawContext()) coreParameter.graphicsContext->GetDrawContext()->BeginFrame(); - coreState = CORE_RUNNING; - while (coreState == CORE_RUNNING) + coreState = coreParameter.startBreak ? CORE_STEPPING : CORE_RUNNING; + while (coreState == CORE_RUNNING || coreState == CORE_STEPPING) { int blockTicks = usToCycles(1000000 / 10); PSP_RunLoopFor(blockTicks); @@ -188,6 +190,9 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, bool coreState = CORE_RUNNING; headlessHost->SwapBuffers(); } + if (coreState == CORE_STEPPING && !coreParameter.startBreak) { + break; + } if (time_now_d() > deadline) { // Don't compare, print the output at least up to this point, and bail. printf("%s", output.c_str()); @@ -230,6 +235,7 @@ int main(int argc, const char* argv[]) const char *stateToLoad = 0; GPUCore gpuCore = GPUCORE_SOFTWARE; CPUCore cpuCore = CPUCore::JIT; + int debuggerPort = -1; std::vector testFilenames; const char *mountIso = 0; @@ -291,6 +297,8 @@ int main(int argc, const char* argv[]) screenshotFilename = argv[i] + strlen("--screenshot="); else if (!strncmp(argv[i], "--timeout=", strlen("--timeout=")) && strlen(argv[i]) > strlen("--timeout=")) timeout = strtod(argv[i] + strlen("--timeout="), NULL); + else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger=")) + debuggerPort = (int)strtoul(argv[i] + strlen("--debugger="), NULL, 10); else if (!strcmp(argv[i], "--teamcity")) teamCityMode = true; else if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state=")) @@ -422,6 +430,14 @@ int main(int argc, const char* argv[]) } #endif + UpdateUIState(UISTATE_INGAME); + + if (debuggerPort > 0) { + g_Config.iRemoteISOPort = debuggerPort; + coreParameter.startBreak = true; + StartWebServer(WebServerFlags::DEBUGGER); + } + if (stateToLoad != NULL) SaveState::Load(stateToLoad, -1); @@ -458,6 +474,10 @@ int main(int argc, const char* argv[]) } } + if (debuggerPort > 0) { + ShutdownWebServer(); + } + host->ShutdownGraphics(); delete host; host = nullptr; diff --git a/headless/StubHost.cpp b/headless/StubHost.cpp index 1219d42365..27832dc2a0 100644 --- a/headless/StubHost.cpp +++ b/headless/StubHost.cpp @@ -33,12 +33,7 @@ void HeadlessHost::SendOrCollectDebugOutput(const std::string &data) DEBUG_LOG(COMMON, "%s", data.c_str()); } -void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) -{ - if (!gfx_) { - return; - } - +void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) { // Only if we're actually comparing. if (comparisonScreenshot_.empty()) { return; @@ -65,22 +60,19 @@ void HeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) SendOrCollectDebugOutput(temp); } - if (errors > 0 && !teamCityMode) - { - // Lazy, just read in the original header to output the failed screenshot. - u8 header[14 + 40] = {0}; - FILE *bmp = File::OpenCFile(comparisonScreenshot_, "rb"); - if (bmp) - { - if (fread(&header, sizeof(header), 1, bmp) != 1) { - SendOrCollectDebugOutput("Failed to read original screenshot header.\n"); - } - fclose(bmp); - } + if (errors > 0 && !teamCityMode && !getenv("GITHUB_ACTIONS")) { + static const u8 header[14 + 40] = { + 0x42, 0x4D, 0x38, 0x80, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x01, + 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x80, 0x08, 0x00, 0x12, 0x0B, + 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; FILE *saved = File::OpenCFile("__testfailure.bmp", "wb"); - if (saved) - { + if (saved) { fwrite(&header, sizeof(header), 1, saved); fwrite(pixels.data(), sizeof(u32), FRAME_STRIDE * FRAME_HEIGHT, saved); fclose(saved); diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 7ba5a84878..59bd731ee4 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -196,6 +196,7 @@ static LocationHelper *locationHelper; GLKView* view = (GLKView *)self.view; view.context = self.context; view.drawableDepthFormat = GLKViewDrawableDepthFormat24; + view.drawableStencilFormat = GLKViewDrawableStencilFormat8; [EAGLContext setCurrentContext:self.context]; self.preferredFramesPerSecond = 60; @@ -686,7 +687,7 @@ static LocationHelper *locationHelper; }; } #endif -#if defined(__IPHONE_13) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13 +#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 if (extendedProfile.buttonOptions != nil) { extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) { [self controllerButtonPressed:pressed keyCode:NKCODE_BUTTON_13]; diff --git a/ios/main.mm b/ios/main.mm index 5daecb3e79..4b3e768d5c 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -64,6 +64,14 @@ std::string System_GetProperty(SystemProperty prop) { } } +std::vector System_GetPropertyStringVec(SystemProperty prop) { + switch (prop) { + case SYSPROP_TEMP_DIRS: + default: + return std::vector(); + } +} + int System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_AUDIO_SAMPLE_RATE: diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index 68f8c0ec7d..122cab72e2 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -905,6 +905,8 @@ float System_GetPropertyFloat(SystemProperty prop) } std::string System_GetProperty(SystemProperty prop) { return ""; } +std::vector System_GetPropertyStringVec(SystemProperty prop) { return std::vector(); } + void System_SendMessage(const char *command, const char *parameter) {} void NativeUpdate() {} void NativeRender(GraphicsContext *graphicsContext) {} diff --git a/source_assets/image/buttons.svg b/source_assets/image/buttons.svg index b0b87419ff..61aa452258 100644 --- a/source_assets/image/buttons.svg +++ b/source_assets/image/buttons.svg @@ -1,6 +1,4 @@ - - + + + + + + + + + + + + + + + + + inkscape:window-maximized="1" + inkscape:document-rotation="0"> + + @@ -1669,13 +1768,12 @@ id="rect4729" style="fill:#60d068;fill-opacity:1;stroke:#ffffff;stroke-width:1.68099999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:0.64893613;stroke-dasharray:none" /> - + style="fill:none;stroke:#ffffff;stroke-width:11.99826908;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + cx="114.28571" + cy="175.21933" + rx="142.85715" + ry="140" /> + style="stroke:#ffffff;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5264)"> - - + + inkscape:export-ydpi="135" + cx="114.28571" + cy="175.21933" + rx="142.85715" + ry="140" /> + inkscape:export-ydpi="135" + transform="translate(339.66379,6.8185296)"> + style="fill:none;stroke:#ffffff;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter5328)" /> + y="34.633499" + style="font-size:40px;line-height:1.25">  ⚙ + x="270.72089" + y="200.55106" + style="font-size:40px;line-height:1.25">⚙ 1 2 3 4 5 6 + + + SD + + + + + + + + + + + diff --git a/source_assets/image/fullscreen.png b/source_assets/image/fullscreen.png new file mode 100644 index 0000000000..4cf4350efa Binary files /dev/null and b/source_assets/image/fullscreen.png differ diff --git a/source_assets/image/home.png b/source_assets/image/home.png new file mode 100644 index 0000000000..dc570a880b Binary files /dev/null and b/source_assets/image/home.png differ diff --git a/source_assets/image/restore.png b/source_assets/image/restore.png new file mode 100644 index 0000000000..143ef0779f Binary files /dev/null and b/source_assets/image/restore.png differ diff --git a/source_assets/image/sdcard.png b/source_assets/image/sdcard.png new file mode 100644 index 0000000000..ae7225c65e Binary files /dev/null and b/source_assets/image/sdcard.png differ diff --git a/source_assets/image/umd256.png b/source_assets/image/umd256.png deleted file mode 100644 index dc09340d90..0000000000 Binary files a/source_assets/image/umd256.png and /dev/null differ diff --git a/source_assets/image/umd48.png b/source_assets/image/umd48.png deleted file mode 100644 index edf16fed77..0000000000 Binary files a/source_assets/image/umd48.png and /dev/null differ diff --git a/unittest/TestShaderGenerators.cpp b/unittest/TestShaderGenerators.cpp index a68c8247ba..81d74558a4 100644 --- a/unittest/TestShaderGenerators.cpp +++ b/unittest/TestShaderGenerators.cpp @@ -1,3 +1,4 @@ +#include "ppsspp_config.h" #include #include "Common/StringUtils.h" @@ -13,11 +14,13 @@ #include "GPU/Common/VertexShaderGenerator.h" #include "GPU/Common/ReinterpretFramebuffer.h" +#if PPSSPP_PLATFORM(WINDOWS) #include "GPU/D3D11/D3D11Util.h" #include "GPU/D3D11/D3D11Loader.h" #include "GPU/D3D9/D3DCompilerLoader.h" #include "GPU/D3D9/D3D9ShaderCompiler.h" +#endif bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bugs, std::string *errorString) { uint64_t uniformMask; @@ -89,6 +92,7 @@ bool GenerateVShader(VShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs bool TestCompileShader(const char *buffer, ShaderLanguage lang, bool vertex, std::string *errorMessage) { std::vector spirv; switch (lang) { +#if PPSSPP_PLATFORM(WINDOWS) case ShaderLanguage::HLSL_D3D11: { auto output = CompileShaderToBytecodeD3D11(buffer, strlen(buffer), vertex ? "vs_4_0" : "ps_4_0", 0); @@ -104,6 +108,7 @@ bool TestCompileShader(const char *buffer, ShaderLanguage lang, bool vertex, std return false; } } +#endif case ShaderLanguage::GLSL_VULKAN: return GLSLtoSPV(vertex ? VK_SHADER_STAGE_VERTEX_BIT : VK_SHADER_STAGE_FRAGMENT_BIT, buffer, GLSLVariant::VULKAN, spirv, errorMessage); @@ -352,9 +357,13 @@ bool TestFragmentShaders() { } bool TestShaderGenerators() { +#if PPSSPP_PLATFORM(WINDOWS) LoadD3D11(); init_glslang(); LoadD3DCompilerDynamic(); +#else + init_glslang(); +#endif if (!TestFragmentShaders()) { return false; diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index b5beb3cefa..6547be7ea6 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -27,16 +27,17 @@ // To use, set command line parameter to one or more of the tests below, or "all". // Search for "availableTests". +#include "ppsspp_config.h" #include #include #include +#include #include #include -#if defined(ANDROID) +#if PPSSPP_PLATFORM(ANDROID) #include #endif -#include "ppsspp_config.h" #include "Common/System/NativeApp.h" #include "Common/System/System.h" #include "Common/Input/InputState.h" @@ -58,7 +59,9 @@ #include "unittest/TestVertexJit.h" #include "unittest/UnitTest.h" + std::string System_GetProperty(SystemProperty prop) { return ""; } +std::vector System_GetPropertyStringVec(SystemProperty prop) { return std::vector(); } int System_GetPropertyInt(SystemProperty prop) { return -1; } @@ -69,7 +72,7 @@ bool System_GetPropertyBool(SystemProperty prop) { return false; } -#if defined(ANDROID) +#if PPSSPP_PLATFORM(ANDROID) JNIEnv *getEnv() { return nullptr; } @@ -77,6 +80,9 @@ JNIEnv *getEnv() { jclass findClass(const char *name) { return nullptr; } + +bool audioRecording_Available() { return false; } +bool audioRecording_State() { return false; } #endif #ifndef M_PI_2