diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b8fddd1a76d6..00187ee1c5e1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,8 @@ on: push: branches: - master + # For testing. + - actions paths-ignore: - '*.{txt,md}' - 'Tools/**' @@ -176,9 +178,19 @@ jobs: with: submodules: recursive + - name: Cache Qt + uses: actions/cache@v1 + if: matrix.extra == 'qt' + id: cache-qt + with: + path: ${{ runner.workspace }}/Qt + key: ${{ runner.os }}-QtCache + - name: Install Qt uses: jurplel/install-qt-action@v2 if: matrix.extra == 'qt' + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} - uses: nttld/setup-ndk@v1 if: matrix.extra == 'android' diff --git a/.gitignore b/.gitignore index 818e0a459a41..07b897f32866 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,3 @@ debian/ppsspp/ # RenderDoc *.rdc - -# bad output from libretro. don't want to accidentally add it -nul diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f1af20d66540..981f938ebc9d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,26 +11,49 @@ variables: variables: EXTRA_PATH: lib +.windows-defs: + variables: + MAKEFILE_PATH: libretro + include: - - template: Jobs/Code-Quality.gitlab-ci.yml - project: 'libretro-infrastructure/ci-templates' file: '/android-cmake.yml' - project: 'libretro-infrastructure/ci-templates' file: '/linux-cmake.yml' + - project: 'libretro-infrastructure/ci-templates' + file: '/windows-x64-msvc19-msys2.yml' + - project: 'libretro-infrastructure/ci-templates' + file: '/windows-i686-msvc19-msys2.yml' stages: - build-prepare - build-shared - - build-static - - test -#Desktop -libretro-build-linux-x86_64: +# Desktop +libretro-build-linux-x64: extends: - .libretro-linux-cmake-x86_64 - .core-defs - .linux-defs +libretro-build-linux-i686: + extends: + - .libretro-linux-cmake-x86 + - .core-defs + - .linux-defs + +libretro-build-windows-x64: + extends: + - .libretro-windows-x64-msvc19-msys2-make-default + - .core-defs + - .windows-defs + +libretro-build-windows-i686: + extends: + - .libretro-windows-i686-msvc19-msys2-make-default + - .core-defs + - .windows-defs + # Android libretro-build-android-armeabi-v7a: extends: diff --git a/CMakeLists.txt b/CMakeLists.txt index 21a2332eebf2..f71e066314c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,7 +134,8 @@ option(USE_MINIUPNPC "Build with miniUPnPc support" ON) option(USE_SYSTEM_SNAPPY "Dynamically link against system snappy" ${USE_SYSTEM_SNAPPY}) option(USE_SYSTEM_FFMPEG "Dynamically link against system FFMPEG" ${USE_SYSTEM_FFMPEG}) option(USE_SYSTEM_LIBZIP "Dynamically link against system libzip" ${USE_SYSTEM_LIBZIP}) -option(USE_ADDRESS_SANITIZER "Use Clang memory sanitizer" ${USE_ADDRESS_SANITIZER}) +option(USE_ASAN "Use address sanitizer" OFF) +option(USE_UBSAN "Use undefined behaviour sanitizer" OFF) if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN) if(USING_X11_VULKAN) @@ -272,11 +273,24 @@ if(NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-switch -Wno-uninitialized") endif() - if(USE_ADDRESS_SANITIZER) + if(USE_ASAN) message("Address sanitizer enabled (DEBUG only)") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -fsanitize=address") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address") - add_definitions(-DUSE_ADDRESS_SANITIZER) + add_definitions(-DUSE_ASAN) + endif() + if(USE_UBSAN) + message("Undefined behaviour sanitizer enabled (DEBUG only)") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined") + set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=undefined") + + # UBSAN is a collection of sanitizers, including vtpr, which reqiuires RTTI. + # ext/glslang disables RTTI by default using the `ENABLE_RTTI` option. + # If RTTI is disabled, we must also disable the vtpr sanitizer. + if(NOT ENABLE_RTTI) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-sanitize=vptr") + set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-sanitize=vptr") + endif() endif() set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -D_DEBUG") @@ -1492,6 +1506,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 @@ -2186,6 +2204,7 @@ 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/GPU/OpenGL/GLCommon.h b/Common/GPU/OpenGL/GLCommon.h index b9fbb3e41390..dd7bdeaa1cdc 100644 --- a/Common/GPU/OpenGL/GLCommon.h +++ b/Common/GPU/OpenGL/GLCommon.h @@ -8,9 +8,6 @@ #elif defined(USING_GLES2) #include #include -// At least Nokia platforms need the three below -#include -typedef char GLchar; #define GL_BGRA_EXT 0x80E1 #else // OpenGL #include "GL/glew.h" diff --git a/Common/GPU/OpenGL/GLFeatures.cpp b/Common/GPU/OpenGL/GLFeatures.cpp index 9ae4185c337b..5902ed649274 100644 --- a/Common/GPU/OpenGL/GLFeatures.cpp +++ b/Common/GPU/OpenGL/GLFeatures.cpp @@ -181,7 +181,7 @@ void CheckGLExtensions() { // Just for reference: Galaxy Y has renderer == "VideoCore IV HW" } else if (vendor == "Vivante Corporation") { gl_extensions.gpuVendor = GPU_VENDOR_VIVANTE; - } else if (vendor == "Apple Inc.") { + } else if (vendor == "Apple Inc." || vendor == "Apple") { gl_extensions.gpuVendor = GPU_VENDOR_APPLE; } else { WARN_LOG(G3D, "Unknown GL vendor: '%s'", vendor.c_str()); @@ -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/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 44e0a2da27e1..80606241b5bc 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -1369,12 +1369,14 @@ void GLQueueRunner::PerformCopy(const GLRStep &step) { #if defined(USING_GLES2) #ifndef IOS + _assert_msg_(gl_extensions.OES_copy_image || gl_extensions.NV_copy_image || gl_extensions.EXT_copy_image, "Image copy extension expected"); glCopyImageSubDataOES( srcTex, target, srcLevel, srcRect.x, srcRect.y, srcZ, dstTex, target, dstLevel, dstPos.x, dstPos.y, dstZ, srcRect.w, srcRect.h, depth); #endif #else + _assert_msg_(gl_extensions.ARB_copy_image || gl_extensions.NV_copy_image, "Image copy extension expected"); if (gl_extensions.ARB_copy_image) { glCopyImageSubData( srcTex, target, srcLevel, srcRect.x, srcRect.y, srcZ, diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index fcce3aabbdc9..7c1708fb63a0 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -302,7 +302,7 @@ class OpenGLPipeline : public Pipeline { // TODO: Optimize by getting the locations first and putting in a custom struct UniformBufferDesc dynamicUniforms; - GLint samplerLocs_[8]; + GLint samplerLocs_[8]{}; std::vector dynamicUniformLocs_; GLRProgram *program_ = nullptr; @@ -1038,8 +1038,8 @@ Pipeline *OpenGLContext::CreateGraphicsPipeline(const PipelineDesc &desc) { ERROR_LOG(G3D, "Pipeline requires at least one shader"); return nullptr; } - if ((int)desc.prim >= (int)Primitive::PRIMITIVE_TYPE_COUNT) { - ERROR_LOG(G3D, "Invalid primitive type"); + if ((uint32_t)desc.prim >= (uint32_t)Primitive::PRIMITIVE_TYPE_COUNT) { + ERROR_LOG(G3D, "Invalid primitive type"); return nullptr; } if (!desc.depthStencil || !desc.blend || !desc.raster) { diff --git a/Common/GPU/Vulkan/VulkanDebug.cpp b/Common/GPU/Vulkan/VulkanDebug.cpp index f165d736095c..acfd81b1d545 100644 --- a/Common/GPU/Vulkan/VulkanDebug.cpp +++ b/Common/GPU/Vulkan/VulkanDebug.cpp @@ -37,6 +37,11 @@ VKAPI_ATTR VkBool32 VKAPI_CALL VulkanDebugUtilsCallback( // UNASSIGNED-CoreValidation-Shader-OutputNotConsumed - benign perf warning return false; } + if (messageCode == 1303270965) { + // Benign perf warning, image blit using GENERAL layout. + // UNASSIGNED + return false; + } const char *pLayerPrefix = ""; if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { diff --git a/Common/GPU/Vulkan/VulkanImage.cpp b/Common/GPU/Vulkan/VulkanImage.cpp index 5301a47803c9..564729a99af4 100644 --- a/Common/GPU/Vulkan/VulkanImage.cpp +++ b/Common/GPU/Vulkan/VulkanImage.cpp @@ -31,6 +31,11 @@ static bool IsDepthStencilFormat(VkFormat format) { } bool VulkanTexture::CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *allocator, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage, const VkComponentMapping *mapping) { + if (w == 0 || h == 0 || numMips == 0) { + ERROR_LOG(G3D, "Can't create a zero-size VulkanTexture"); + return false; + } + Wipe(); width_ = w; @@ -196,7 +201,8 @@ void VulkanTexture::ClearMip(VkCommandBuffer cmd, int mip, uint32_t value) { vkCmdClearColorImage(cmd, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearVal, 1, &range); } -void VulkanTexture::GenerateMip(VkCommandBuffer cmd, int mip) { +// Low-quality mipmap generation by bilinear blit, but works okay. +void VulkanTexture::GenerateMip(VkCommandBuffer cmd, int mip, VkImageLayout imageLayout) { _assert_msg_(mip != 0, "Cannot generate the first level"); _assert_msg_(mip < numMips_, "Cannot generate mipmaps past the maximum created (%d vs %d)", mip, numMips_); VkImageBlit blit{}; @@ -214,16 +220,20 @@ void VulkanTexture::GenerateMip(VkCommandBuffer cmd, int mip) { blit.dstOffsets[1].y = height_ >> mip; blit.dstOffsets[1].z = 1; + // TODO: We could do better with the image transitions - would be enough with one per level + // for the memory barrier, then one final one for the whole stack when done. This function + // currently doesn't have a global enough view, though. + // We should also coalesce barriers across multiple texture uploads in a frame and all kinds of other stuff, but... + TransitionImageLayout2(cmd, image_, mip - 1, 1, VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + imageLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); - // Low-quality mipmap generation, but works okay. - vkCmdBlitImage(cmd, image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image_, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); + vkCmdBlitImage(cmd, image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image_, imageLayout, 1, &blit, VK_FILTER_LINEAR); TransitionImageLayout2(cmd, image_, mip - 1, 1, VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, imageLayout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); } diff --git a/Common/GPU/Vulkan/VulkanImage.h b/Common/GPU/Vulkan/VulkanImage.h index 0745e0e3523f..be0367aee083 100644 --- a/Common/GPU/Vulkan/VulkanImage.h +++ b/Common/GPU/Vulkan/VulkanImage.h @@ -21,7 +21,7 @@ class VulkanTexture { bool CreateDirect(VkCommandBuffer cmd, VulkanDeviceAllocator *allocator, int w, int h, int numMips, VkFormat format, VkImageLayout initialLayout, VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, const VkComponentMapping *mapping = nullptr); void ClearMip(VkCommandBuffer cmd, int mip, uint32_t value); void UploadMip(VkCommandBuffer cmd, int mip, int mipWidth, int mipHeight, VkBuffer buffer, uint32_t offset, size_t rowLength); // rowLength is in pixels - void GenerateMip(VkCommandBuffer cmd, int mip); + void GenerateMip(VkCommandBuffer cmd, int mip, VkImageLayout imageLayout); void EndCreate(VkCommandBuffer cmd, bool vertexTexture = false, VkImageLayout layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); // When loading mips from compute shaders, you need to pass VK_IMAGE_LAYOUT_GENERAL to the above function. diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index ba3bc058971b..e0ffa8f644b2 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -215,7 +215,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con std::vector spirv; std::string errorMessage; if (!GLSLtoSPV(vkstage_, source_.c_str(), GLSLVariant::VULKAN, spirv, &errorMessage)) { - INFO_LOG(G3D, "Shader compile to module failed: %s", errorMessage.c_str()); + WARN_LOG(G3D, "Shader compile to module failed: %s", errorMessage.c_str()); return false; } @@ -231,6 +231,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con if (vulkan->CreateShaderModule(spirv, &module_)) { ok_ = true; } else { + WARN_LOG(G3D, "vkCreateShaderModule failed"); ok_ = false; } return ok_; @@ -754,7 +755,7 @@ bool VKTexture::Create(VkCommandBuffer cmd, VulkanPushBuffer *push, const Textur } // Generate the rest of the mips automatically. for (; i < mipLevels_; i++) { - vkTex_->GenerateMip(cmd, i); + vkTex_->GenerateMip(cmd, i, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } } vkTex_->EndCreate(cmd, false); @@ -1281,7 +1282,7 @@ ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage la if (shader->Compile(vulkan_, language, data, size)) { return shader; } else { - ERROR_LOG(G3D, "Failed to compile shader: %s", (const char *)data); + ERROR_LOG(G3D, "Failed to compile shader:\n%s", (const char *)data); shader->Release(); return nullptr; } diff --git a/Common/MemArenaPosix.cpp b/Common/MemArenaPosix.cpp index 9a6166b379f2..7df84ab35c19 100644 --- a/Common/MemArenaPosix.cpp +++ b/Common/MemArenaPosix.cpp @@ -107,7 +107,7 @@ void MemArena::ReleaseView(void* view, size_t size) { u8* MemArena::Find4GBBase() { // Now, create views in high memory where there's plenty of space. -#if PPSSPP_ARCH(64BIT) && !defined(USE_ADDRESS_SANITIZER) +#if PPSSPP_ARCH(64BIT) && !defined(USE_ASAN) // We should probably just go look in /proc/self/maps for some free space. // But let's try the anonymous mmap trick, just like on 32-bit, but bigger and // aligned to 4GB for the movk trick. We can ensure that we get an aligned 4GB diff --git a/Common/Render/DrawBuffer.cpp b/Common/Render/DrawBuffer.cpp index f23c753726b6..b29513d72512 100644 --- a/Common/Render/DrawBuffer.cpp +++ b/Common/Render/DrawBuffer.cpp @@ -224,6 +224,20 @@ void DrawBuffer::DrawImage(ImageID atlas_image, float x, float y, float scale, C DrawImageStretch(atlas_image, x, y, x + w, y + h, color); } +void DrawBuffer::DrawImageCenterTexel(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color) { + const AtlasImage *image = atlas->getImage(atlas_image); + if (!image) + return; + float centerU = (image->u1 + image->u2) * 0.5f; + float centerV = (image->v1 + image->v2) * 0.5f; + V(x1, y1, color, centerU, centerV); + V(x2, y1, color, centerU, centerV); + V(x2, y2, color, centerU, centerV); + V(x1, y1, color, centerU, centerV); + V(x2, y2, color, centerU, centerV); + V(x1, y2, color, centerU, centerV); +} + void DrawBuffer::DrawImageStretch(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color) { const AtlasImage *image = atlas->getImage(atlas_image); if (!image) @@ -236,6 +250,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 40d482ef8343..f88308cf9786 100644 --- a/Common/Render/DrawBuffer.h +++ b/Common/Render/DrawBuffer.h @@ -107,7 +107,11 @@ class DrawBuffer { const Atlas *GetAtlas() const { return atlas; } 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); + + // Good for stretching out a white image without edge artifacts that I'm getting on iOS. + void DrawImageCenterTexel(ImageID atlas_image, float x1, float y1, float x2, float y2, Color color = COLOR(0xFFFFFF)); 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/Serialize/Serializer.cpp b/Common/Serialize/Serializer.cpp index 4738b829f383..8d09141a260c 100644 --- a/Common/Serialize/Serializer.cpp +++ b/Common/Serialize/Serializer.cpp @@ -174,7 +174,7 @@ void PointerWrap::DoMarker(const char *prevName, u32 arbitraryNumber) { u32 cookie = arbitraryNumber; Do(*this, cookie); if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { - _assert_msg_(false, "Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); + ERROR_LOG(SAVESTATE, "Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting savestate load...", prevName, cookie, cookie, arbitraryNumber, arbitraryNumber); SetError(ERROR_FAILURE); } } diff --git a/Common/UI/Context.cpp b/Common/UI/Context.cpp index adfa7d89d0bc..365f7ea4ebcc 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); @@ -223,7 +224,7 @@ void UIContext::FillRect(const UI::Drawable &drawable, const Bounds &bounds) { switch (drawable.type) { case UI::DRAW_SOLID_COLOR: - uidrawbuffer_->DrawImageStretch(theme->whiteImage, bounds.x, bounds.y, bounds.x2(), bounds.y2(), drawable.color); + uidrawbuffer_->DrawImageCenterTexel(theme->whiteImage, bounds.x, bounds.y, bounds.x2(), bounds.y2(), drawable.color); break; case UI::DRAW_4GRID: uidrawbuffer_->DrawImage4Grid(drawable.image, bounds.x, bounds.y, bounds.x2(), bounds.y2(), drawable.color); @@ -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 18f18bd515e0..29635b74fdec 100644 --- a/Common/UI/Context.h +++ b/Common/UI/Context.h @@ -85,6 +85,7 @@ class UIContext { 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; } @@ -98,7 +99,7 @@ class UIContext { Bounds TransformBounds(const Bounds &bounds); private: - Draw::DrawContext *draw_; + Draw::DrawContext *draw_ = nullptr; Bounds bounds_; float fontScaleX_ = 1.0f; @@ -106,7 +107,7 @@ class UIContext { UI::FontStyle *fontStyle_ = nullptr; TextDrawer *textDrawer_ = nullptr; - Draw::SamplerState *sampler_; + Draw::SamplerState *sampler_ = nullptr; Draw::Pipeline *ui_pipeline_ = nullptr; Draw::Pipeline *ui_pipeline_notex_ = nullptr; std::unique_ptr uitexture_; diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 2babf3e4baf6..2c25198db2bd 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -544,7 +544,7 @@ void InfoItem::Draw(UIContext &dc) { dc.SetFontStyle(dc.theme->uiFont); dc.DrawText(text_.c_str(), bounds_.x + paddingX, bounds_.centerY(), style.fgColor, ALIGN_VCENTER); dc.DrawText(rightText_.c_str(), bounds_.x2() - paddingX, bounds_.centerY(), style.fgColor, ALIGN_VCENTER | ALIGN_RIGHT); -// dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y + 2, dc.theme->itemDownStyle.bgColor); +// dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y + 2, dc.theme->itemDownStyle.bgColor); } ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams) @@ -556,7 +556,7 @@ ItemHeader::ItemHeader(const std::string &text, LayoutParams *layoutParams) void ItemHeader::Draw(UIContext &dc) { dc.SetFontStyle(dc.theme->uiFontSmall); dc.DrawText(text_.c_str(), bounds_.x + 4, bounds_.centerY(), dc.theme->headerStyle.fgColor, ALIGN_LEFT | ALIGN_VCENTER); - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->headerStyle.fgColor); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->headerStyle.fgColor); } void ItemHeader::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const { @@ -593,7 +593,7 @@ void PopupHeader::Draw(UIContext &dc) { } dc.DrawText(text_.c_str(), bounds_.x + tx, bounds_.centerY(), dc.theme->popupTitle.fgColor, ALIGN_LEFT | ALIGN_VCENTER); - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->popupTitle.fgColor); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2()-2, bounds_.x2(), bounds_.y2(), dc.theme->popupTitle.fgColor); if (availableWidth < tw) { dc.PopScissor(); @@ -1032,7 +1032,7 @@ void ProgressBar::GetContentDimensions(const UIContext &dc, float &w, float &h) void ProgressBar::Draw(UIContext &dc) { char temp[32]; sprintf(temp, "%i%%", (int)(progress_ * 100.0f)); - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x + bounds_.w * progress_, bounds_.y2(), 0xc0c0c0c0); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x + bounds_.w * progress_, bounds_.y2(), 0xc0c0c0c0); dc.SetFontStyle(dc.theme->uiFont); dc.DrawTextRect(temp, bounds_, 0xFFFFFFFF, ALIGN_CENTER); } diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 60685b8b8092..f022b6b137eb 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -1348,9 +1348,9 @@ void ChoiceStrip::Draw(UIContext &dc) { ViewGroup::Draw(dc); if (topTabs_) { if (orientation_ == ORIENT_HORIZONTAL) - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 4, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color ); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 4, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color ); else if (orientation_ == ORIENT_VERTICAL) - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x2() - 4, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color ); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x2() - 4, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color ); } } diff --git a/Common/x64Emitter.cpp b/Common/x64Emitter.cpp index 82c790c36f77..1b59a79aeee8 100644 --- a/Common/x64Emitter.cpp +++ b/Common/x64Emitter.cpp @@ -501,7 +501,8 @@ void XEmitter::SetJumpTarget(const FixupBranch &branch) { s64 distance = (s64)(code - branch.ptr); _assert_msg_(distance >= -0x80000000LL && distance < 0x80000000LL, "Jump target too far away, needs indirect register"); - ((s32*)branch.ptr)[-1] = (s32)distance; + const s32 distance32 = static_cast(distance); + std::memcpy(branch.ptr - sizeof(s32), &distance32, sizeof(s32)); } } diff --git a/Common/x64Emitter.h b/Common/x64Emitter.h index fe32fa0d6d92..59e8e4b0388e 100644 --- a/Common/x64Emitter.h +++ b/Common/x64Emitter.h @@ -20,6 +20,7 @@ #include "ppsspp_config.h" #include +#include #include "Common/Common.h" #include "Common/Log.h" @@ -361,9 +362,9 @@ class XEmitter protected: inline void Write8(u8 value) {*code++ = value;} - inline void Write16(u16 value) {*(u16*)code = (value); code += 2;} - inline void Write32(u32 value) {*(u32*)code = (value); code += 4;} - inline void Write64(u64 value) {*(u64*)code = (value); code += 8;} + inline void Write16(u16 value) {std::memcpy(code, &value, sizeof(u16)); code += 2;} + inline void Write32(u32 value) {std::memcpy(code, &value, sizeof(u32)); code += 4;} + inline void Write64(u64 value) {std::memcpy(code, &value, sizeof(u64)); code += 8;} public: XEmitter() { code = nullptr; flags_locked = false; } diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index dbd8a804f5ab..45a5bb25bd7c 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -79,6 +79,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ShaderColorBitmask", &flags_.ShaderColorBitmask); CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback); CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); + CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index d5cee15879ba..da0e36c1f639 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -77,6 +77,7 @@ struct CompatFlags { bool ShaderColorBitmask; bool DisableFirstFrameReadback; bool DisableRangeCulling; + bool MpegAvcWarmUp; }; class IniFile; diff --git a/Core/Config.cpp b/Core/Config.cpp index 409d080852d5..1c53e8e571e2 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -507,7 +507,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("FullscreenOnDoubleclick", &g_Config.bFullscreenOnDoubleclick, true, false, false), ReportedConfigSetting("MemStickInserted", &g_Config.bMemStickInserted, true, true, true), - ConfigSetting("LoadPlugins", &g_Config.bLoadPlugins, false, true, true), + ConfigSetting("EnablePlugins", &g_Config.bLoadPlugins, true, true, true), ConfigSetting(false), }; diff --git a/Core/Core.cpp b/Core/Core.cpp index f3cdd9b62421..e7d95c685a31 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -460,17 +460,15 @@ void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) { const char *desc = ExecExceptionTypeAsString(type); WARN_LOG(MEMMAP, "%s: Invalid destination %08x PC %08x LR %08x", desc, address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); - if (!g_Config.bIgnoreBadMemAccess) { - ExceptionInfo &e = g_exceptionInfo; - e = {}; - e.type = ExceptionType::BAD_EXEC_ADDR; - e.info = ""; - e.exec_type = type; - e.address = address; - e.pc = pc; - Core_EnableStepping(true); - host->SetDebugMode(true); - } + ExceptionInfo &e = g_exceptionInfo; + e = {}; + e.type = ExceptionType::BAD_EXEC_ADDR; + e.info = ""; + e.exec_type = type; + e.address = address; + e.pc = pc; + Core_EnableStepping(true); + host->SetDebugMode(true); } void Core_Break() { diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index e91c514383a6..060eeb4ddbb4 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 76fac1fe8871..93b6b137effd 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/CwCheat.cpp b/Core/CwCheat.cpp index be1edd7a239e..1644a04743c6 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -785,9 +785,13 @@ CheatOperation CWCheatEngine::InterpretNextOp(const CheatCode &cheat, size_t &i) return InterpretNextCwCheat(cheat, i); else if (cheat.fmt == CheatCodeFormat::TEMPAR) return InterpretNextTempAR(cheat, i); - else - _assert_(false); - return { CheatOp::Invalid }; + else { + // This shouldn't happen, but apparently does: #14082 + // Either I'm missing a path or we have memory corruption. + // Not sure whether to log here though, feels like we could end up with a + // ton of logspam... + return { CheatOp::Invalid }; + } } void CWCheatEngine::ApplyMemoryOperator(const CheatOperation &op, uint32_t(*oper)(uint32_t, uint32_t)) { diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 9c4cdeef3caa..c609e08698ef 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 000000000000..388000dbbc7a --- /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 000000000000..dd8031d90249 --- /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 000000000000..b35c8413e7ef --- /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 000000000000..65acbe9efe2b --- /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/SavedataParam.cpp b/Core/Dialog/SavedataParam.cpp index 354fd3b82348..9e139377c726 100644 --- a/Core/Dialog/SavedataParam.cpp +++ b/Core/Dialog/SavedataParam.cpp @@ -844,11 +844,13 @@ bool SavedataParam::GetExpectedHash(const std::string &dirPath, const std::strin void SavedataParam::LoadFile(const std::string& dirPath, const std::string& filename, PspUtilitySavedataFileData *fileData) { std::string filePath = dirPath + "/" + filename; - s64 readSize = -1; - if(!fileData->buf.IsValid()) + if (!fileData->buf.IsValid()) return; + u8 *buf = fileData->buf; - if(ReadPSPFile(filePath, &buf, fileData->bufSize, &readSize)) + u32 size = Memory::ValidSize(fileData->buf.ptr, fileData->bufSize); + s64 readSize = -1; + if (ReadPSPFile(filePath, &buf, size, &readSize)) fileData->size = readSize; } diff --git a/Core/FileLoaders/DiskCachingFileLoader.cpp b/Core/FileLoaders/DiskCachingFileLoader.cpp index 412f409a8454..93b2bebd383e 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.cpp +++ b/Core/FileLoaders/DiskCachingFileLoader.cpp @@ -98,6 +98,10 @@ size_t DiskCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, // While in case the cache size is too small for the entire read. while (readSize < bytes) { readSize += cache_->SaveIntoCache(backend_, absolutePos + readSize, bytes - readSize, (u8 *)data + readSize, flags); + // We're done, nothing more to read. + if (readSize == bytes) { + break; + } // If there are already-cached blocks afterward, we have to read them. size_t bytesFromCache = cache_->ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize); readSize += bytesFromCache; @@ -441,6 +445,9 @@ bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t if (!f_) { return false; } + if (size == 0) { + return true; + } s64 blockOffset = GetBlockOffset(info.block); // Before we read, make sure the buffers are flushed. diff --git a/Core/FileLoaders/LocalFileLoader.cpp b/Core/FileLoaders/LocalFileLoader.cpp index 1a6205bfab1b..232f3764a899 100644 --- a/Core/FileLoaders/LocalFileLoader.cpp +++ b/Core/FileLoaders/LocalFileLoader.cpp @@ -119,6 +119,9 @@ std::string LocalFileLoader::Path() const { } size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags) { + if (bytes == 0) + return 0; + #if PPSSPP_PLATFORM(SWITCH) // Toolchain has no fancy IO API. We must lock. std::lock_guard guard(readLock_); diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp index 4484ac811024..d5f20409bd81 100644 --- a/Core/FileSystems/ISOFileSystem.cpp +++ b/Core/FileSystems/ISOFileSystem.cpp @@ -63,13 +63,27 @@ bool parseLBN(std::string filename, u32 *sectorStart, u32 *readSize) { #pragma pack(push) #pragma pack(1) +struct u32_le_be_pair { + u8 valueLE[4]; + u8 valueBE[4]; + operator u32() const { + return valueLE[0] + (valueLE[1] << 8) + (valueLE[2] << 16) + (valueLE[3] << 24); + } +}; + +struct u16_le_be_pair { + u8 valueLE[2]; + u8 valueBE[2]; + operator u16() const { + return valueLE[0] + (valueLE[1] << 8); + } +}; + struct DirectoryEntry { u8 size; u8 sectorsInExtendedRecord; - u32_le firstDataSectorLE; // LBA - u32_be firstDataSectorBE; - u32_le dataLengthLE; // Size - u32_be dataLengthBE; + u32_le_be_pair firstDataSector; // LBA + u32_le_be_pair dataLength; // Size u8 years; u8 month; u8 day; @@ -80,38 +94,9 @@ struct DirectoryEntry { u8 flags; // 2 = directory u8 fileUnitSize; u8 interleaveGap; - u16_le volSeqNumberLE; - u16_be volSeqNumberBE; + u16_le_be_pair volSeqNumber; u8 identifierLength; //identifier comes right after u8 firstIdChar; - -#if COMMON_LITTLE_ENDIAN - u32 firstDataSector() const - { - return firstDataSectorLE; - } - u32 dataLength() const - { - return dataLengthLE; - } - u32 volSeqNumber() const - { - return volSeqNumberLE; - } -#else - u32 firstDataSector() const - { - return firstDataSectorBE; - } - u32 dataLength() const - { - return dataLengthBE; - } - u32 volSeqNumber() const - { - return volSeqNumberBE; - } -#endif }; struct DirectorySector { @@ -126,25 +111,16 @@ struct VolDescriptor { char sysid[32]; char volid[32]; char zeros[8]; - u32_le numSectorsLE; - u32_be numSectoreBE; + u32_le_be_pair numSectors; char morezeros[32]; - u16_le volSetSizeLE; - u16_be volSetSizeBE; - u16_le volSeqNumLE; - u16_be volSeqNumBE; - u16_le sectorSizeLE; - u16_be sectorSizeBE; - u32_le pathTableLengthLE; - u32_be pathTableLengthBE; - u16_le firstLETableSectorLE; - u16_be firstLETableSectorBE; - u16_le secondLETableSectorLE; - u16_be secondLETableSectorBE; - u16_le firstBETableSectorLE; - u16_be firstBETableSectorBE; - u16_le secondBETableSectorLE; - u16_be secondBETableSectorBE; + u16_le_be_pair volSetSize; + u16_le_be_pair volSeqNum; + u16_le_be_pair sectorSize; + u32_le_be_pair pathTableLength; + u16_le_be_pair firstLETableSector; + u16_le_be_pair secondLETableSector; + u16_le_be_pair firstBETableSector; + u16_le_be_pair secondBETableSector; DirectoryEntry root; char volumeSetIdentifier[128]; char publisherIdentifier[128]; @@ -192,8 +168,8 @@ ISOFileSystem::ISOFileSystem(IHandleAllocator *_hAlloc, BlockDevice *_blockDevic return; } - treeroot->startsector = desc.root.firstDataSector(); - treeroot->dirsize = desc.root.dataLength(); + treeroot->startsector = desc.root.firstDataSector; + treeroot->dirsize = desc.root.dataLength; } ISOFileSystem::~ISOFileSystem() { @@ -244,16 +220,15 @@ void ISOFileSystem::ReadDirectory(TreeEntry *root) { relative = false; } - entry->size = dir.dataLength(); - entry->startingPosition = dir.firstDataSector() * 2048; + entry->size = dir.dataLength; + entry->startingPosition = dir.firstDataSector * 2048; entry->isDirectory = !isFile; entry->flags = dir.flags; entry->parent = root; - entry->startsector = dir.firstDataSector(); - entry->dirsize = dir.dataLength(); + entry->startsector = dir.firstDataSector; + entry->dirsize = dir.dataLength; entry->valid = isFile; // Can pre-mark as valid if file, as we don't recurse into those. - // Let's not excessively spam the log - I commented this line out. - //DEBUG_LOG(FILESYS, "%s: %s %08x %08x %i", entry->isDirectory?"D":"F", entry->name.c_str(), dir.firstDataSectorLE, entry->startingPosition, entry->startingPosition); + VERBOSE_LOG(FILESYS, "%s: %s %08x %08x %i", entry->isDirectory ? "D" : "F", entry->name.c_str(), (u32)dir.firstDataSector, entry->startingPosition, entry->startingPosition); if (entry->isDirectory && !relative) { if (entry->startsector == root->startsector) { @@ -438,11 +413,11 @@ int ISOFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outd VolDescriptor desc; blockDevice->ReadBlock(16, (u8 *)&desc); - if (outlen < (u32)desc.pathTableLengthLE) { + if (outlen < (u32)desc.pathTableLength) { return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT; } else { - int block = (u16)desc.firstLETableSectorLE; - u32 size = (u32)desc.pathTableLengthLE; + int block = (u16)desc.firstLETableSector; + u32 size = Memory::ValidSize(outdataPtr, (u32)desc.pathTableLength); u8 *out = Memory::GetPointer(outdataPtr); int blocks = size / blockDevice->GetBlockSize(); diff --git a/Core/Font/PGF.cpp b/Core/Font/PGF.cpp index ae3afa57b848..deec9d589eff 100644 --- a/Core/Font/PGF.cpp +++ b/Core/Font/PGF.cpp @@ -53,8 +53,10 @@ static int getBits(int numBits, const u8 *buf, size_t pos) { const u8 done = 32 - bitoff; const u8 remaining = numBits - done; - const u32 mask = (1 << remaining) - 1; - v |= (wordbuf[wordpos + 1] & mask) << done; + if (remaining > 0) { + const u32 mask = (1 << remaining) - 1; + v |= (wordbuf[wordpos + 1] & mask) << done; + } return v; } } diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index eb4575fea6ba..e57afafe6627 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -732,6 +732,7 @@ void CallSyscall(MIPSOpcode op) int funcnum = callno & 0xFFF; int modulenum = (callno & 0xFF000) >> 12; double total = time_now_d() - start - hleSteppingTime; + _dbg_assert_msg_(total >= 0.0, "Time spent in syscall became negative"); hleSteppingTime = 0.0; updateSyscallStats(modulenum, funcnum, total); } diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index 5a0ec169a707..dda1f661436b 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -60,6 +60,8 @@ uint16_t portOffset; uint32_t minSocketTimeoutUS; uint32_t fakePoolSize = 0; SceNetAdhocMatchingContext * contexts = NULL; +char* dummyPeekBuf64k = NULL; +int dummyPeekBuf64kSize = 65536; int one = 1; bool friendFinderRunning = false; SceNetAdhocctlPeerInfo * friends = NULL; @@ -128,16 +130,19 @@ bool isPDPPortInUse(uint16_t port) { return false; } -bool isPTPPortInUse(uint16_t port, bool forListen) { +bool isPTPPortInUse(uint16_t port, bool forListen, SceNetEtherAddr* dstmac, uint16_t dstport) { // Iterate Sockets for (int i = 0; i < MAX_SOCKET; i++) { auto sock = adhocSockets[i]; if (sock != NULL && sock->type == SOCK_PTP) - // It's allowed to Listen and Open the same PTP port, But it's not allowed to Listen or Open the same PTP port twice. - if (sock->data.ptp.lport == port && - ((forListen && sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) || - (!forListen && sock->data.ptp.state != ADHOC_PTP_STATE_LISTEN))) + // It's allowed to Listen and Open the same PTP port, But it's not allowed to Listen or Open the same PTP port twice (unless destination mac or port are different). + if (sock->data.ptp.lport == port && + ((forListen && sock->data.ptp.state == ADHOC_PTP_STATE_LISTEN) || + (!forListen && sock->data.ptp.state != ADHOC_PTP_STATE_LISTEN && + sock->data.ptp.pport == dstport && dstmac != nullptr && isMacMatch(&sock->data.ptp.paddr, dstmac)))) + { return true; + } } // Unused Port return false; @@ -1136,18 +1141,18 @@ void AfterMatchingMipsCall::SetData(int ContextID, int eventId, u32_le BufAddr) bool SetMatchingInCallback(SceNetAdhocMatchingContext* context, bool IsInCB) { if (context == NULL) return false; - context->eventlock->lock(); //peerlock.lock(); + peerlock.lock(); context->IsMatchingInCB = IsInCB; - context->eventlock->unlock(); //peerlock.unlock(); + peerlock.unlock(); return IsInCB; } bool IsMatchingInCallback(SceNetAdhocMatchingContext* context) { bool inCB = false; if (context == NULL) return inCB; - context->eventlock->lock(); //peerlock.lock(); + peerlock.lock(); inCB = (context->IsMatchingInCB); - context->eventlock->unlock(); //peerlock.unlock(); + peerlock.unlock(); return inCB; } @@ -1893,13 +1898,20 @@ uint16_t getLocalPort(int sock) { return ntohs(localAddr.sin_port); } -u_long getAvailToRecv(int sock) { +u_long getAvailToRecv(int sock, int udpBufferSize) { u_long n = 0; // Typical MTU size is 1500 + int err = -1; #if defined(_WIN32) // May not be available on all platform - ioctlsocket(sock, FIONREAD, &n); + err = ioctlsocket(sock, FIONREAD, &n); #else - ioctl(sock, FIONREAD, &n); + err = ioctl(sock, FIONREAD, &n); #endif + if (err < 0) + return 0; + + if (udpBufferSize > 0 && n > 0) { + // TODO: Cap number of bytes of full DGRAM message(s) up to buffer size, but may cause Warriors Orochi 2 to get FPS drops + } return n; } diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 660c0ff1c671..421759611cdb 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -66,6 +66,7 @@ #undef EISCONN #undef EALREADY #undef ETIMEDOUT +#undef EOPNOTSUPP #define errno WSAGetLastError() #define ESHUTDOWN WSAESHUTDOWN #define ECONNABORTED WSAECONNABORTED @@ -77,6 +78,7 @@ #define EISCONN WSAEISCONN #define EALREADY WSAEALREADY #define ETIMEDOUT WSAETIMEDOUT +#define EOPNOTSUPP WSAEOPNOTSUPP inline bool connectInProgress(int errcode){ return (errcode == WSAEWOULDBLOCK || errcode == WSAEINPROGRESS || errcode == WSAEALREADY); } inline bool isDisconnected(int errcode) { return (errcode == WSAECONNRESET || errcode == WSAECONNABORTED || errcode == WSAESHUTDOWN); } #else @@ -356,8 +358,8 @@ typedef struct SceNetAdhocPtpStat { SceNetEtherAddr paddr; u16_le lport; u16_le pport; - s32_le snd_sb_cc; // Number of bytes existed in buffer to be sent/flushed? - s32_le rcv_sb_cc; // Number of bytes available in buffer to be received? + u32_le snd_sb_cc; // Number of bytes existed in sendBuffer to be sent/flushed + u32_le rcv_sb_cc; // Number of bytes available in recvBuffer to be received s32_le state; } PACK SceNetAdhocPtpStat; @@ -915,6 +917,8 @@ extern int defaultWlanChannel; // Default WLAN Channel for Auto, JPCSP uses 11 extern uint32_t fakePoolSize; extern SceNetAdhocMatchingContext * contexts; +extern char* dummyPeekBuf64k; +extern int dummyPeekBuf64kSize; extern int one; extern bool friendFinderRunning; extern SceNetAdhocctlPeerInfo * friends; @@ -972,9 +976,11 @@ bool isPDPPortInUse(uint16_t port); * Check whether PTP Port is in use or not (only sockets with non-Listening state will be considered as in use) * @param port To-be-checked Port Number * @param forListen to check for listening or non-listening port + * @param dstmac destination address (non-listening only) + * @param dstport destination port (non-listening only) * @return 1 if in use or... 0 */ -bool isPTPPortInUse(uint16_t port, bool forListen); +bool isPTPPortInUse(uint16_t port, bool forListen, SceNetEtherAddr* dstmac = nullptr, uint16_t dstport = 0); // Convert MAC address to string std::string mac2str(SceNetEtherAddr* mac); @@ -1269,8 +1275,10 @@ bool isPrivateIP(uint32_t ip); /* * Get Number of bytes available in buffer to be Received + * @param sock fd + * @param udpBufferSize (UDP only) */ -u_long getAvailToRecv(int sock); +u_long getAvailToRecv(int sock, int udpBufferSize = 0); /* * Get UDP Socket Max Message Size diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 8c27ed16c62b..04b2e40831bc 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -563,12 +563,11 @@ static int sceGeGetMtx(int type, u32 matrixPtr) { } static u32 sceGeGetCmd(int cmd) { - INFO_LOG(SCEGE, "sceGeGetCmd(%i)", cmd); if (cmd >= 0 && cmd < (int)ARRAY_SIZE(gstate.cmdmem)) { - return gstate.cmdmem[cmd]; // Does not mask away the high bits. - } else { - return SCE_KERNEL_ERROR_INVALID_INDEX; + // Does not mask away the high bits. + return hleLogSuccessInfoX(SCEGE, gstate.cmdmem[cmd]); } + return hleLogError(SCEGE, SCE_KERNEL_ERROR_INVALID_INDEX); } static int sceGeGetStack(int index, u32 stackPtr) { diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 43e0700b98ca..9abfd60f5168 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -1027,10 +1027,11 @@ static bool __IoRead(int &result, int id, u32 data_addr, int size, int &us) { return true; } else if (Memory::IsValidAddress(data_addr)) { CBreakPoints::ExecMemCheck(data_addr, true, size, currentMIPS->pc); - u8 *data = (u8*) Memory::GetPointer(data_addr); + u8 *data = (u8 *)Memory::GetPointer(data_addr); + u32 validSize = Memory::ValidSize(data_addr, size); if (f->npdrm) { - result = npdrmRead(f, data, size); - currentMIPS->InvalidateICache(data_addr, size); + result = npdrmRead(f, data, validSize); + currentMIPS->InvalidateICache(data_addr, validSize); return true; } @@ -1046,17 +1047,17 @@ static bool __IoRead(int &result, int id, u32 data_addr, int size, int &us) { AsyncIOEvent ev = IO_EVENT_READ; ev.handle = f->handle; ev.buf = data; - ev.bytes = size; + ev.bytes = validSize; ev.invalidateAddr = data_addr; ioManager.ScheduleOperation(ev); return false; } else { if (GetIOTimingMethod() != IOTIMING_REALISTIC) { - result = (int) pspFileSystem.ReadFile(f->handle, data, size); + result = (int)pspFileSystem.ReadFile(f->handle, data, validSize); } else { - result = (int) pspFileSystem.ReadFile(f->handle, data, size, us); + result = (int)pspFileSystem.ReadFile(f->handle, data, validSize, us); } - currentMIPS->InvalidateICache(data_addr, size); + currentMIPS->InvalidateICache(data_addr, validSize); return true; } } else { @@ -1136,12 +1137,13 @@ static bool __IoWrite(int &result, int id, u32 data_addr, int size, int &us) { } const void *data_ptr = Memory::GetPointer(data_addr); + const u32 validSize = Memory::ValidSize(data_addr, size); // Let's handle stdout/stderr specially. if (id == PSP_STDOUT || id == PSP_STDERR) { const char *str = (const char *) data_ptr; - const int str_size = size == 0 ? 0 : (str[size - 1] == '\n' ? size - 1 : size); + const int str_size = size <= 0 ? 0 : (str[validSize - 1] == '\n' ? validSize - 1 : validSize); INFO_LOG(SCEIO, "%s: %.*s", id == 1 ? "stdout" : "stderr", str_size, str); - result = size; + result = validSize; return true; } u32 error; @@ -1174,15 +1176,15 @@ static bool __IoWrite(int &result, int id, u32 data_addr, int size, int &us) { AsyncIOEvent ev = IO_EVENT_WRITE; ev.handle = f->handle; ev.buf = (u8 *) data_ptr; - ev.bytes = size; + ev.bytes = validSize; ev.invalidateAddr = 0; ioManager.ScheduleOperation(ev); return false; } else { if (GetIOTimingMethod() != IOTIMING_REALISTIC) { - result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size); + result = (int)pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, validSize); } else { - result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size, us); + result = (int)pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, validSize, us); } } return true; @@ -2017,20 +2019,22 @@ static int sceIoChangeAsyncPriority(int id, int priority) { return hleLogSuccessI(SCEIO, 0); } -static int sceIoCloseAsync(int id) -{ +static int sceIoCloseAsync(int id) { u32 error; FileNode *f = __IoGetFd(id, error); - if (f) { - f->closePending = true; - - auto ¶ms = asyncParams[id]; - params.op = IoAsyncOp::CLOSE; - IoStartAsyncThread(id, f); - return hleLogSuccessI(SCEIO, 0); - } else { + if (!f) { return hleLogError(SCEIO, error, "bad file descriptor"); } + if (f->asyncBusy()) { + return hleLogWarning(SCEIO, SCE_KERNEL_ERROR_ASYNC_BUSY, "async busy"); + } + + f->closePending = true; + + auto ¶ms = asyncParams[id]; + params.op = IoAsyncOp::CLOSE; + IoStartAsyncThread(id, f); + return hleLogSuccessI(SCEIO, 0); } static u32 sceIoSetAsyncCallback(int id, u32 clbckId, u32 clbckArg) diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index bebf6f62d71b..5907848036e2 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -896,8 +896,6 @@ const HLEFunction ThreadManForKernel[] = {0X9944F31F, &WrapI_I, "sceKernelSuspendThread", 'i', "i", HLE_KERNEL_SYSCALL }, {0X75156E8F, &WrapI_I, "sceKernelResumeThread", 'i', "i", HLE_KERNEL_SYSCALL }, {0X94416130, &WrapU_UUUU, "sceKernelGetThreadmanIdList", 'x', "xxxx", HLE_KERNEL_SYSCALL }, - {0X28B6489C, &WrapI_I, "sceKernelDeleteSema", 'i', "i", HLE_KERNEL_SYSCALL }, - {0XEF9E4C70, &WrapU_I, "sceKernelDeleteEventFlag", 'x', "i", HLE_KERNEL_SYSCALL }, {0x278c0df5, &WrapI_IU, "sceKernelWaitThreadEnd", 'i', "ix", HLE_KERNEL_SYSCALL }, {0xd6da4ba1, &WrapI_CUIIU, "sceKernelCreateSema", 'i', "sxiix", HLE_KERNEL_SYSCALL }, {0x28b6489c, &WrapI_I, "sceKernelDeleteSema", 'i', "i", HLE_KERNEL_SYSCALL }, diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 45c6d2d92982..a60b07e71f88 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" @@ -1880,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 e0a5eac4d467..92c5fee535e2 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1336,32 +1336,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/sceMp3.cpp b/Core/HLE/sceMp3.cpp index b3bdcb9b1860..9b1056e943b3 100644 --- a/Core/HLE/sceMp3.cpp +++ b/Core/HLE/sceMp3.cpp @@ -23,6 +23,7 @@ #include "Core/Config.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" +#include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceMp3.h" #include "Core/HW/MediaEngine.h" #include "Core/MemMap.h" @@ -33,6 +34,7 @@ static const u32 ERROR_MP3_INVALID_HANDLE = 0x80671001; static const u32 ERROR_MP3_UNRESERVED_HANDLE = 0x80671102; static const u32 ERROR_MP3_NOT_YET_INIT_HANDLE = 0x80671103; static const u32 ERROR_MP3_NO_RESOURCE_AVAIL = 0x80671201; +static const u32 ERROR_MP3_BAD_SAMPLE_RATE = 0x80671302; static const u32 ERROR_MP3_BAD_RESET_FRAME = 0x80671501; static const u32 ERROR_MP3_BAD_ADDR = 0x80671002; static const u32 ERROR_MP3_BAD_SIZE = 0x80671003; @@ -157,19 +159,21 @@ void __Mp3DoState(PointerWrap &p) { } static int sceMp3Decode(u32 mp3, u32 outPcmPtr) { - DEBUG_LOG(ME, "sceMp3Decode(%08x,%08x)", mp3, outPcmPtr); - AuCtx *ctx = getMp3Ctx(mp3); if (!ctx) { - ERROR_LOG(ME, "%s: bad mp3 handle %08x", __FUNCTION__, mp3); - return -1; + if (mp3 >= MP3_MAX_HANDLES) + return hleLogError(ME, ERROR_MP3_INVALID_HANDLE, "invalid handle"); + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "unreserved handle"); + } else if (ctx->Version < 0 || ctx->AuBuf == 0) { + return hleLogError(ME, ERROR_MP3_NOT_YET_INIT_HANDLE, "not yet init"); } int pcmBytes = ctx->AuDecode(outPcmPtr); if (pcmBytes > 0) { // decode data successfully, delay thread - return hleDelayResult(pcmBytes, "mp3 decode", mp3DecodeDelay); + return hleDelayResult(hleLogSuccessI(ME, pcmBytes), "mp3 decode", mp3DecodeDelay); } + // Should already have logged. return pcmBytes; } @@ -377,6 +381,7 @@ static int FindMp3Header(AuCtx *ctx, int &header, int end) { } static int sceMp3Init(u32 mp3) { + int sdkver = sceKernelGetCompiledSdkVersion(); AuCtx *ctx = getMp3Ctx(mp3); if (!ctx) { if (mp3 >= MP3_MAX_HANDLES) @@ -397,30 +402,41 @@ static int sceMp3Init(u32 mp3) { // Parse the Mp3 header int layerBits = (header >> 17) & 0x3; int versionBits = (header >> 19) & 0x3; - ctx->SamplingRate = __CalculateMp3SampleRates((header >> 10) & 0x3, versionBits); - ctx->Channels = __CalculateMp3Channels((header >> 6) & 0x3); - ctx->BitRate = __CalculateMp3Bitrates((header >> 12) & 0xF, versionBits, layerBits); - ctx->MaxOutputSample = CalculateMp3SamplesPerFrame(versionBits, layerBits); - ctx->freq = ctx->SamplingRate; + int bitrate = __CalculateMp3Bitrates((header >> 12) & 0xF, versionBits, layerBits); + int samplerate = __CalculateMp3SampleRates((header >> 10) & 0x3, versionBits);; + int channels = __CalculateMp3Channels((header >> 6) & 0x3); - DEBUG_LOG(ME, "sceMp3Init(): channels=%i, samplerate=%iHz, bitrate=%ikbps", ctx->Channels, ctx->SamplingRate, ctx->BitRate); + DEBUG_LOG(ME, "sceMp3Init(): channels=%i, samplerate=%iHz, bitrate=%ikbps, layerBits=%d ,versionBits=%d,HEADER: %08x", channels, samplerate, bitrate, layerBits, versionBits, header); if (layerBits != 1) { // TODO: Should return ERROR_AVCODEC_INVALID_DATA. WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not layer 3"); } + if (bitrate == 0 || bitrate == -1) { + return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid bitrate v%d l%d rate %04x", versionBits, layerBits, (header >> 12) & 0xF), "mp3 init", PARSE_DELAY_MS); + } + if (samplerate == -1) { + return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid sample rate v%d l%d rate %02x", versionBits, layerBits, (header >> 10) & 0x3), "mp3 init", PARSE_DELAY_MS); + } + + // Before we allow init, newer SDK versions next require at least 156 bytes. + // That happens to be the size of the first frame header for VBR. + if (sdkver >= 0x06000000 && ctx->readPos < 156) { + return hleDelayResult(hleLogError(ME, SCE_KERNEL_ERROR_INVALID_VALUE, "insufficient mp3 data for init"), "mp3 init", PARSE_DELAY_MS); + } + + ctx->SamplingRate = samplerate; + ctx->Channels = channels; + ctx->BitRate = bitrate; + ctx->MaxOutputSample = CalculateMp3SamplesPerFrame(versionBits, layerBits); + ctx->freq = ctx->SamplingRate; + if (versionBits != 3) { // TODO: Should return 0x80671301 (unsupported version?) WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not MPEG v1"); } - if (ctx->BitRate == 0 || ctx->BitRate == -1) { - return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid bitrate v%d l%d rate %04x", versionBits, layerBits, (header >> 12) & 0xF), "mp3 init", PARSE_DELAY_MS); - } - if (ctx->SamplingRate == -1) { - return hleDelayResult(hleReportError(ME, ERROR_AVCODEC_INVALID_DATA, "invalid sample rate v%d l%d rate %02x", versionBits, layerBits, (header >> 10) & 0x3), "mp3 init", PARSE_DELAY_MS); - } else if (ctx->SamplingRate != 44100) { - // TODO: Should return 0x80671302 (unsupported sample rate?) - WARN_LOG_REPORT(ME, "sceMp3Init: invalid data: not 44.1kHz"); + if (samplerate != 44100 && sdkver < 3090500) { + return hleDelayResult(hleLogError(ME, ERROR_MP3_BAD_SAMPLE_RATE, "invalid data: not 44.1kHz"), "mp3 init", PARSE_DELAY_MS); } // Based on bitrate, we can calculate the frame size in bytes. @@ -432,11 +448,8 @@ static int sceMp3Init(u32 mp3) { ctx->Version = versionBits; - // for mp3, if required freq is 48000, reset resampling Frequency to 48000 seems get better sound quality (e.g. Miku Custom BGM) - // TODO: Isn't this backwards? Woudln't we want to read as 48kHz and resample to 44.1kHz? - if (ctx->freq == 48000) { - ctx->decoder->SetResampleFrequency(ctx->freq); - } + // This tells us to resample to the same frequency it decodes to. + ctx->decoder->SetResampleFrequency(ctx->freq); return hleDelayResult(hleLogSuccessI(ME, 0), "mp3 init", PARSE_DELAY_MS); } @@ -707,7 +720,7 @@ const HLEFunction sceMp3[] = { {0X8AB81558, &WrapU_V, "sceMp3StartEntry", 'x', "" }, {0X8F450998, &WrapI_U, "sceMp3GetSamplingRate", 'i', "x" }, {0XA703FE0F, &WrapI_UUUU, "sceMp3GetInfoToAddStreamData", 'i', "xppp" }, - {0XD021C0FB, &WrapI_UU, "sceMp3Decode", 'i', "xx" }, + {0XD021C0FB, &WrapI_UU, "sceMp3Decode", 'i', "xp" }, {0XD0A56296, &WrapI_U, "sceMp3CheckStreamDataNeeded", 'i', "x" }, {0XD8F54A51, &WrapI_U, "sceMp3GetLoopNum", 'i', "x" }, {0XF5478233, &WrapI_U, "sceMp3ReleaseMp3Handle", 'i', "x" }, diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 298f2423d90d..b98f57d7b409 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -34,6 +34,8 @@ #include "Core/Reporting.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" +#include "Core/HLE/sceKernelMemory.h" +#include "Core/Core.h" // MPEG AVC elementary stream. static const int MPEG_AVC_ES_SIZE = 2048; // MPEG packet size. @@ -1584,11 +1586,13 @@ static int sceMpegGetAvcAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) ERROR_LOG_REPORT(ME, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): invalid ringbuffer address", mpeg, streamId, auAddr, attrAddr); return -1; } - - if (ctx->mpegwarmUp < MPEG_WARMUP_FRAMES) { - DEBUG_LOG(ME, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): warming up", mpeg, streamId, auAddr, attrAddr); - ctx->mpegwarmUp++; - return ERROR_MPEG_NO_DATA; + + if (PSP_CoreParameter().compat.flags().MpegAvcWarmUp) { + if (ctx->mpegwarmUp == 0) { + DEBUG_LOG(ME, "sceMpegGetAvcAu(%08x, %08x, %08x, %08x): warming up", mpeg, streamId, auAddr, attrAddr); + ctx->mpegwarmUp++; + return ERROR_MPEG_NO_DATA; + } } SceMpegAu avcAu; @@ -1686,12 +1690,6 @@ static int sceMpegGetAtracAu(u32 mpeg, u32 streamId, u32 auAddr, u32 attrAddr) return -1; } - if (ctx->mpegwarmUp < MPEG_WARMUP_FRAMES) { - DEBUG_LOG(ME, "sceMpegGetAtracAu(%08x, %08x, %08x, %08x): warming up", mpeg, streamId, auAddr, attrAddr); - ctx->mpegwarmUp++; - return ERROR_MPEG_NO_DATA; - } - SceMpegAu atracAu; atracAu.read(auAddr); diff --git a/Core/HLE/sceNet.cpp b/Core/HLE/sceNet.cpp index 448b0ba66e19..48a51688b32e 100644 --- a/Core/HLE/sceNet.cpp +++ b/Core/HLE/sceNet.cpp @@ -203,6 +203,7 @@ void __NetInit() { g_adhocServerIP.in.sin_port = htons(SERVER_PORT); //27312 // Maybe read this from config too g_adhocServerIP.in.sin_addr.s_addr = INADDR_NONE; + dummyPeekBuf64k = (char*)malloc(dummyPeekBuf64kSize); InitLocalhostIP(); SceNetEtherAddr mac; @@ -235,6 +236,8 @@ void __NetShutdown() { // Since PortManager supposed to be general purpose for whatever port forwarding PPSSPP needed, may be we shouldn't clear & restore ports in here? it will be cleared and restored by PortManager's destructor when exiting PPSSPP anyway __UPnPShutdown(); + + free(dummyPeekBuf64k); } static void __UpdateApctlHandlers(u32 oldState, u32 newState, u32 flag, u32 error) { @@ -591,7 +594,7 @@ u32 Net_Term() { } static u32 sceNetTerm() { - WARN_LOG(SCENET, "sceNetTerm()"); + WARN_LOG(SCENET, "sceNetTerm() at %08x", currentMIPS->pc); int retval = Net_Term(); // Give time to make sure everything are cleaned up diff --git a/Core/HLE/sceNetAdhoc.cpp b/Core/HLE/sceNetAdhoc.cpp index 0b6e97b4727d..cb0537b80ab0 100644 --- a/Core/HLE/sceNetAdhoc.cpp +++ b/Core/HLE/sceNetAdhoc.cpp @@ -109,6 +109,7 @@ int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock int FlushPtpSocket(int socketId); int NetAdhocGameMode_DeleteMaster(); int NetAdhocctl_ExitGameMode(); +int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect = true); static int sceNetAdhocPdpSend(int id, const char* mac, u32 port, void* data, int len, int timeout, int flag); static int sceNetAdhocPdpRecv(int id, void* addr, void* port, void* buf, void* dataLength, u32 timeout, int flag); @@ -180,8 +181,13 @@ static void __GameModeNotify(u64 userdata, int cyclesLate) { int recvd = 0; for (auto& gma : replicaGameModeAreas) { // Either replicas new data has been received or that player has been disconnected - if (gma.dataUpdated || gma.updateTimestamp == 0) + if (gma.dataUpdated || gma.updateTimestamp == 0) { recvd++; + // Since we're able to receive data, now we're certain that remote player is listening and ready to receive data, so we send initial data one more time in case they're not listening yet on previous attempt (ie. Pocket Pool) + if (gma.dataUpdated) { + sceNetAdhocPdpSend(gameModeSocket, (const char*)&gma.mac, ADHOC_GAMEMODE_PORT, masterGameModeArea.data, masterGameModeArea.size, 0, ADHOC_F_NONBLOCK); + } + } } // Resume blocked thread u64 now = CoreTiming::GetGlobalTimeUsScaled(); @@ -406,8 +412,11 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { memset(&sin, 0, sizeof(sin)); socklen_t sinlen = sizeof(sin); - int ret = recvfrom(uid, (char*)req.buffer, *req.length, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + int ret = recvfrom(uid, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); int sockerr = errno; + if (ret > 0 && *req.length > 0) + memcpy(req.buffer, dummyPeekBuf64k, std::min(ret, *req.length)); // Note: UDP must not be received partially, otherwise leftover data in socket's buffer will be discarded if (ret >= 0 && ret <= *req.length) { @@ -477,6 +486,7 @@ int DoBlockingPdpRecv(int uid, AdhocSocketRequest& req, s64& result) { } result = ERROR_NET_ADHOC_NOT_ENOUGH_SPACE; } + // FIXME: Blocking operation with infinite timeout(0) should never get a TIMEOUT error, right? May be we should return INVALID_ARG instead if it was infinite timeout (0)? else result = ERROR_NET_ADHOC_TIMEOUT; // ERROR_NET_ADHOC_INVALID_ARG; // ERROR_NET_ADHOC_DISCONNECTED @@ -555,10 +565,14 @@ int DoBlockingPtpSend(int uid, AdhocSocketRequest& req, s64& result) { DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", req.id, ptpsocket.lport, ret, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + // Set to Established on successful Send when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + // Return Success result = 0; } - else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { u64 now = (u64)(time_now_d() * 1000000.0); if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; @@ -604,9 +618,13 @@ int DoBlockingPtpRecv(int uid, AdhocSocketRequest& req, s64& result) { if (peer != NULL) peer->last_recv = CoreTiming::GetGlobalTimeUsScaled(); peerlock.unlock(); + // Set to Established on successful Recv when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + result = 0; } - else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK)) { + else if (ret == SOCKET_ERROR && (sockerr == EAGAIN || sockerr == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (sockerr == ENOTCONN || connectInProgress(sockerr))))) { u64 now = (u64)(time_now_d() * 1000000.0); if (req.timeout == 0 || now - req.startTime <= req.timeout) { return -1; @@ -1675,7 +1693,11 @@ static int sceNetAdhocPdpRecv(int id, void *addr, void * port, void *buf, void * // Receive Data. PDP always sent in full size or nothing(failed), recvfrom will always receive in full size as requested (blocking) or failed (non-blocking). If available UDP data is larger than buffer, excess data is lost. // Should peek first for the available data size if it's more than len return ERROR_NET_ADHOC_NOT_ENOUGH_SPACE along with required size in len to prevent losing excess data - received = recvfrom(pdpsocket.id, (char*)buf, *len, MSG_PEEK | MSG_NOSIGNAL | MSG_TRUNC, (sockaddr*)&sin, &sinlen); + // On Windows: MSG_TRUNC are not supported on recvfrom (socket error WSAEOPNOTSUPP), so we use dummy buffer as an alternative + received = recvfrom(pdpsocket.id, dummyPeekBuf64k, dummyPeekBuf64kSize, MSG_PEEK | MSG_NOSIGNAL, (sockaddr*)&sin, &sinlen); + if (received > 0 && *len > 0) + memcpy(buf, dummyPeekBuf64k, std::min(received, *len)); + if (received != SOCKET_ERROR && *len < received) { WARN_LOG(SCENET, "sceNetAdhocPdpRecv[%i:%u]: Peeked %u/%u bytes from %s:%u\n", id, getLocalPort(pdpsocket.id), received, *len, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); *len = received; @@ -1794,14 +1816,16 @@ int NetAdhoc_SetSocketAlert(int id, s32_le flag) { adhocSockets[id - 1]->flags = flg; adhocSockets[id - 1]->alerted_flags = 0; - return hleDelayResult(0, "set socket alert delay", 1000); + return 0; } // Flags seems to be bitmasks of ADHOC_F_ALERT... (need more games to test this) int sceNetAdhocSetSocketAlert(int id, int flag) { WARN_LOG_REPORT_ONCE(sceNetAdhocSetSocketAlert, SCENET, "UNTESTED sceNetAdhocSetSocketAlert(%d, %08x) at %08x", id, flag, currentMIPS->pc); - return hleLogDebug(SCENET, NetAdhoc_SetSocketAlert(id, flag), ""); + int retval = NetAdhoc_SetSocketAlert(id, flag); + hleDelayResult(retval, "set socket alert delay", 1000); + return hleLogDebug(SCENET, retval, ""); } int PollAdhocSocket(SceNetAdhocPollSd* sds, int count, int timeout, int nonblock) { @@ -2347,7 +2371,7 @@ u32 NetAdhocctl_Disconnect() { } // Return Success, some games might ignore returned value and always treat it as success, otherwise repeatedly calling this function - return hleDelayResult(0, "disconnect delay", us); + return 0; } // Library uninitialized @@ -2370,7 +2394,7 @@ static u32 sceNetAdhocctlDelHandler(u32 handlerID) { if (adhocctlHandlers.find(handlerID) != adhocctlHandlers.end()) { adhocctlHandlers.erase(handlerID); - INFO_LOG(SCENET, "sceNetAdhocctlDelHandler(%d)", handlerID); + INFO_LOG(SCENET, "sceNetAdhocctlDelHandler(%d) at %08x", handlerID, currentMIPS->pc); } else { WARN_LOG(SCENET, "sceNetAdhocctlDelHandler(%d): Invalid Handler ID", handlerID); } @@ -2381,6 +2405,7 @@ static u32 sceNetAdhocctlDelHandler(u32 handlerID) { int NetAdhocctl_Term() { if (netAdhocctlInited) { if (adhocctlState != ADHOCCTL_STATE_DISCONNECTED) { + // Note: This might block current thread if the first attempt to send OPCODE_DISCONNECT to AdhocServer failed with EAGAIN error if (netAdhocGameModeEntered) NetAdhocctl_ExitGameMode(); else @@ -2393,6 +2418,8 @@ int NetAdhocctl_Term() { friendFinderThread.join(); } + // TODO: May need to block current thread to make sure all Adhocctl callbacks have been fully executed before terminating Adhoc PSPThread (ie. threadAdhocID). + // Clear GameMode resources NetAdhocGameMode_DeleteMaster(); deleteAllGMB(); @@ -2427,7 +2454,7 @@ int NetAdhocctl_Term() { int sceNetAdhocctlTerm() { // WLAN might be disabled in the middle of successfull multiplayer, but we still need to cleanup right? - INFO_LOG(SCENET, "sceNetAdhocctlTerm()"); + INFO_LOG(SCENET, "sceNetAdhocctlTerm() at %08x", currentMIPS->pc); //if (netAdhocMatchingInited) NetAdhocMatching_Term(); int retval = NetAdhocctl_Term(); @@ -2645,7 +2672,7 @@ int NetAdhocctl_Create(const char* groupName) { hleEatMicro(us); // Return Success // FIXME: When tested using JPCSP + official prx files it seems sceNetAdhocctlCreate switching to a different thread for at least 100ms after returning success and before executing the next line. - return hleDelayResult(0, "create/connect/join delay", adhocEventPollDelay); + return 0; } // Connected State @@ -2928,8 +2955,10 @@ static int sceNetAdhocGetPdpStat(u32 structSize, u32 structAddr) { // Valid Socket Entry auto sock = adhocSockets[j]; if (sock != NULL && sock->type == SOCK_PDP) { - // Set available bytes to be received. With FIOREAD There might be ghosting 1 byte in recv buffer when remote peer's socket got closed (ie. Warriors Orochi 2) Attempting to recv this ghost 1 byte will result to socket error 10054 (may need to disable SIO_UDP_CONNRESET error) - sock->data.pdp.rcv_sb_cc = getAvailToRecv(sock->data.pdp.id); + // Set available bytes to be received. With FIONREAD There might be ghosting 1 byte in recv buffer when remote peer's socket got closed (ie. Warriors Orochi 2) Attempting to recv this ghost 1 byte will result to socket error 10054 (may need to disable SIO_UDP_CONNRESET error) + // It seems real PSP respecting the socket buffer size arg, so we may need to cap the value up to the buffer size arg since we use larger buffer, for PDP/UDP the total size must not contains partial/truncated message to avoid data loss. + // TODO: We may need to manage PDP messages ourself by reading each msg 1-by-1 and moving it to our internal buffer(msg array) in order to calculate the correct messages size that can fit into buffer size when there are more than 1 messages in the recv buffer (simulate FIONREAD) + sock->data.pdp.rcv_sb_cc = getAvailToRecv(sock->data.pdp.id, sock->buffer_size); // Copy Socket Data from Internal Memory memcpy(&buf[i], &sock->data.pdp, sizeof(SceNetAdhocPdpStat)); @@ -3022,6 +3051,8 @@ static int sceNetAdhocGetPtpStat(u32 structSize, u32 structAddr) { // Set available bytes to be received sock->data.ptp.rcv_sb_cc = getAvailToRecv(sock->data.ptp.id); + // It seems real PSP respecting the socket buffer size arg, so we may need to cap the value to the buffer size arg since we use larger buffer + sock->data.ptp.rcv_sb_cc = std::min(sock->data.ptp.rcv_sb_cc, (u32_le)sock->buffer_size); // Copy Socket Data from internal Memory memcpy(&buf[i], &sock->data.ptp, sizeof(SceNetAdhocPtpStat)); @@ -3089,7 +3120,7 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Valid Addresses. FIXME: MAC only valid after successful attempt to Create/Connect/Join a Group? (ie. adhocctlCurrentMode != ADHOCCTL_MODE_NONE) if ((adhocctlCurrentMode != ADHOCCTL_MODE_NONE) && saddr != NULL && isLocalMAC(saddr) && daddr != NULL && !isBroadcastMAC(daddr) && !isZeroMAC(daddr)) { // Dissidia 012 will try to reOpen the port without Closing the old one first when PtpConnect failed to try again. - if (isPTPPortInUse(sport, false)) { + if (isPTPPortInUse(sport, false, daddr, dport)) { // FIXME: When PORT_IN_USE error occured it seems the index to the socket id also increased, which means it tries to create & bind the socket first and then closes it due to failed to bind return hleLogDebug(SCENET, ERROR_NET_ADHOC_PORT_IN_USE, "port in use"); } @@ -3191,6 +3222,13 @@ static int sceNetAdhocPtpOpen(const char *srcmac, int sport, const char *dstmac, // Switch to non-blocking for futher usage changeBlockingMode(tcpsocket, 1); + // Initiate PtpConnect (ie. The Warrior seems to try to PtpSend right after PtpOpen without trying to PtpConnect first) + NetAdhocPtp_Connect(i + 1, rexmt_int, 1, false); + + // Workaround to give some time to get connected before returning from PtpOpen over high latency + if (g_Config.bForcedFirstConnect && internal->attemptCount == 1) + hleDelayResult(i + 1, "delayed ptpopen", rexmt_int); + // Return PTP Socket Pointer return hleLogDebug(SCENET, i + 1, "success"); } @@ -3438,19 +3476,7 @@ static int sceNetAdhocPtpAccept(int id, u32 peerMacAddrPtr, u32 peerPortPtr, int return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } -/** - * Adhoc Emulator PTP Connection Opener - * @param id Socket File Descriptor - * @param timeout Connect Timeout (in Microseconds) - * @param flag Nonblocking Flag - * @return 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_SOCKET_ID, ADHOC_SOCKET_DELETED, ADHOC_CONNECTION_REFUSED, ADHOC_SOCKET_ALERTED, ADHOC_WOULD_BLOCK, ADHOC_TIMEOUT, ADHOC_NOT_OPENED, ADHOC_THREAD_ABORTED, NET_INTERNAL - */ -static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { - INFO_LOG(SCENET, "sceNetAdhocPtpConnect(%i, %i, %i) at %08x", id, timeout, flag, currentMIPS->pc); - if (!g_Config.bEnableWlan) { - return -1; - } - +int NetAdhocPtp_Connect(int id, int timeout, int flag, bool allowForcedConnect) { // Library is initialized if (netAdhocInited) { @@ -3477,21 +3503,21 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { // Target Address sockaddr_in sin; memset(&sin, 0, sizeof(sin)); - + // Setup Target Address // sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_port = htons(ptpsocket.pport + portOffset); - + // Grab Peer IP - if (resolveMAC(&ptpsocket.paddr, (uint32_t *)&sin.sin_addr.s_addr)) { + if (resolveMAC(&ptpsocket.paddr, (uint32_t*)&sin.sin_addr.s_addr)) { // Some games (ie. PSP2) might try to talk to it's self, not sure if they talked through WAN or LAN when using public Adhoc Server tho sin.sin_port = htons(ptpsocket.pport + ((isOriPort && !isPrivateIP(sin.sin_addr.s_addr)) ? 0 : portOffset)); // Connect Socket to Peer // NOTE: Based on what i read at stackoverflow, The First Non-blocking POSIX connect will always returns EAGAIN/EWOULDBLOCK because it returns without waiting for ACK/handshake, But GvG Next Plus is treating non-blocking PtpConnect just like blocking connect, May be on a real PSP the first non-blocking sceNetAdhocPtpConnect can be successfull? - int connectresult = connect(ptpsocket.id, (sockaddr *)&sin, sizeof(sin)); - + int connectresult = connect(ptpsocket.id, (sockaddr*)&sin, sizeof(sin)); + // Grab Error Code int errorcode = errno; @@ -3508,12 +3534,12 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); // Set Connected State ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; - + INFO_LOG(SCENET, "sceNetAdhocPtpConnect[%i:%u]: Already Connected to %s:%u", id, ptpsocket.lport, inet_ntoa(sin.sin_addr), ptpsocket.pport); // Success return 0; } - + // Error handling else if (connectresult == SOCKET_ERROR) { // Connection in Progress @@ -3523,7 +3549,7 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { socket->lastAttempt = CoreTiming::GetGlobalTimeUsScaled(); // Blocking Mode // Workaround: Forcing first attempt to be blocking to prevent issue related to lobby or high latency networks. (can be useful for GvG Next Plus, Dissidia 012, and Fate Unlimited Codes) - if (!flag || (g_Config.bForcedFirstConnect && socket->attemptCount == 1)) { + if (!flag || (allowForcedConnect && g_Config.bForcedFirstConnect && socket->attemptCount == 1)) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; return WaitBlockingAdhocSocket(threadSocketId, PTP_CONNECT, id, nullptr, nullptr, (flag) ? std::max((int)socket->retry_interval, timeout) : timeout, nullptr, nullptr, "ptp connect"); @@ -3539,23 +3565,39 @@ static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { } } } - + // Peer not found return hleLogDebug(SCENET, ERROR_NET_ADHOC_INVALID_ADDR, "invalid address"); // ERROR_NET_ADHOC_WOULD_BLOCK / ERROR_NET_ADHOC_TIMEOUT } - + // Not a valid Client Socket return hleLogDebug(SCENET, ERROR_NET_ADHOC_NOT_OPENED, "not opened"); } - + // Invalid Socket return hleLogDebug(SCENET, ERROR_NET_ADHOC_INVALID_SOCKET_ID, "invalid socket id"); } - + // Library is uninitialized return hleLogDebug(SCENET, ERROR_NET_ADHOC_NOT_INITIALIZED, "not initialized"); } +/** + * Adhoc Emulator PTP Connection Opener + * @param id Socket File Descriptor + * @param timeout Connect Timeout (in Microseconds) + * @param flag Nonblocking Flag + * @return 0 on success or... ADHOC_NOT_INITIALIZED, ADHOC_INVALID_ARG, ADHOC_INVALID_SOCKET_ID, ADHOC_SOCKET_DELETED, ADHOC_CONNECTION_REFUSED, ADHOC_SOCKET_ALERTED, ADHOC_WOULD_BLOCK, ADHOC_TIMEOUT, ADHOC_NOT_OPENED, ADHOC_THREAD_ABORTED, NET_INTERNAL + */ +static int sceNetAdhocPtpConnect(int id, int timeout, int flag) { + INFO_LOG(SCENET, "sceNetAdhocPtpConnect(%i, %i, %i) at %08x", id, timeout, flag, currentMIPS->pc); + if (!g_Config.bEnableWlan) { + return -1; + } + + return NetAdhocPtp_Connect(id, timeout, flag); +} + int NetAdhocPtp_Close(int id, int unknown) { // Library is initialized if (netAdhocInited) { @@ -3810,7 +3852,7 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou socket->nonblocking = flag; // Connected Socket - if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) { + if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED || ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) { // Valid Arguments if (data != NULL && len != NULL && *len > 0) { // Schedule Timeout Removal @@ -3844,12 +3886,16 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou DEBUG_LOG(SCENET, "sceNetAdhocPtpSend[%i:%u]: Sent %u bytes to %s:%u\n", id, ptpsocket.lport, sent, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + // Set to Established on successful Send when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + // Return Success return 0; } // Non-Critical Error - else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK)) { + else if (sent == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { // Non-Blocking if (flag) return hleLogSuccessVerboseI(SCENET, ERROR_NET_ADHOC_WOULD_BLOCK, "would block"); @@ -3864,16 +3910,16 @@ static int sceNetAdhocPtpSend(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Change Socket State ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - // Disconnected - return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); + // Disconnected or Not connected? + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected?"); } // Invalid Arguments return hleLogError(SCENET, ERROR_NET_ADHOC_INVALID_ARG, "invalid arg"); } - // Not connected - return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CONNECTED, "not connected"); + // Disconnected + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); } // Invalid Socket @@ -3909,7 +3955,7 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou auto& ptpsocket = socket->data.ptp; socket->nonblocking = flag; - if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED) { + if (ptpsocket.state == ADHOC_PTP_STATE_ESTABLISHED || ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) { // Schedule Timeout Removal //if (flag) timeout = 0; @@ -3934,7 +3980,7 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou received = recv(ptpsocket.id, (char*)buf, *len, MSG_NOSIGNAL); error = errno; - if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK)) { + if (received == SOCKET_ERROR && (error == EAGAIN || error == EWOULDBLOCK || (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT && (error == ENOTCONN || connectInProgress(error))))) { if (flag == 0) { // Simulate blocking behaviour with non-blocking socket u64 threadSocketId = ((u64)__KernelGetCurThread()) << 32 | ptpsocket.id; @@ -3962,6 +4008,10 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou DEBUG_LOG(SCENET, "sceNetAdhocPtpRecv[%i:%u]: Received %u bytes from %s:%u\n", id, ptpsocket.lport, received, mac2str(&ptpsocket.paddr).c_str(), ptpsocket.pport); + // Set to Established on successful Recv when an attempt to Connect was initiated + if (ptpsocket.state == ADHOC_PTP_STATE_SYN_SENT) + ptpsocket.state = ADHOC_PTP_STATE_ESTABLISHED; + // Return Success return 0; } @@ -3974,11 +4024,11 @@ static int sceNetAdhocPtpRecv(int id, u32 dataAddr, u32 dataSizeAddr, int timeou // Change Socket State ptpsocket.state = ADHOC_PTP_STATE_CLOSED; - // Disconnected - return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); + // Disconnected or Not connected? + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected?"); } - return hleLogError(SCENET, ERROR_NET_ADHOC_NOT_CONNECTED, "not connected"); + return hleLogError(SCENET, ERROR_NET_ADHOC_DISCONNECTED, "disconnected"); } // Invalid Socket @@ -4627,8 +4677,10 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int // Create PDP Socket int sock = sceNetAdhocPdpCreate((const char*)&item->mac, static_cast(item->port), item->rxbuflen, 0); item->socket = sock; - if (sock < 1) + if (sock < 1) { + peerlock.unlock(); return hleLogError(SCENET, ERROR_NET_ADHOC_MATCHING_PORT_IN_USE, "adhoc matching port in use"); + } // Create & Start the Fake PSP Thread ("matching_ev%d" and "matching_io%d") netAdhocValidateLoopMemory(); @@ -4658,8 +4710,7 @@ int NetAdhocMatching_Start(int matchingId, int evthPri, int evthPartitionId, int // Multithreading Unlock peerlock.unlock(); - // Give a little time to make sure matching Threads are ready before the game use the next sceNet functions, should've checked for status instead of guessing the time? - return hleDelayResult(0, "give some time", adhocMatchingEventDelay); + return 0; } #define KERNEL_PARTITION_ID 1 @@ -4671,7 +4722,9 @@ static int sceNetAdhocMatchingStart(int matchingId, int evthPri, int evthStack, if (!g_Config.bEnableWlan) return -1; - return NetAdhocMatching_Start(matchingId, evthPri, USER_PARTITION_ID, evthStack, inthPri, USER_PARTITION_ID, inthStack, optLen, optDataAddr); + int retval = NetAdhocMatching_Start(matchingId, evthPri, USER_PARTITION_ID, evthStack, inthPri, USER_PARTITION_ID, inthStack, optLen, optDataAddr); + // Give a little time to make sure matching Threads are ready before the game use the next sceNet functions, should've checked for status instead of guessing the time? + return hleDelayResult(retval, "give some time", adhocMatchingEventDelay); } // With params for Partition ID for the event & input handler stack @@ -4680,7 +4733,9 @@ static int sceNetAdhocMatchingStart2(int matchingId, int evthPri, int evthPartit if (!g_Config.bEnableWlan) return -1; - return NetAdhocMatching_Start(matchingId, evthPri, evthPartitionId, evthStack, inthPri, inthPartitionId, inthStack, optLen, optDataAddr); + int retval = NetAdhocMatching_Start(matchingId, evthPri, evthPartitionId, evthStack, inthPri, inthPartitionId, inthStack, optLen, optDataAddr); + // Give a little time to make sure matching Threads are ready before the game use the next sceNet functions, should've checked for status instead of guessing the time? + return hleDelayResult(retval, "give some time", adhocMatchingEventDelay); } diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 10159d7daa6d..2e9ac873d42d 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -327,9 +327,8 @@ size_t AuCtx::FindNextMp3Sync() { // return output pcm size, <0 error u32 AuCtx::AuDecode(u32 pcmAddr) { - if (!Memory::IsValidAddress(pcmAddr)){ - ERROR_LOG(ME, "%s: output bufferAddress %08x is invalctx", __FUNCTION__, pcmAddr); - return -1; + if (!Memory::GetPointer(PCMBuf)) { + return hleLogError(ME, -1, "ctx output bufferAddress %08x is invalid", PCMBuf); } auto outbuf = Memory::GetPointer(PCMBuf); @@ -376,7 +375,8 @@ u32 AuCtx::AuDecode(u32 pcmAddr) { memset(outbuf + outpcmbufsize, 0, PCMBufSize - outpcmbufsize); } - Memory::Write_U32(PCMBuf, pcmAddr); + if (pcmAddr) + Memory::Write_U32(PCMBuf, pcmAddr); return outpcmbufsize; } diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 77c2fafae0db..c79faa2149c3 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -40,8 +40,14 @@ void RegisterFileLoaderFactory(std::string prefix, std::unique_ptr>11) & 0x1F) #define _IMM16 (signed short)(op & 0xFFFF) #define _IMM26 (op & 0x03FFFFFF) +#define TARGET16 ((int)((uint32_t)(int)_IMM16 << 2)) +#define TARGET26 (_IMM26 << 2) #define LOOPOPTIMIZATION 0 @@ -69,7 +71,7 @@ void ArmJit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -182,7 +184,7 @@ void ArmJit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool like ERROR_LOG_REPORT(JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -328,7 +330,7 @@ void ArmJit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -386,7 +388,7 @@ void ArmJit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -456,7 +458,7 @@ void ArmJit::Comp_Jump(MIPSOpcode op) { ERROR_LOG_REPORT(JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - u32 off = _IMM26 << 2; + u32 off = TARGET26; u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off; // Might be a stubbed address or something? diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 50322a65bc65..b6e313891336 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -184,6 +184,9 @@ class ArmJit : public ArmGen::ARMXCodeBlock, public JitInterface, public MIPSFro const u8 *GetDispatcher() const override { return dispatcher; } + bool IsAtDispatchFetch(const u8 *ptr) const override { + return ptr == dispatcherFetch; + } void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override; void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; @@ -311,6 +314,7 @@ class ArmJit : public ArmGen::ARMXCodeBlock, public JitInterface, public MIPSFro const u8 *dispatcherCheckCoreState; const u8 *dispatcherPCInR0; const u8 *dispatcher; + const u8 *dispatcherFetch; const u8 *dispatcherNoCheck; const u8 *restoreRoundingMode; diff --git a/Core/MIPS/ARM64/Arm64Asm.cpp b/Core/MIPS/ARM64/Arm64Asm.cpp index b95b8e54b4d9..bca3f535e179 100644 --- a/Core/MIPS/ARM64/Arm64Asm.cpp +++ b/Core/MIPS/ARM64/Arm64Asm.cpp @@ -250,6 +250,7 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) { #ifdef MASKED_PSP_MEMORY ANDI2R(SCRATCH1, SCRATCH1, 0x3FFFFFFF); #endif + dispatcherFetch = GetCodePtr(); LDR(SCRATCH1, MEMBASEREG, SCRATCH1_64); LSR(SCRATCH2, SCRATCH1, 24); // or UBFX(SCRATCH2, SCRATCH1, 24, 8) ANDI2R(SCRATCH1, SCRATCH1, 0x00FFFFFF); diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index ce7bc2d34150..e3914ee1a63e 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -49,6 +49,8 @@ #define _SIZE ((op>>11) & 0x1F) #define _IMM16 (signed short)(op & 0xFFFF) #define _IMM26 (op & 0x03FFFFFF) +#define TARGET16 ((int)((uint32_t)(int)_IMM16 << 2)) +#define TARGET26 (_IMM26 << 2) #define LOOPOPTIMIZATION 0 @@ -69,7 +71,7 @@ void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -200,7 +202,7 @@ void Arm64Jit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool li ERROR_LOG_REPORT(JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -345,7 +347,7 @@ void Arm64Jit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely) { ERROR_LOG_REPORT(JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -402,7 +404,7 @@ void Arm64Jit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely) { ERROR_LOG_REPORT(JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -471,7 +473,7 @@ void Arm64Jit::Comp_Jump(MIPSOpcode op) { ERROR_LOG_REPORT(JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - u32 off = _IMM26 << 2; + u32 off = TARGET26; u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off; // Might be a stubbed address or something? diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 17734ade594a..95affe2515b4 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -185,6 +185,9 @@ class Arm64Jit : public Arm64Gen::ARM64CodeBlock, public JitInterface, public MI const u8 *GetDispatcher() const override { return dispatcher; } + bool IsAtDispatchFetch(const u8 *ptr) const override { + return ptr == dispatcherFetch; + } void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override; void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; @@ -275,6 +278,7 @@ class Arm64Jit : public Arm64Gen::ARM64CodeBlock, public JitInterface, public MI const u8 *dispatcherPCInSCRATCH1; const u8 *dispatcher; const u8 *dispatcherNoCheck; + const u8 *dispatcherFetch; const u8 *saveStaticRegisters; const u8 *loadStaticRegisters; diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index 2592d6227c6d..16a158b9fe61 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -42,6 +42,8 @@ #define _SIZE ((op>>11) & 0x1F) #define _IMM16 (signed short)(op & 0xFFFF) #define _IMM26 (op & 0x03FFFFFF) +#define TARGET16 ((int)((uint32_t)(int)_IMM16 << 2)) +#define TARGET26 (_IMM26 << 2) #define LOOPOPTIMIZATION 0 @@ -57,7 +59,7 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) { ERROR_LOG_REPORT(JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -114,7 +116,7 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, ERROR_LOG_REPORT(JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -192,7 +194,7 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) { ERROR_LOG_REPORT(JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; ir.Write(IROp::FpCondToReg, IRTEMP_LHS); @@ -235,7 +237,7 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) { ERROR_LOG_REPORT(JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -290,7 +292,7 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { return; } - u32 off = _IMM26 << 2; + u32 off = TARGET26; u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off; // Might be a stubbed address or something? diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index 08063f866e49..e39a8bab217b 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -123,6 +123,9 @@ namespace MIPSComp { virtual bool CodeInRange(const u8 *ptr) const = 0; virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; + virtual bool IsAtDispatchFetch(const u8 *ptr) const { + return false; + } virtual const u8 *GetDispatcher() const = 0; virtual const u8 *GetCrashHandler() const = 0; virtual JitBlockCache *GetBlockCache() = 0; diff --git a/Core/MIPS/MIPSCodeUtils.cpp b/Core/MIPS/MIPSCodeUtils.cpp index ce91b2199ce3..5f1253ee0ada 100644 --- a/Core/MIPS/MIPSCodeUtils.cpp +++ b/Core/MIPS/MIPSCodeUtils.cpp @@ -28,72 +28,59 @@ namespace MIPSCodeUtils #define OP_SYSCALL_MASK 0xFC00003F #define _RS ((op>>21) & 0x1F) #define _RT ((op>>16) & 0x1F) +#define _IMM16 (signed short)(op & 0xFFFF) +#define _IMM26 (op & 0x03FFFFFF) +#define TARGET16 ((int)((uint32_t)(int)_IMM16 << 2)) +#define TARGET26 (_IMM26 << 2) - u32 GetJumpTarget(u32 addr) - { + u32 GetJumpTarget(u32 addr) { MIPSOpcode op = Memory::Read_Instruction(addr, true); - if (op != 0) - { + if (op != 0) { MIPSInfo info = MIPSGetInfo(op); if ((info & IS_JUMP) && (info & IN_IMM26)) - { - u32 target = (addr & 0xF0000000) | ((op&0x03FFFFFF) << 2); - return target; - } + return (addr & 0xF0000000) | TARGET26; else return INVALIDTARGET; - } - else + } else { return INVALIDTARGET; + } } - u32 GetBranchTarget(u32 addr) - { + u32 GetBranchTarget(u32 addr) { MIPSOpcode op = Memory::Read_Instruction(addr, true); - if (op != 0) - { + if (op != 0) { MIPSInfo info = MIPSGetInfo(op); if (info & IS_CONDBRANCH) - { - return addr + 4 + ((signed short)(op&0xFFFF)<<2); - } + return addr + 4 + TARGET16; else return INVALIDTARGET; - } - else + } else { return INVALIDTARGET; + } } - u32 GetBranchTargetNoRA(u32 addr) - { + u32 GetBranchTargetNoRA(u32 addr) { MIPSOpcode op = Memory::Read_Instruction(addr, true); return GetBranchTargetNoRA(addr, op); } - u32 GetBranchTargetNoRA(u32 addr, MIPSOpcode op) - { - if (op != 0) - { + u32 GetBranchTargetNoRA(u32 addr, MIPSOpcode op) { + if (op != 0) { MIPSInfo info = MIPSGetInfo(op); if ((info & IS_CONDBRANCH) && !(info & OUT_RA)) - { - return addr + 4 + ((signed short)(op&0xFFFF)<<2); - } + return addr + 4 + TARGET16; else return INVALIDTARGET; - } - else + } else { return INVALIDTARGET; + } } - u32 GetSureBranchTarget(u32 addr) - { + u32 GetSureBranchTarget(u32 addr) { MIPSOpcode op = Memory::Read_Instruction(addr, true); - if (op != 0) - { + if (op != 0) { MIPSInfo info = MIPSGetInfo(op); - if ((info & IS_CONDBRANCH) && !(info & (IN_FPUFLAG | IS_VFPU))) - { + if ((info & IS_CONDBRANCH) && !(info & (IN_FPUFLAG | IS_VFPU))) { bool sure; bool takeBranch; switch (info & CONDTYPE_MASK) @@ -125,17 +112,17 @@ namespace MIPSCodeUtils } if (sure && takeBranch) - return addr + 4 + ((signed short)(op&0xFFFF)<<2); + return addr + 4 + TARGET16; else if (sure && !takeBranch) return addr + 8; else return INVALIDTARGET; - } - else + } else { return INVALIDTARGET; - } - else + } + } else { return INVALIDTARGET; + } } bool IsVFPUBranch(MIPSOpcode op) { diff --git a/Core/MIPS/x86/Asm.cpp b/Core/MIPS/x86/Asm.cpp index a099e1044b64..84fa1e9cccfe 100644 --- a/Core/MIPS/x86/Asm.cpp +++ b/Core/MIPS/x86/Asm.cpp @@ -161,7 +161,7 @@ void Jit::GenerateFixedCode(JitOptions &jo) { #ifdef MASKED_PSP_MEMORY AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK)); #endif - + dispatcherFetch = GetCodePtr(); #ifdef _M_IX86 _assert_msg_( Memory::base != 0, "Memory base bogus"); MOV(32, R(EAX), MDisp(EAX, (u32)Memory::base)); diff --git a/Core/MIPS/x86/CompBranch.cpp b/Core/MIPS/x86/CompBranch.cpp index 8c8ce8fd3de3..4910e2965536 100644 --- a/Core/MIPS/x86/CompBranch.cpp +++ b/Core/MIPS/x86/CompBranch.cpp @@ -48,6 +48,8 @@ #define _SIZE ((op>>11) & 0x1F) #define _IMM16 (signed short)(op & 0xFFFF) #define _IMM26 (op & 0x03FFFFFF) +#define TARGET16 ((int)((uint32_t)(int)_IMM16 << 2)) +#define TARGET26 (_IMM26 << 2) #define LOOPOPTIMIZATION 0 @@ -319,7 +321,7 @@ void Jit::BranchRSRTComp(MIPSOpcode op, Gen::CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in RSRTComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -395,7 +397,7 @@ void Jit::BranchRSZeroComp(MIPSOpcode op, Gen::CCFlags cc, bool andLink, bool li ERROR_LOG_REPORT(JIT, "Branch in RSZeroComp delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; MIPSGPReg rs = _RS; u32 targetAddr = GetCompilerPC() + offset + 4; @@ -511,7 +513,7 @@ void Jit::BranchFPFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely) ERROR_LOG_REPORT(JIT, "Branch in FPFlag delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -551,7 +553,7 @@ void Jit::BranchVFPUFlag(MIPSOpcode op, Gen::CCFlags cc, bool likely) WARN_LOG(JIT, "Branch in VFPU delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - int offset = _IMM16 << 2; + int offset = TARGET16; u32 targetAddr = GetCompilerPC() + offset + 4; MIPSOpcode delaySlotOp = GetOffsetInstruction(1); @@ -602,7 +604,7 @@ void Jit::Comp_Jump(MIPSOpcode op) { ERROR_LOG_REPORT(JIT, "Branch in Jump delay slot at %08x in block starting at %08x", GetCompilerPC(), js.blockStart); return; } - u32 off = _IMM26 << 2; + u32 off = TARGET26; u32 targetAddr = (GetCompilerPC() & 0xF0000000) | off; // Might be a stubbed address or something? diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 97682d5f7db2..6e77fbade4e0 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -274,6 +274,11 @@ void Jit::Compile(u32 em_address) { ClearCache(); } + if (!Memory::IsValidAddress(em_address)) { + Core_ExecException(em_address, em_address, ExecExceptionType::JUMP); + return; + } + BeginWrite(); int block_num = blocks.AllocateBlock(em_address); diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index b065d9052216..6c679547bafa 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -298,6 +298,11 @@ class Jit : public Gen::XCodeBlock, public JitInterface, public MIPSFrontendInte } return true; } + + bool IsAtDispatchFetch(const u8 *codePtr) const override { + return codePtr == dispatcherFetch; + } + void SaveFlags(); void LoadFlags(); @@ -321,6 +326,7 @@ class Jit : public Gen::XCodeBlock, public JitInterface, public MIPSFrontendInte const u8 *dispatcherCheckCoreState; const u8 *dispatcherNoCheck; const u8 *dispatcherInEAXNoCheck; + const u8 *dispatcherFetch; const u8 *restoreRoundingMode; const u8 *applyRoundingMode; diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp index 38944bf98ba4..db2a4d4d98c8 100644 --- a/Core/MemFault.cpp +++ b/Core/MemFault.cpp @@ -41,11 +41,14 @@ namespace Memory { static int64_t g_numReportedBadAccesses = 0; const uint8_t *g_lastCrashAddress; +MemoryExceptionType g_lastMemoryExceptionType; + std::unordered_set g_ignoredAddresses; void MemFault_Init() { g_numReportedBadAccesses = 0; g_lastCrashAddress = nullptr; + g_lastMemoryExceptionType = MemoryExceptionType::NONE; g_ignoredAddresses.clear(); } @@ -122,11 +125,15 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { std::string infoString = ""; + bool isAtDispatch = false; if (MIPSComp::jit) { std::string desc; if (MIPSComp::jit->DescribeCodePtr(codePtr, desc)) { infoString += desc + "\n"; } + if (MIPSComp::jit->IsAtDispatchFetch(codePtr)) { + isAtDispatch = true; + } } int instructionSize = 4; @@ -158,7 +165,19 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { infoString += disassembly + "\n"; } - if (success) { + if (isAtDispatch) { + u32 targetAddr = currentMIPS->pc; // bad approximation + // TODO: Do the other archs and platforms. +#if PPSSPP_ARCH(AMD64) && PPSSPP_PLATFORM(WINDOWS) + // We know which register the address is in, look in Asm.cpp. + targetAddr = context->Rax; +#endif + Core_ExecException(targetAddr, currentMIPS->pc, ExecExceptionType::JUMP); + // Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately. + context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler(); + ERROR_LOG(MEMMAP, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress); + return true; + } else if (success) { if (info.isMemoryWrite) { type = MemoryExceptionType::WRITE_WORD; } else { @@ -168,6 +187,8 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { type = MemoryExceptionType::UNKNOWN; } + g_lastMemoryExceptionType = type; + if (success && (g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) { if (!info.isMemoryWrite) { // It was a read. Fill the destination register with 0. diff --git a/Core/MemMap.h b/Core/MemMap.h index 1bd6027c22a0..b82e619625de 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -84,7 +84,7 @@ extern u32 g_PSPModel; // UWP has such limited memory management that we need to mask // even in 64-bit mode. Also, when using the sanitizer, we need to mask as well. -#if PPSSPP_ARCH(32BIT) || PPSSPP_PLATFORM(UWP) || USE_ADDRESS_SANITIZER || PPSSPP_PLATFORM(IOS) +#if PPSSPP_ARCH(32BIT) || PPSSPP_PLATFORM(UWP) || USE_ASAN || PPSSPP_PLATFORM(IOS) #define MASKED_PSP_MEMORY #endif diff --git a/Core/Reporting.cpp b/Core/Reporting.cpp index a5870f87c32f..5e22b7aeb6a7 100644 --- a/Core/Reporting.cpp +++ b/Core/Reporting.cpp @@ -126,10 +126,9 @@ namespace Reporting return 0; } - void QueueCRC() { + void QueueCRC(const std::string &gamePath) { std::lock_guard guard(crcLock); - const std::string &gamePath = PSP_CoreParameter().fileToStart; auto it = crcResults.find(gamePath); if (it != crcResults.end()) { // Nothing to do, we've already calculated it. @@ -146,9 +145,15 @@ namespace Reporting crcThread = std::thread(CalculateCRCThread); } - u32 RetrieveCRC() { - const std::string &gamePath = PSP_CoreParameter().fileToStart; - QueueCRC(); + bool HasCRC(const std::string &gamePath) { + QueueCRC(gamePath); + + std::lock_guard guard(crcLock); + return crcResults.find(gamePath) != crcResults.end(); + } + + uint32_t RetrieveCRC(const std::string &gamePath) { + QueueCRC(gamePath); std::unique_lock guard(crcLock); auto it = crcResults.find(gamePath); @@ -468,7 +473,7 @@ namespace Reporting postdata.Add("graphics", StringFromFormat("%d", payload.int1)); postdata.Add("speed", StringFromFormat("%d", payload.int2)); postdata.Add("gameplay", StringFromFormat("%d", payload.int3)); - postdata.Add("crc", StringFromFormat("%08x", Core_GetPowerSaving() ? 0 : RetrieveCRC())); + postdata.Add("crc", StringFromFormat("%08x", Core_GetPowerSaving() ? 0 : RetrieveCRC(PSP_CoreParameter().fileToStart))); postdata.Add("suggestions", payload.string1 != "perfect" && payload.string1 != "playable" ? "1" : "0"); AddScreenshotData(postdata, payload.string2); payload.string1.clear(); diff --git a/Core/Reporting.h b/Core/Reporting.h index 50b24f93c8bb..87a6d186a45c 100644 --- a/Core/Reporting.h +++ b/Core/Reporting.h @@ -87,6 +87,13 @@ namespace Reporting // Get the latest compatibility result. Only valid when GetStatus() is not BUSY. std::vector CompatibilitySuggestions(); + // Queues game for CRC hash if needed, and returns true if the hash is available. + bool HasCRC(const std::string &gamePath); + + // Blocks until the CRC hash is available for game, and returns it. + // To avoid stalling, call HasCRC() in update() or similar and call this if it returns true. + uint32_t RetrieveCRC(const std::string &gamePath); + // Returns true if that identifier has not been logged yet. bool ShouldLogNTimes(const char *identifier, int n); diff --git a/Core/System.cpp b/Core/System.cpp index 16ae2808debf..239533b502d0 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -245,51 +245,48 @@ bool CPU_Init() { MIPSAnalyst::Reset(); Replacement_Init(); - std::string discID; + bool allowPlugins = true; + std::string geDumpDiscID; switch (type) { case IdentifiedFileType::PSP_ISO: case IdentifiedFileType::PSP_ISO_NP: case IdentifiedFileType::PSP_DISC_DIRECTORY: InitMemoryForGameISO(loadedFile); - discID = g_paramSFO.GetDiscID(); break; case IdentifiedFileType::PSP_PBP: case IdentifiedFileType::PSP_PBP_DIRECTORY: // This is normal for homebrew. // ERROR_LOG(LOADER, "PBP directory resolution failed."); InitMemoryForGamePBP(loadedFile); - discID = g_paramSFO.GetDiscID(); break; case IdentifiedFileType::PSP_ELF: if (Memory::g_PSPModel != PSP_MODEL_FAT) { INFO_LOG(LOADER, "ELF, using full PSP-2000 memory access"); Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE; } - discID = g_paramSFO.GetDiscID(); break; case IdentifiedFileType::PPSSPP_GE_DUMP: - // Try to grab the disc ID from the filename, since unfortunately, we don't store - // it in the GE dump. This should probably be fixed, but as long as you don't rename the dumps, - // this will do the trick. - if (!DiscIDFromGEDumpPath(filename, loadedFile, &discID)) { - // Failed? Let the param SFO autogen a fake disc ID. - discID = g_paramSFO.GetDiscID(); + // Try to grab the disc ID from the filenameor GE dump. + if (DiscIDFromGEDumpPath(filename, loadedFile, &geDumpDiscID)) { + // Store in SFO, otherwise it'll generate a fake disc ID. + g_paramSFO.SetValue("DISC_ID", geDumpDiscID, 16); } + allowPlugins = false; break; default: - discID = g_paramSFO.GetDiscID(); break; } // Here we have read the PARAM.SFO, let's see if we need any compatibility overrides. // Homebrew usually has an empty discID, and even if they do have a disc id, it's not // likely to collide with any commercial ones. - coreParameter.compat.Load(discID); + coreParameter.compat.Load(g_paramSFO.GetDiscID()); InitVFPUSinCos(coreParameter.compat.flags().DoublePrecisionSinCos); - HLEPlugins::Init(); + if (allowPlugins) + HLEPlugins::Init(); if (!Memory::Init()) { // We're screwed. return false; @@ -415,6 +412,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { if (!CPU_Init()) { *error_string = "Failed initializing CPU/Memory"; + pspIsIniting = false; return false; } diff --git a/Core/Util/PPGeDraw.cpp b/Core/Util/PPGeDraw.cpp index 9a1eeaed8146..1beff411b8bd 100644 --- a/Core/Util/PPGeDraw.cpp +++ b/Core/Util/PPGeDraw.cpp @@ -125,7 +125,7 @@ struct PPGeTextDrawerImage { TextStringEntry entry; u32 ptr; }; -std::map textDrawerImages; +static std::map textDrawerImages; void PPGeSetDrawContext(Draw::DrawContext *draw) { g_draw = draw; @@ -141,6 +141,8 @@ void PPGePrepareText(const char *text, float x, float y, PPGeAlign align, float // Clears the buffer and state when done. void PPGeDrawCurrentText(u32 color = 0xFFFFFFFF); +static void PPGeDecimateTextImages(int age = 97); + void PPGeSetTexture(u32 dataAddr, int width, int height); //only 0xFFFFFF of data is used @@ -364,6 +366,10 @@ void __PPGeShutdown() delete textDrawer; textDrawer = nullptr; + + for (auto im : textDrawerImages) + kernelMemory.Free(im.second.ptr); + textDrawerImages.clear(); } void PPGeBegin() @@ -906,6 +912,18 @@ static void PPGeDrawTextImage(PPGeTextDrawerImage im, float x, float y, const PP PPGeSetDefaultTexture(); } +static void PPGeDecimateTextImages(int age) { + // Do this always, in case the platform has no TextDrawer but save state did. + for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) { + if (gpuStats.numFlips - it->second.entry.lastUsedFrame >= age) { + kernelMemory.Free(it->second.ptr); + it = textDrawerImages.erase(it); + } else { + ++it; + } + } +} + void PPGeDrawText(const char *text, float x, float y, const PPGeStyle &style) { if (!text || !strlen(text)) { return; @@ -913,8 +931,10 @@ void PPGeDrawText(const char *text, float x, float y, const PPGeStyle &style) { if (HasTextDrawer()) { PPGeTextDrawerImage im = PPGeGetTextImage(text, style, 480.0f - x, false); - PPGeDrawTextImage(im, x, y, style); - return; + if (im.ptr) { + PPGeDrawTextImage(im, x, y, style); + return; + } } if (style.hasShadow) { @@ -957,6 +977,7 @@ void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, fl } int zoom = (PSP_CoreParameter().pixelHeight + 479) / 480; + zoom = std::min(zoom, PSP_CoreParameter().renderScaleFactor); float maxScaleDown = zoom == 1 ? 1.3f : 2.0f; if (HasTextDrawer()) { @@ -987,8 +1008,10 @@ void PPGeDrawTextWrapped(const char *text, float x, float y, float wrapWidth, fl } PPGeTextDrawerImage im = PPGeGetTextImage(s.c_str(), adjustedStyle, wrapWidth <= 0 ? 480.0f - x : wrapWidth, true); - PPGeDrawTextImage(im, x, y, adjustedStyle); - return; + if (im.ptr) { + PPGeDrawTextImage(im, x, y, adjustedStyle); + return; + } } int sx = style.hasShadow ? 1 : 0; @@ -1299,15 +1322,6 @@ void PPGeNotifyFrame() { textDrawer->OncePerFrame(); } - // Do this always, in case the platform has no TextDrawer but save state did. - for (auto it = textDrawerImages.begin(); it != textDrawerImages.end(); ) { - if (it->second.entry.lastUsedFrame - gpuStats.numFlips >= 97) { - kernelMemory.Free(it->second.ptr); - it = textDrawerImages.erase(it); - } else { - ++it; - } - } - + PPGeDecimateTextImages(); PPGeImage::Decimate(); } diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index 3effac63a970..9b67600294ed 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -78,7 +78,10 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu bool enableColorDoubling = id.Bit(FS_BIT_COLOR_DOUBLE); bool doTextureProjection = id.Bit(FS_BIT_DO_TEXTURE_PROJ); bool doTextureAlpha = id.Bit(FS_BIT_TEXALPHA); - bool doFlatShading = id.Bit(FS_BIT_FLATSHADE) && !bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER); + + bool flatBug = bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER) && g_Config.bVendorBugChecksEnabled; + + bool doFlatShading = id.Bit(FS_BIT_FLATSHADE) && !flatBug; bool shaderDepal = id.Bit(FS_BIT_SHADER_DEPAL); bool bgraTexture = id.Bit(FS_BIT_BGRA_TEXTURE); bool colorWriteMask = id.Bit(FS_BIT_COLOR_WRITEMASK) && compat.bitwiseOps; @@ -359,9 +362,9 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, "uniform vec3 u_texenv;\n"); } - WRITE(p, "%s %s vec4 v_color0;\n", shading, compat.varying_fs); + WRITE(p, "%s %s lowp vec4 v_color0;\n", shading, compat.varying_fs); if (lmode) - WRITE(p, "%s %s vec3 v_color1;\n", shading, compat.varying_fs); + WRITE(p, "%s %s lowp vec3 v_color1;\n", shading, compat.varying_fs); if (enableFog) { *uniformMask |= DIRTY_FOGCOLOR; WRITE(p, "uniform vec3 u_fogcolor;\n"); @@ -394,7 +397,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. diff --git a/GPU/Common/FramebufferManagerCommon.cpp b/GPU/Common/FramebufferManagerCommon.cpp index 4e3667209611..f30a05bee54a 100644 --- a/GPU/Common/FramebufferManagerCommon.cpp +++ b/GPU/Common/FramebufferManagerCommon.cpp @@ -28,7 +28,9 @@ #include "Core/ConfigValues.h" #include "Core/Core.h" #include "Core/CoreParameter.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/Host.h" +#include "Core/MIPS/MIPS.h" #include "Core/Reporting.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferManagerCommon.h" @@ -517,6 +519,25 @@ void FramebufferManagerCommon::NotifyRenderFramebufferUpdated(VirtualFramebuffer } } +// Can't easily dynamically create these strings, we just pass along the pointer. +static const char *reinterpretStrings[3][3] = { + { + "self_reinterpret_565", + "reinterpret_565_to_5551", + "reinterpret_565_to_4444", + }, + { + "reinterpret_5551_to_565", + "self_reinterpret_5551", + "reinterpret_5551_to_4444", + }, + { + "reinterpret_4444_to_565", + "reinterpret_4444_to_5551", + "self_reinterpret_4444", + }, +}; + void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, GEBufferFormat oldFormat, GEBufferFormat newFormat) { if (!useBufferedRendering_ || !vfb->fbo) { return; @@ -530,6 +551,9 @@ void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, G bool doReinterpret = PSP_CoreParameter().compat.flags().ReinterpretFramebuffers && (lang == HLSL_D3D11 || lang == GLSL_VULKAN || lang == GLSL_3xx); + // Copy image required for now. + if (!gstate_c.Supports(GPU_SUPPORTS_COPY_IMAGE)) + doReinterpret = false; if (!doReinterpret) { // Fake reinterpret - just clear the way we always did on Vulkan. Just clear color and stencil. if (oldFormat == GE_FORMAT_565) { @@ -617,7 +641,7 @@ void FramebufferManagerCommon::ReinterpretFramebuffer(VirtualFramebuffer *vfb, G draw_->InvalidateCachedState(); draw_->CopyFramebufferImage(vfb->fbo, 0, 0, 0, 0, temp, 0, 0, 0, 0, vfb->renderWidth, vfb->renderHeight, 1, Draw::FBChannel::FB_COLOR_BIT, "reinterpret_prep"); - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "reinterpret"); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, reinterpretStrings[(int)oldFormat][(int)newFormat]); draw_->BindPipeline(pipeline); draw_->BindFramebufferAsTexture(temp, 0, Draw::FBChannel::FB_COLOR_BIT, 0); draw_->BindSamplerStates(0, 1, &reinterpretSampler_); @@ -2127,9 +2151,10 @@ void FramebufferManagerCommon::PackFramebufferSync_(VirtualFramebuffer *vfb, int const int dstBpp = (int)DataFormatSizeInBytes(destFormat); const int dstByteOffset = (y * vfb->fb_stride + x) * dstBpp; + const int dstSize = (h * vfb->fb_stride + w - 1) * dstBpp; - if (!Memory::IsValidRange(fb_address + dstByteOffset, ((h - 1) * vfb->fb_stride + w) * dstBpp)) { - ERROR_LOG(G3D, "PackFramebufferSync_ would write outside of memory, ignoring"); + if (!Memory::IsValidRange(fb_address + dstByteOffset, dstSize)) { + ERROR_LOG_REPORT(G3D, "PackFramebufferSync_ would write outside of memory, ignoring"); return; } @@ -2141,6 +2166,7 @@ void FramebufferManagerCommon::PackFramebufferSync_(VirtualFramebuffer *vfb, int if (destPtr) { draw_->CopyFramebufferToMemorySync(vfb->fbo, Draw::FB_COLOR_BIT, x, y, w, h, destFormat, destPtr, vfb->fb_stride, "PackFramebufferSync_"); + CBreakPoints::ExecMemCheck(fb_address + dstByteOffset, true, dstSize, currentMIPS->pc); } else { ERROR_LOG(G3D, "PackFramebufferSync_: Tried to readback to bad address %08x (stride = %d)", fb_address + dstByteOffset, vfb->fb_stride); } diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 2f366bf5b896..331436b02a8f 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -1303,8 +1303,6 @@ void ConvertBlendState(GenericBlendState &blendState, bool allowFramebufferRead) } static void ConvertStencilFunc5551(GenericStencilFuncState &state) { - state.writeMask = state.writeMask >= 0x80 ? 0xff : 0x00; - // Flaws: // - INVERT should convert 1, 5, 0xFF to 0. Currently it won't always. // - INCR twice shouldn't change the value. @@ -1436,13 +1434,19 @@ static void ConvertStencilFunc5551(GenericStencilFuncState &state) { } } +static void ConvertStencilMask5551(GenericStencilFuncState &state) { + state.writeMask = state.writeMask >= 0x80 ? 0xff : 0x00; +} + void ConvertStencilFuncState(GenericStencilFuncState &state) { + // The PSP's mask is reversed (bits not to write.) Ignore enabled, used for clears too. + state.writeMask = (~gstate.getStencilWriteMask()) & 0xFF; state.enabled = gstate.isStencilTestEnabled(); - if (!state.enabled) + if (!state.enabled) { + if (gstate.FrameBufFormat() == GE_FORMAT_5551) + ConvertStencilMask5551(state); return; - - // The PSP's mask is reversed (bits not to write.) - state.writeMask = (~gstate.getStencilWriteMask()) & 0xFF; + } state.sFail = gstate.getStencilOpSFail(); state.zFail = gstate.getStencilOpZFail(); @@ -1458,6 +1462,7 @@ void ConvertStencilFuncState(GenericStencilFuncState &state) { break; case GE_FORMAT_5551: + ConvertStencilMask5551(state); ConvertStencilFunc5551(state); break; diff --git a/GPU/Common/PostShader.cpp b/GPU/Common/PostShader.cpp index 9971650ba53e..e415aa92261f 100644 --- a/GPU/Common/PostShader.cpp +++ b/GPU/Common/PostShader.cpp @@ -161,6 +161,7 @@ void LoadPostShaderInfo(const std::vector &directories) { info.section = section.name(); section.Get("Name", &info.name, section.name().c_str()); section.Get("Compute", &temp, ""); + section.Get("MaxScale", &info.maxScale, 255); info.computeShaderFile = path + "/" + temp; appendTextureShader(info); diff --git a/GPU/Common/PostShader.h b/GPU/Common/PostShader.h index 42aa30d4bc16..b5c1f562f948 100644 --- a/GPU/Common/PostShader.h +++ b/GPU/Common/PostShader.h @@ -70,6 +70,7 @@ struct TextureShaderInfo { std::string name; std::string computeShaderFile; + int maxScale; bool operator == (const std::string &other) { return name == other; diff --git a/GPU/Common/ReinterpretFramebuffer.cpp b/GPU/Common/ReinterpretFramebuffer.cpp index 4793278f03bd..15bd29939b16 100644 --- a/GPU/Common/ReinterpretFramebuffer.cpp +++ b/GPU/Common/ReinterpretFramebuffer.cpp @@ -29,14 +29,14 @@ bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBuff switch (from) { case GE_FORMAT_4444: - writer.C(" uint color = uint(val.r * 15.99) | (uint(val.g * 15.99) << 4) | (uint(val.b * 15.99) << 8) | (uint(val.a * 15.99) << 12);\n"); + writer.C(" uint color = uint(val.r * 15.99) | (uint(val.g * 15.99) << 4u) | (uint(val.b * 15.99) << 8u) | (uint(val.a * 15.99) << 12u);\n"); break; case GE_FORMAT_5551: - writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 31.99) << 5) | (uint(val.b * 31.99) << 10);\n"); + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 31.99) << 5u) | (uint(val.b * 31.99) << 10u);\n"); writer.C(" if (val.a >= 0.5) color |= 0x8000U;\n"); break; case GE_FORMAT_565: - writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 63.99) << 5) | (uint(val.b * 31.99) << 11);\n"); + writer.C(" uint color = uint(val.r * 31.99) | (uint(val.g * 63.99) << 5u) | (uint(val.b * 31.99) << 11u);\n"); break; default: _assert_(false); @@ -45,16 +45,16 @@ bool GenerateReinterpretFragmentShader(char *buffer, GEBufferFormat from, GEBuff switch (to) { case GE_FORMAT_4444: - writer.C(" vec4 outColor = vec4(float(color & 0xFU), float((color >> 4) & 0xFU), float((color >> 8) & 0xFU), float((color >> 12) & 0xFU));\n"); + writer.C(" vec4 outColor = vec4(float(color & 0xFU), float((color >> 4u) & 0xFU), float((color >> 8u) & 0xFU), float((color >> 12u) & 0xFU));\n"); writer.C(" outColor *= 1.0 / 15.0;\n"); break; case GE_FORMAT_5551: - writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x1FU), float((color >> 10) & 0x1FU), 0.0);\n"); + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5u) & 0x1FU), float((color >> 10u) & 0x1FU), 0.0);\n"); writer.C(" outColor.rgb *= 1.0 / 31.0;\n"); writer.C(" outColor.a = float(color >> 15);\n"); break; case GE_FORMAT_565: - writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5) & 0x3FU), float((color >> 11) & 0x1FU), 1.0);\n"); + writer.C(" vec4 outColor = vec4(float(color & 0x1FU), float((color >> 5u) & 0x3FU), float((color >> 11u) & 0x1FU), 1.0);\n"); writer.C(" outColor.rb *= 1.0 / 31.0;\n"); writer.C(" outColor.g *= 1.0 / 63.0;\n"); break; diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 0a369204b92a..89a4606450c7 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/VertexDecoderX86.cpp b/GPU/Common/VertexDecoderX86.cpp index 6fb69c99dfc7..a3c6f15e4149 100644 --- a/GPU/Common/VertexDecoderX86.cpp +++ b/GPU/Common/VertexDecoderX86.cpp @@ -53,7 +53,7 @@ alignas(16) static const float by16384[4] = { 1.0f / 16384.0f, 1.0f / 16384.0f, 1.0f / 16384.0f, 1.0f / 16384.0f, }; -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) #ifdef _WIN32 static const X64Reg tempReg1 = RAX; static const X64Reg tempReg2 = R9; @@ -185,7 +185,7 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int // Parameters automatically fall into place. // This will align the stack properly to 16 bytes (the call of this function pushed RIP, which is 8 bytes). - const uint8_t STACK_FIXED_ALLOC = 64 + 8; + const uint8_t STACK_FIXED_ALLOC = 96 + 8; #endif // Allocate temporary storage on the stack. @@ -197,6 +197,10 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int MOVUPS(MDisp(ESP, 16), XMM5); MOVUPS(MDisp(ESP, 32), XMM6); MOVUPS(MDisp(ESP, 48), XMM7); +#if PPSSPP_ARCH(AMD64) + MOVUPS(MDisp(ESP, 64), XMM8); + MOVUPS(MDisp(ESP, 80), XMM9); +#endif bool prescaleStep = false; // Look for prescaled texcoord steps @@ -273,9 +277,13 @@ JittedVertexDecoder VertexDecoderJitCache::Compile(const VertexDecoder &dec, int MOVUPS(XMM5, MDisp(ESP, 16)); MOVUPS(XMM6, MDisp(ESP, 32)); MOVUPS(XMM7, MDisp(ESP, 48)); +#if PPSSPP_ARCH(AMD64) + MOVUPS(XMM8, MDisp(ESP, 64)); + MOVUPS(XMM9, MDisp(ESP, 80)); +#endif ADD(PTRBITS, R(ESP), Imm8(STACK_FIXED_ALLOC)); -#ifdef _M_IX86 +#if PPSSPP_ARCH(X86) // Restore register values POP(EBP); POP(EBX); @@ -462,7 +470,7 @@ void VertexDecoderJitCache::Jit_WeightsFloat() { void VertexDecoderJitCache::Jit_WeightsU8Skin() { MOV(PTRBITS, R(tempReg2), ImmPtr(&bones)); -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) if (dec_->nweights > 4) { // This reads 8 bytes, we split the top 4 so we can expand each set of 4. MOVQ_xmm(XMM8, MDisp(srcReg, dec_->weightoff)); @@ -514,7 +522,7 @@ void VertexDecoderJitCache::Jit_WeightsU8Skin() { for (int j = 0; j < dec_->nweights; j++) { X64Reg weight = XMM1; -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) X64Reg weightSrc = j < 4 ? XMM8 : XMM9; if (j == 3 || j == dec_->nweights - 1) { // In the previous iteration, we already spread this value to all lanes. @@ -572,7 +580,7 @@ void VertexDecoderJitCache::Jit_WeightsU8Skin() { void VertexDecoderJitCache::Jit_WeightsU16Skin() { MOV(PTRBITS, R(tempReg2), ImmPtr(&bones)); -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) if (dec_->nweights > 6) { // Since this is probably not aligned, two MOVQs are better than one MOVDQU. MOVQ_xmm(XMM8, MDisp(srcReg, dec_->weightoff)); @@ -628,7 +636,7 @@ void VertexDecoderJitCache::Jit_WeightsU16Skin() { for (int j = 0; j < dec_->nweights; j++) { X64Reg weight = XMM1; -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) X64Reg weightSrc = j < 4 ? XMM8 : XMM9; if (j == 3 || j == dec_->nweights - 1) { // In the previous iteration, we already spread this value to all lanes. @@ -726,7 +734,7 @@ void VertexDecoderJitCache::Jit_TcU16ToFloat() { } void VertexDecoderJitCache::Jit_TcFloat() { -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) MOV(64, R(tempReg1), MDisp(srcReg, dec_->tcoff)); MOV(64, MDisp(dstReg, dec_->decFmt.uvoff), R(tempReg1)); #else @@ -907,7 +915,7 @@ void VertexDecoderJitCache::Jit_TcU16ThroughToFloat() { } void VertexDecoderJitCache::Jit_TcFloatThrough() { -#ifdef _M_X64 +#if PPSSPP_ARCH(AMD64) MOV(64, R(tempReg1), MDisp(srcReg, dec_->tcoff)); MOV(64, MDisp(dstReg, dec_->decFmt.uvoff), R(tempReg1)); #else diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index b0d5cb64631f..33fccab57b78 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -152,7 +152,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag // this is only valid for some settings of uvGenMode GETexProjMapMode uvProjMode = static_cast(id.Bits(VS_BIT_UVPROJ_MODE, 2)); bool doShadeMapping = uvGenMode == GE_TEXMAP_ENVIRONMENT_MAP; - bool doFlatShading = id.Bit(VS_BIT_FLATSHADE) && !bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER); + + bool flatBug = bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER) && g_Config.bVendorBugChecksEnabled; + bool doFlatShading = id.Bit(VS_BIT_FLATSHADE) && !flatBug; bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM); bool hasColor = id.Bit(VS_BIT_HAS_COLOR) || !useHWTransform; diff --git a/GPU/D3D11/StateMappingD3D11.cpp b/GPU/D3D11/StateMappingD3D11.cpp index 5b0bda982f2c..5e5d8877b917 100644 --- a/GPU/D3D11/StateMappingD3D11.cpp +++ b/GPU/D3D11/StateMappingD3D11.cpp @@ -284,6 +284,9 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { } if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { + GenericStencilFuncState stencilState; + ConvertStencilFuncState(stencilState); + if (gstate.isModeClear()) { keys_.depthStencil.value = 0; keys_.depthStencil.depthTestEnable = true; @@ -307,7 +310,7 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { // We override this value in the pipeline from software transform for clear rectangles. dynState_.stencilRef = 0xFF; // But we still apply the stencil write mask. - keys_.depthStencil.stencilWriteMask = (~gstate.getStencilWriteMask()) & 0xFF; + keys_.depthStencil.stencilWriteMask = stencilState.writeMask; } else { keys_.depthStencil.stencilTestEnable = false; dynState_.useStencil = false; @@ -329,9 +332,6 @@ void DrawEngineD3D11::ApplyDrawState(int prim) { keys_.depthStencil.depthCompareOp = D3D11_COMPARISON_ALWAYS; } - GenericStencilFuncState stencilState; - ConvertStencilFuncState(stencilState); - // Stencil Test if (stencilState.enabled) { keys_.depthStencil.stencilTestEnable = true; diff --git a/GPU/Directx9/DrawEngineDX9.cpp b/GPU/Directx9/DrawEngineDX9.cpp index 0f7603e8de6e..338701689a7f 100644 --- a/GPU/Directx9/DrawEngineDX9.cpp +++ b/GPU/Directx9/DrawEngineDX9.cpp @@ -553,7 +553,7 @@ void DrawEngineDX9::DoFlush() { params.fbman = framebufferManager_; params.texCache = textureCache_; params.allowClear = true; - params.allowSeparateAlphaClear = true; + params.allowSeparateAlphaClear = false; params.provokeFlatFirst = true; int maxIndex = indexGen.MaxIndex(); @@ -602,7 +602,6 @@ void DrawEngineDX9::DoFlush() { framebufferManager_->SetColorUpdated(gstate_c.skipDrawReason); } - dxstate.colorMask.set((mask & D3DCLEAR_TARGET) != 0, (mask & D3DCLEAR_TARGET) != 0, (mask & D3DCLEAR_TARGET) != 0, (mask & D3DCLEAR_STENCIL) != 0); device_->Clear(0, NULL, mask, SwapRB(clearColor), clearDepth, clearColor >> 24); if ((gstate_c.featureFlags & GPU_USE_CLEAR_RAM_HACK) && gstate.isClearModeColorMask() && (gstate.isClearModeAlphaMask() || gstate.FrameBufFormat() == GE_FORMAT_565)) { diff --git a/GPU/Directx9/StateMappingDX9.cpp b/GPU/Directx9/StateMappingDX9.cpp index dfb5c5a754ee..b4fc44aa1d78 100644 --- a/GPU/Directx9/StateMappingDX9.cpp +++ b/GPU/Directx9/StateMappingDX9.cpp @@ -187,6 +187,9 @@ void DrawEngineDX9::ApplyDrawState(int prim) { if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { gstate_c.Clean(DIRTY_DEPTHSTENCIL_STATE); + GenericStencilFuncState stencilState; + ConvertStencilFuncState(stencilState); + // Set Stencil/Depth if (gstate.isModeClear()) { // Depth Test @@ -203,7 +206,7 @@ void DrawEngineDX9::ApplyDrawState(int prim) { dxstate.stencilTest.enable(); dxstate.stencilOp.set(D3DSTENCILOP_REPLACE, D3DSTENCILOP_REPLACE, D3DSTENCILOP_REPLACE); dxstate.stencilFunc.set(D3DCMP_ALWAYS, 255, 0xFF); - dxstate.stencilMask.set((~gstate.getStencilWriteMask()) & 0xFF); + dxstate.stencilMask.set(stencilState.writeMask); } else { dxstate.stencilTest.disable(); } @@ -221,9 +224,6 @@ void DrawEngineDX9::ApplyDrawState(int prim) { dxstate.depthTest.disable(); } - GenericStencilFuncState stencilState; - ConvertStencilFuncState(stencilState); - // Stencil Test if (stencilState.enabled) { dxstate.stencilTest.enable(); diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index ecd9ce5a53bd..8f9b79cf7e1e 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -103,9 +103,8 @@ FramebufferManagerGLES::FramebufferManagerGLES(Draw::DrawContext *draw, GLRender { needBackBufferYSwap_ = true; needGLESRebinds_ = true; - CreateDeviceObjects(); - render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); presentation_->SetLanguage(draw_->GetShaderLanguageDesc().shaderLanguage); + CreateDeviceObjects(); } void FramebufferManagerGLES::Init() { @@ -344,8 +343,8 @@ void FramebufferManagerGLES::DeviceLost() { void FramebufferManagerGLES::DeviceRestore(Draw::DrawContext *draw) { FramebufferManagerCommon::DeviceRestore(draw); - CreateDeviceObjects(); render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); + CreateDeviceObjects(); } void FramebufferManagerGLES::Resized() { diff --git a/GPU/GLES/StateMappingGLES.cpp b/GPU/GLES/StateMappingGLES.cpp index cc89eda9b7d3..d72386d2299c 100644 --- a/GPU/GLES/StateMappingGLES.cpp +++ b/GPU/GLES/StateMappingGLES.cpp @@ -235,13 +235,16 @@ void DrawEngineGLES::ApplyDrawState(int prim) { if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { gstate_c.Clean(DIRTY_DEPTHSTENCIL_STATE); + GenericStencilFuncState stencilState; + ConvertStencilFuncState(stencilState); + if (gstate.isModeClear()) { // Depth Test if (gstate.isClearModeDepthMask()) { framebufferManager_->SetDepthUpdated(); } renderManager->SetStencilFunc(gstate.isClearModeAlphaMask(), GL_ALWAYS, 0xFF, 0xFF); - renderManager->SetStencilOp((~gstate.getStencilWriteMask()) & 0xFF, GL_REPLACE, GL_REPLACE, GL_REPLACE); + renderManager->SetStencilOp(stencilState.writeMask, GL_REPLACE, GL_REPLACE, GL_REPLACE); renderManager->SetDepth(true, gstate.isClearModeDepthMask() ? true : false, GL_ALWAYS); } else { // Depth Test @@ -250,8 +253,6 @@ void DrawEngineGLES::ApplyDrawState(int prim) { framebufferManager_->SetDepthUpdated(); } - GenericStencilFuncState stencilState; - ConvertStencilFuncState(stencilState); // Stencil Test if (stencilState.enabled) { renderManager->SetStencilFunc(stencilState.enabled, compareOps[stencilState.testFunc], stencilState.testRef, stencilState.testMask); diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 67b0dab08e59..c722d0a081ed 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -954,7 +954,9 @@ void GPUCommon::NotifySteppingExit() { if (timeSteppingStarted_ <= 0.0) { ERROR_LOG(G3D, "Mismatched stepping enter/exit."); } - timeSpentStepping_ += time_now_d() - timeSteppingStarted_; + double total = time_now_d() - timeSteppingStarted_; + _dbg_assert_msg_(total >= 0.0, "Time spent stepping became negative"); + timeSpentStepping_ += total; timeSteppingStarted_ = 0.0; } } @@ -1028,6 +1030,7 @@ bool GPUCommon::InterpretList(DisplayList &list) { if (coreCollectDebugStats) { double total = time_now_d() - start - timeSpentStepping_; + _dbg_assert_msg_(total >= 0.0, "Time spent DL processing became negative"); hleSetSteppingTime(timeSpentStepping_); timeSpentStepping_ = 0.0; gpuStats.msProcessingDisplayLists += total; @@ -1480,6 +1483,14 @@ void GPUCommon::Execute_End(u32 op, u32 diff) { default: currentList->subIntrToken = prev & 0xFFFF; UpdateState(GPUSTATE_DONE); + // Since we marked done, we have to restore the context now before the next list runs. + if (currentList->started && currentList->context.IsValid()) { + gstate.Restore(currentList->context); + ReapplyGfxState(); + // Don't restore the context again. + currentList->started = false; + } + if (currentList->interruptsEnabled && __GeTriggerInterrupt(currentList->id, currentList->pc, startingTicks + cyclesExecuted)) { currentList->pendingInterrupt = true; } else { @@ -1487,10 +1498,6 @@ void GPUCommon::Execute_End(u32 op, u32 diff) { currentList->waitTicks = startingTicks + cyclesExecuted; busyTicks = std::max(busyTicks, currentList->waitTicks); __GeTriggerSync(GPU_SYNC_LIST, currentList->id, currentList->waitTicks); - if (currentList->started && currentList->context.IsValid()) { - gstate.Restore(currentList->context); - ReapplyGfxState(); - } } break; } diff --git a/GPU/Vulkan/StateMappingVulkan.cpp b/GPU/Vulkan/StateMappingVulkan.cpp index a636531e53db..0ae5f0305b00 100644 --- a/GPU/Vulkan/StateMappingVulkan.cpp +++ b/GPU/Vulkan/StateMappingVulkan.cpp @@ -253,6 +253,9 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag } if (gstate_c.IsDirty(DIRTY_DEPTHSTENCIL_STATE)) { + GenericStencilFuncState stencilState; + ConvertStencilFuncState(stencilState); + if (gstate.isModeClear()) { key.depthTestEnable = true; key.depthCompareOp = VK_COMPARE_OP_ALWAYS; @@ -275,7 +278,7 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag // We override this value in the pipeline from software transform for clear rectangles. dynState.stencilRef = 0xFF; // But we still apply the stencil write mask. - dynState.stencilWriteMask = (~gstate.getStencilWriteMask()) & 0xFF; + dynState.stencilWriteMask = stencilState.writeMask; } else { key.stencilTestEnable = false; key.stencilCompareOp = VK_COMPARE_OP_ALWAYS; @@ -299,9 +302,6 @@ void DrawEngineVulkan::ConvertStateToVulkanKey(FramebufferManagerVulkan &fbManag key.depthCompareOp = VK_COMPARE_OP_ALWAYS; } - GenericStencilFuncState stencilState; - ConvertStencilFuncState(stencilState); - // Stencil Test if (stencilState.enabled) { key.stencilTestEnable = true; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 7559db10d4fd..e0977a45123e 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -394,6 +394,10 @@ void TextureCacheVulkan::CompileScalingShader() { if (copyCS_ != VK_NULL_HANDLE) vulkan_->Delete().QueueDeleteShaderModule(copyCS_); textureShader_.clear(); + maxScaleFactor_ = 255; + } else if (uploadCS_ || copyCS_) { + // No need to recreate. + return; } if (!g_Config.bTexHardwareScaling) return; @@ -414,6 +418,7 @@ void TextureCacheVulkan::CompileScalingShader() { _dbg_assert_msg_(copyCS_ != VK_NULL_HANDLE, "failed to compile copy shader"); textureShader_ = g_Config.sTextureShaderName; + maxScaleFactor_ = shaderInfo->maxScale; } void TextureCacheVulkan::ReleaseTexture(TexCacheEntry *entry, bool delete_them) { @@ -759,6 +764,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { VkFormat dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); int scaleFactor = standardScaleFactor_; + if (scaleFactor > maxScaleFactor_) + scaleFactor = maxScaleFactor_; // Rachet down scale factor in low-memory mode. if (lowMemoryMode_) { @@ -923,6 +930,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { void *data; bool dataScaled = true; if (replaced.Valid()) { + // Directly load the replaced image. data = drawEngine_->GetPushBufferForTextureData()->PushAligned(size, &bufferOffset, &texBuf, pushAlignment); replaced.Load(i, data, stride); entry->vkTex->UploadMip(cmdInit, i, mipWidth, mipHeight, texBuf, bufferOffset, stride / bpp); @@ -1003,7 +1011,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { // Generate any additional mipmap levels. for (int level = maxLevel + 1; level <= maxLevelToGenerate; level++) { - entry->vkTex->GenerateMip(cmdInit, level); + entry->vkTex->GenerateMip(cmdInit, level, computeUpload ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } if (maxLevel == 0) { diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 254efcd40bac..7f8ae32d25ba 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -139,6 +139,7 @@ class TextureCacheVulkan : public TextureCacheCommon { Vulkan2D *vulkan2D_; std::string textureShader_; + int maxScaleFactor_ = 255; VkShaderModule uploadCS_ = VK_NULL_HANDLE; VkShaderModule copyCS_ = VK_NULL_HANDLE; diff --git a/README.md b/README.md index c59473b5ebb7..6894ea5f5a9a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,50 @@ If you want to download regularly updated builds for Android, Windows x86 and x6 For game compatibility, see [community compatibility feedback](https://report.ppsspp.org/games). +What's new in 1.11.1 +==================== +* A few crash fixes ([#14085], [#14089], [#14091], [#14092]), a few adhoc fixes +* Glitchy menu audio on some devices ([#14101]), in-game UI font memory leak ([#14078]) +* Couple of adhoc fixes ([#14106], [#14117]) + +What's new in 1.11.0 +==================== +* Lots of minor bug fixes, crash fixes, and performance fixes and improvements. +* New Browse... button to allow opening SD cards on Android 11 +* Countless AdHoc networking fixes by ANR2ME, for example Dragon Ball Shin Budokai, PowerStone, + Bleach Heat The Soul 7, Kingdom Hearts, GTA: VCS and many more. +* Graphics issue with car reflections fixed in Outrun, Dirt 2 ([#13636], [#13640], [#13760]) +* Cut-off cards in Yu Gi Oh fixed ([#7124]). +* Numerous fixes to the builtin fonts by nassau-tk +* Added exception handler so PPSSPP stays alive if a game crashes ([#11795]/[#13092]) +* Desktop: Support for multiple instance multiplayer ([#13172], ...) +* Workaround for rendering bugs with flat shading in iOS 14 +* Multiple fixes to the IR interpreter ([#13897], ...) +* UI: New fullscreen button on desktop platforms, optional navigation sounds ([#13239]) +* Audio and multiple hangs fixes in UWP version ([#13792], ...) +* Partial microphone support ([#12336], ...) +* Workaround for wacky action mirroring bug in Hitman Reborn Battle Arena 2 ([#13706], [#13526]) +* Hardware texture upscaling for Vulkan, mipmap generation ([#13235], [#13514]) +* Added MMPX Vulkan texture upscaling shader ([#13986]) +* Depth texturing support in Vulkan and D3D11 ([#13262], [#13556], ...) +* Performance fix for Test Drive Unlimited ([#13355], ...) +* Allow rewind on mobile ([#13866]) +* Added option to disable on-screen messages ([#13695]) +* Added "Lower resolution for effects" on libretro ([#13654]) +* Allow chaining multiple post-processing shaders ([#12924]) +* Support for loading game-specific plugins ([#13335]) +* Fixed Assassin's Creed: Bloodlines Save issue on Android ([#12761]) +* Hanayaka Nari Wa ga Ichizoku: mono voices fixed ([#5213]) +* Additional fixed games: + * Namco Museum - Battle Collection, Vol 2 ([#9523], [#13297], [#13298]) + * Dream Club Portable (graphics bugs, GL and Vulkan) ([#6025]) + * Capcom Classic Collection Reloaded (stuck in return game) ([#4671]) + * Xyanide Resurrection (freezing) ([#8526]) + * Dissidia Final Fantasy Chinese (patched game, invalid address) ([#13204]) + * Crazy Taxi ([#13368]) + * Spiderman: Friend or Foe ([#13969]) + * Downstream Panic (US) (New Game crash) ([#13633]) + What's new in 1.10.3 -------------------- * Fix for control layout editor ([#13125]) @@ -343,3 +387,48 @@ Credit goes to: [#10909]: https://github.com/hrydgard/ppsspp/issues/10909 "WebSocket based debugger interface" [#11447]: https://github.com/hrydgard/ppsspp/issues/11447 "Avoid calling any GL calls during shutdown on Android. Should help #11063" [#11350]: https://github.com/hrydgard/ppsspp/issues/11350 "TexCache: Optimize DXT3/DXT5 decode to single pass" +[#13636]: https://github.com/hrydgard/ppsspp/issues/13636 "Reinterpret framebuffer formats as needed. Outrun reflections partial fix" +[#13640]: https://github.com/hrydgard/ppsspp/issues/13640 "Fix car reflections in Outrun" +[#13760]: https://github.com/hrydgard/ppsspp/issues/13760 "Fix car lighting issues in DiRT 2." +[#7124]: https://github.com/hrydgard/ppsspp/issues/7124 "Yu-Gi-Oh! GX Tag Force Card summoning (card cut-off / cropped)" +[#11795]: https://github.com/hrydgard/ppsspp/issues/11795 "Exception handler - catch bad memory accesses" +[#13092]: https://github.com/hrydgard/ppsspp/issues/13092 "Bad memory access handling improvements" +[#13172]: https://github.com/hrydgard/ppsspp/issues/13172 "Generalized multi-instance" +[#13897]: https://github.com/hrydgard/ppsspp/issues/13897 "LittleBigPlanet - Game Not Loading, Blue Screen (iOS, Unplayable)" +[#13239]: https://github.com/hrydgard/ppsspp/issues/13239 "Add sound effects for PPSSPP interface navigation" +[#13792]: https://github.com/hrydgard/ppsspp/issues/13792 "Fix UWP audio and a hang bug" +[#12336]: https://github.com/hrydgard/ppsspp/issues/12336 "Microphone support" +[#13706]: https://github.com/hrydgard/ppsspp/issues/13706 "Add back the old implementation of vfpu_sin/cos/sincos." +[#13526]: https://github.com/hrydgard/ppsspp/issues/13526 "VFPU: Compute sines and cosines in double precision." +[#13235]: https://github.com/hrydgard/ppsspp/issues/13235 "Vulkan: Allow custom texture upscaling shaders" +[#13514]: https://github.com/hrydgard/ppsspp/issues/13514 "Vulkan: Automatically generate mipmaps for replaced/scaled textures" +[#13986]: https://github.com/hrydgard/ppsspp/issues/13986 "Vulkan: Add MMPX upscaling texture shader" +[#13355]: https://github.com/hrydgard/ppsspp/issues/13355 "Refactor framebuffer attachment. Fixes Test Drive Unlimited performance" +[#13866]: https://github.com/hrydgard/ppsspp/issues/13866 "SaveState: Allow rewind on mobile" +[#13695]: https://github.com/hrydgard/ppsspp/issues/13695 "Add developer setting \"Show on-screen messages\". Uncheck to hide them." +[#13654]: https://github.com/hrydgard/ppsspp/issues/13654 "Expose the \"Lower resolution for effects\" setting in libretro." +[#12924]: https://github.com/hrydgard/ppsspp/issues/12924 "Postprocessing: User chain support" +[#13335]: https://github.com/hrydgard/ppsspp/issues/13335 "Support for loading game-specific plugins" +[#12761]: https://github.com/hrydgard/ppsspp/issues/12761 "[Android][OpenGL&Vulkan][Save issue] Assassin's Creed : Bloodlines (ULJM05571)" +[#5213]: https://github.com/hrydgard/ppsspp/issues/5213 "Hanayaka Nari Wa ga Ichizoku strange MP3 mono voice" +[#9523]: https://github.com/hrydgard/ppsspp/issues/9523 "Namco Museum - Battle Collection - ULUS100035 loading problem" +[#13297]: https://github.com/hrydgard/ppsspp/issues/13297 "Namco Museum Vol. 2 - ULJS00047 infinite loading in some game" +[#13298]: https://github.com/hrydgard/ppsspp/issues/13298 "Fix sceKernelExitThread" +[#6025]: https://github.com/hrydgard/ppsspp/issues/6025 "Dream Club Portable crash after select girl" +[#4671]: https://github.com/hrydgard/ppsspp/issues/4671 "Capcom Classic Collection Reloaded stuck in return game" +[#8526]: https://github.com/hrydgard/ppsspp/issues/8526 "Xyanide Resurrection freezing" +[#13204]: https://github.com/hrydgard/ppsspp/issues/13204 "Dissidia Final Fantasy Chinese patch invalid address" +[#13368]: https://github.com/hrydgard/ppsspp/issues/13368 "Reschedule after resuming thread from suspend." +[#13969]: https://github.com/hrydgard/ppsspp/issues/13969 "Io: Don't allow async close while async busy" +[#13633]: https://github.com/hrydgard/ppsspp/issues/13633 "Downstream Panic (US) New Game crashes" +[#13667]: https://github.com/hrydgard/ppsspp/issues/13667 "Dynasty Warriors Multi Raid 2: Online questions" +[#13262]: https://github.com/hrydgard/ppsspp/issues/13262 "Implement texturing from depth buffers (Vulkan only so far)" +[#13556]: https://github.com/hrydgard/ppsspp/issues/13556 "D3D11 depth texture support" +[#14085]: https://github.com/hrydgard/ppsspp/issues/14085 "Handle exec addr errors better - don't let IgnoreBadMemoryAccesses skip dispatcher exceptions" +[#14089]: https://github.com/hrydgard/ppsspp/issues/14089 "GL: Call CreateDeviceObjects *after* updating render_." +[#14091]: https://github.com/hrydgard/ppsspp/issues/14091 "Only allow sceMpegGetAvcAu warmup for God Eater Series" +[#14092]: https://github.com/hrydgard/ppsspp/issues/14092 "SaveState: Prevent crash on bad cookie marker" +[#14101]: https://github.com/hrydgard/ppsspp/issues/14101 "Menu audio glitchfix" +[#14078]: https://github.com/hrydgard/ppsspp/issues/14078 "PPGe: Decimate text images properly" +[#14106]: https://github.com/hrydgard/ppsspp/issues/14106 "[Adhoc] Fix frozen (0 FPS) issue on Kao Challengers and Asterix & Obelix XX" +[#14117]: https://github.com/hrydgard/ppsspp/issues/14117 "[Adhoc] Fix lob" diff --git a/UI/BackgroundAudio.cpp b/UI/BackgroundAudio.cpp index ba03df8ca29f..7d6ff7732e9d 100644 --- a/UI/BackgroundAudio.cpp +++ b/UI/BackgroundAudio.cpp @@ -343,14 +343,12 @@ int BackgroundAudio::Play() { int sz = lastPlaybackTime_ <= 0.0 ? 44100 / 60 : (int)((now - lastPlaybackTime_) * 44100); sz = std::min(BUFSIZE / 2, sz); if (at3Reader_) { - if (sz >= 16) { - if (at3Reader_->Read(buffer, sz)) { - if (fadingOut_) { - for (int i = 0; i < sz*2; i += 2) { - buffer[i] *= volume_; - buffer[i + 1] *= volume_; - volume_ += delta_; - } + if (at3Reader_->Read(buffer, sz)) { + if (fadingOut_) { + for (int i = 0; i < sz*2; i += 2) { + buffer[i] *= volume_; + buffer[i + 1] *= volume_; + volume_ += delta_; } } } diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 71431f227e3d..8daac5bc898e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -24,6 +24,7 @@ #include "Common/System/System.h" #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/Data/Text/I18n.h" +#include "Common/Net/HTTPClient.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" #include "Common/UI/ViewGroup.h" @@ -48,6 +49,7 @@ #include "GPU/GPUState.h" #include "UI/MiscScreens.h" #include "UI/DevScreens.h" +#include "UI/MainScreen.h" #include "UI/ControlMappingScreen.h" #include "UI/GameSettingsScreen.h" @@ -585,8 +587,8 @@ void SystemInfoScreen::CreateViews() { #else buildConfig->Add(new InfoItem("NDEBUG", "")); #endif -#ifdef USE_ADDRESS_SANITIZER - buildConfig->Add(new InfoItem("USE_ADDRESS_SANITIZER", "")); +#ifdef USE_ASAN + buildConfig->Add(new InfoItem("USE_ASAN", "")); #endif #ifdef USING_GLES2 buildConfig->Add(new InfoItem("USING_GLES2", "")); @@ -1105,3 +1107,90 @@ void ShaderViewScreen::CreateViews() { layout->Add(new Button(di->T("Back")))->OnClick.Handle(this, &UIScreen::OnBack); } + +const std::string framedumpsBaseUrl = "http://framedump.ppsspp.org/repro/"; + +FrameDumpTestScreen::FrameDumpTestScreen() { + +} + +FrameDumpTestScreen::~FrameDumpTestScreen() { + g_DownloadManager.CancelAll(); +} + +void FrameDumpTestScreen::CreateViews() { + using namespace UI; + + root_ = new AnchorLayout(new LayoutParams(FILL_PARENT, FILL_PARENT)); + auto di = GetI18NCategory("Dialog"); + + TabHolder *tabHolder; + tabHolder = new TabHolder(ORIENT_VERTICAL, 200, new AnchorLayoutParams(10, 0, 10, 0, false)); + root_->Add(tabHolder); + AddStandardBack(root_); + tabHolder->SetTag("DumpTypes"); + root_->SetDefaultFocusView(tabHolder); + + ViewGroup *dumpsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); + dumpsScroll->SetTag("GameSettingsGraphics"); + LinearLayout *dumps = new LinearLayout(ORIENT_VERTICAL); + dumps->SetSpacing(0); + dumpsScroll->Add(dumps); + tabHolder->AddTab("Dumps", dumps); + + dumps->Add(new ItemHeader("GE Frame Dumps")); + + for (auto &file : files_) { + std::string url = framedumpsBaseUrl + file; + Choice *c = dumps->Add(new Choice(file)); + c->SetTag(url); + c->OnClick.Handle(this, &FrameDumpTestScreen::OnLoadDump); + } +} + +UI::EventReturn FrameDumpTestScreen::OnLoadDump(UI::EventParams ¶ms) { + std::string url = params.v->Tag(); + INFO_LOG(COMMON, "Trying to launch '%s'", url.c_str()); + // Our disc streaming functionality detects the URL and takes over and handles loading framedumps well, + // except for some reason the game ID. + // TODO: Fix that since it can be important for compat settings. + LaunchFile(screenManager(), url); + return UI::EVENT_DONE; +} + +void FrameDumpTestScreen::update() { + UIScreen::update(); + + if (!listing_) { + listing_ = g_DownloadManager.StartDownload(framedumpsBaseUrl, ""); + } + + if (listing_ && listing_->Done() && files_.empty()) { + if (listing_->ResultCode() == 200) { + std::string listingHtml; + listing_->buffer().TakeAll(&listingHtml); + + std::vector lines; + // We rely slightly on nginx listing format here. Not great. + SplitString(listingHtml, '\n', lines); + for (auto &line : lines) { + std::string trimmed = StripSpaces(line); + if (startsWith(trimmed, " #include "Common/Data/Text/I18n.h" +#include "Common/Net/HTTPClient.h" #include "Common/UI/UIScreen.h" #include "UI/MiscScreens.h" @@ -181,5 +182,21 @@ class ShaderViewScreen : public UIDialogScreenWithBackground { DebugShaderType type_; }; +class FrameDumpTestScreen : public UIDialogScreenWithBackground { +public: + FrameDumpTestScreen(); + ~FrameDumpTestScreen(); + + void CreateViews() override; + void update() override; + +private: + UI::EventReturn OnLoadDump(UI::EventParams &e); + + std::vector files_; + std::shared_ptr listing_; + std::shared_ptr dumpDownload_; +}; + void DrawProfile(UIContext &ui); const char *GetCompilerABI(); diff --git a/UI/DisplayLayoutScreen.cpp b/UI/DisplayLayoutScreen.cpp index 96b224c5735f..95bddf64cd92 100644 --- a/UI/DisplayLayoutScreen.cpp +++ b/UI/DisplayLayoutScreen.cpp @@ -220,7 +220,7 @@ class Boundary : public UI::View { } void Draw(UIContext &dc) override { - dc.Draw()->DrawImageStretch(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color); + dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y, bounds_.x2(), bounds_.y2(), dc.theme->itemDownStyle.background.color); } }; diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index d8387b84fa45..7f0773971a82 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,183 @@ 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 + layout(location = 0) in 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 v = texture(Sampler0, oTexCoord0).aaaa * oColor0; + 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 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, + "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 +261,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 +301,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 +440,6 @@ void GPUDriverTestScreen::DiscardTest() { depthTestLessEqual->Release(); depthTestGreater->Release(); rasterNoCull->Release(); - - SamplerStateDesc nearestDesc{}; - samplerNearest_ = draw->CreateSamplerState(nearestDesc); } UIContext &dc = *screenManager()->getUIContext(); @@ -253,11 +460,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 +530,117 @@ 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(); + + y += 120; + + dc.Begin(); + dc.DrawText("Test done", x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + 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 7f20ecab82e7..2eb077c8260c 100644 --- a/UI/GPUDriverTestScreen.h +++ b/UI/GPUDriverTestScreen.h @@ -20,6 +20,13 @@ class GPUDriverTestScreen : public UIDialogScreenWithBackground { 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 @@ class GPUDriverTestScreen : public UIDialogScreenWithBackground { 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 0d13f0428a51..2f1e157eee36 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1643,6 +1643,8 @@ void DeveloperToolsScreen::CreateViews() { if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL) { list->Add(new Choice(dev->T("GPU Driver Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnGPUDriverTest); } + list->Add(new CheckBox(&g_Config.bVendorBugChecksEnabled, dev->T("Enable driver bug workarounds"))); + list->Add(new Choice(dev->T("Framedump tests")))->OnClick.Handle(this, &DeveloperToolsScreen::OnFramedumpTest); list->Add(new Choice(dev->T("Touchscreen Test")))->OnClick.Handle(this, &DeveloperToolsScreen::OnTouchscreenTest); allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); @@ -1752,6 +1754,11 @@ UI::EventReturn DeveloperToolsScreen::OnGPUDriverTest(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn DeveloperToolsScreen::OnFramedumpTest(UI::EventParams &e) { + screenManager()->push(new FrameDumpTestScreen()); + return UI::EVENT_DONE; +} + UI::EventReturn DeveloperToolsScreen::OnTouchscreenTest(UI::EventParams &e) { screenManager()->push(new TouchTestScreen()); return UI::EVENT_DONE; diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 43050e289f21..76fd384aeda9 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -178,6 +178,7 @@ class DeveloperToolsScreen : public UIDialogScreenWithBackground { UI::EventReturn OnJitDebugTools(UI::EventParams &e); UI::EventReturn OnRemoteDebugger(UI::EventParams &e); UI::EventReturn OnGPUDriverTest(UI::EventParams &e); + UI::EventReturn OnFramedumpTest(UI::EventParams &e); UI::EventReturn OnTouchscreenTest(UI::EventParams &e); UI::EventReturn OnCopyStatesToRoot(UI::EventParams &e); diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 2bc2572533b2..6ec92e7ea88c 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -38,6 +38,8 @@ enum class BrowseFlags { }; ENUM_CLASS_BITOPS(BrowseFlags); +bool LaunchFile(ScreenManager *screenManager, std::string path); + class GameBrowser : public UI::LinearLayout { public: GameBrowser(std::string path, BrowseFlags browseFlags, bool *gridStyle, ScreenManager *screenManager, std::string lastText, std::string lastLink, UI::LayoutParams *layoutParams = nullptr); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index c9fa81db85e9..a7284654be3b 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -131,7 +131,7 @@ void DrawBackground(UIContext &dc, float alpha) { // jitter we accumulate time instead. static int frameCount = 0.0; frameCount++; - double t = (double)frameCount / 60.0; + double t = (double)frameCount / System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE); #else double t = time_now_d(); #endif @@ -472,12 +472,19 @@ void LogoScreen::Next() { const float logoScreenSeconds = 2.5f; +LogoScreen::LogoScreen(bool gotoGameSettings) + : gotoGameSettings_(gotoGameSettings) { +} + void LogoScreen::update() { UIScreen::update(); - frames_++; - if (frames_ > 60 * logoScreenSeconds) { + double rate = std::max(30.0, (double)System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE)); + + if ((double)frames_ / rate > logoScreenSeconds) { Next(); } + frames_++; + sinceStart_ = (double)frames_ / rate; } void LogoScreen::sendMessage(const char *message, const char *value) { @@ -510,11 +517,12 @@ void LogoScreen::render() { const Bounds &bounds = dc.GetBounds(); - float xres = dc.GetBounds().w; - float yres = dc.GetBounds().h; + float xres = bounds.w; + float yres = bounds.h; dc.Begin(); - float t = (float)frames_ / (60.0f * logoScreenSeconds / 3.0f); + + float t = (float)sinceStart_ / (logoScreenSeconds / 3.0f); float alpha = t; if (t > 1.0f) @@ -639,10 +647,13 @@ UI::EventReturn CreditsScreen::OnOK(UI::EventParams &e) { return UI::EVENT_DONE; } +CreditsScreen::CreditsScreen() { + startTime_ = time_now_d(); +} + void CreditsScreen::update() { UIScreen::update(); UpdateUIState(UISTATE_MENU); - frames_++; } void CreditsScreen::render() { @@ -797,7 +808,10 @@ void CreditsScreen::render() { const int numItems = ARRAY_SIZE(credits); int itemHeight = 36; int totalHeight = numItems * itemHeight + bounds.h + 200; - int y = bounds.y2() - (frames_ % totalHeight); + + float t = (float)(time_now_d() - startTime_) * 60.0; + + float y = bounds.y2() - fmodf(t, (float)totalHeight); for (int i = 0; i < numItems; i++) { float alpha = linearInOut(y+32, 64, bounds.y2() - 192, 64); uint32_t textColor = colorAlpha(dc.theme->infoStyle.fgColor, alpha); diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index e62363823f08..0cbd38ff0fa1 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -126,8 +126,7 @@ class TextureShaderScreen : public ListPopupScreen { class LogoScreen : public UIScreen { public: - LogoScreen(bool gotoGameSettings = false) - : gotoGameSettings_(gotoGameSettings) {} + LogoScreen(bool gotoGameSettings = false); bool key(const KeyInput &key) override; bool touch(const TouchInput &touch) override; void update() override; @@ -138,13 +137,14 @@ class LogoScreen : public UIScreen { private: void Next(); int frames_ = 0; + double sinceStart_ = 0.0; bool switched_ = false; bool gotoGameSettings_ = false; }; class CreditsScreen : public UIDialogScreenWithBackground { public: - CreditsScreen() : frames_(0) {} + CreditsScreen(); void update() override; void render() override; @@ -161,5 +161,5 @@ class CreditsScreen : public UIDialogScreenWithBackground { UI::EventReturn OnShare(UI::EventParams &e); UI::EventReturn OnTwitter(UI::EventParams &e); - int frames_; + double startTime_ = 0.0; }; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index fd1133b415e5..f7b7b13f065f 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -559,18 +560,24 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Parse command line LogTypes::LOG_LEVELS logLevel = LogTypes::LINFO; + bool forceLogLevel = false; + const auto setLogLevel = [&logLevel, &forceLogLevel](LogTypes::LOG_LEVELS level) { + logLevel = level; + forceLogLevel = true; + }; + for (int i = 1; i < argc; i++) { if (argv[i][0] == '-') { switch (argv[i][1]) { case 'd': // Enable debug logging // Note that you must also change the max log level in Log.h. - logLevel = LogTypes::LDEBUG; + setLogLevel(LogTypes::LDEBUG); break; case 'v': // Enable verbose logging // Note that you must also change the max log level in Log.h. - logLevel = LogTypes::LVERBOSE; + setLogLevel(LogTypes::LVERBOSE); break; case 'j': g_Config.iCpuCore = (int)CPUCore::JIT; @@ -585,6 +592,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch g_Config.bSaveSettings = false; break; case '-': + if (!strncmp(argv[i], "--loglevel=", strlen("--loglevel=")) && strlen(argv[i]) > strlen("--loglevel=")) + setLogLevel(static_cast(std::atoi(argv[i] + strlen("--loglevel=")))); if (!strncmp(argv[i], "--log=", strlen("--log=")) && strlen(argv[i]) > strlen("--log=")) fileToLog = argv[i] + strlen("--log="); if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state=")) @@ -665,6 +674,9 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch if (fileToLog) LogManager::GetInstance()->ChangeFileLog(fileToLog); + if (forceLogLevel) + LogManager::GetInstance()->SetAllLogLevels(logLevel); + PostLoadConfig(); #if PPSSPP_PLATFORM(ANDROID) @@ -1048,6 +1060,9 @@ void RenderOverlays(UIContext *dc, void *userdata) { } void NativeRender(GraphicsContext *graphicsContext) { + _assert_(graphicsContext != nullptr); + _assert_(screenManager != nullptr); + g_GameManager.Update(); if (GetUIState() != UISTATE_INGAME) { diff --git a/UI/TextureUtil.cpp b/UI/TextureUtil.cpp index cdfe9440e2d8..3d986e765a80 100644 --- a/UI/TextureUtil.cpp +++ b/UI/TextureUtil.cpp @@ -124,25 +124,26 @@ bool ManagedTexture::LoadFromFileData(const uint8_t *data, size_t dataSize, Imag } int potentialLevels = std::min(log2i(width[0]), log2i(height[0])); - - TextureDesc desc{}; - desc.type = TextureType::LINEAR2D; - desc.format = fmt; - desc.width = width[0]; - desc.height = height[0]; - desc.depth = 1; - desc.mipLevels = generateMips ? potentialLevels : num_levels; - desc.generateMips = generateMips && potentialLevels > num_levels; - desc.tag = name; - for (int i = 0; i < num_levels; i++) { - desc.initData.push_back(image[i]); + if (width[0] > 0 && height[0] > 0) { + TextureDesc desc{}; + desc.type = TextureType::LINEAR2D; + desc.format = fmt; + desc.width = width[0]; + desc.height = height[0]; + desc.depth = 1; + desc.mipLevels = generateMips ? potentialLevels : num_levels; + desc.generateMips = generateMips && potentialLevels > num_levels; + desc.tag = name; + for (int i = 0; i < num_levels; i++) { + desc.initData.push_back(image[i]); + } + texture_ = draw_->CreateTexture(desc); } - texture_ = draw_->CreateTexture(desc); for (int i = 0; i < num_levels; i++) { if (image[i]) free(image[i]); } - return texture_; + return texture_ != nullptr; } bool ManagedTexture::LoadFromFile(const std::string &filename, ImageFileType type, bool generateMips) { diff --git a/UI/UI.vcxproj.filters b/UI/UI.vcxproj.filters index ea4444bb8a10..eb282757cf85 100644 --- a/UI/UI.vcxproj.filters +++ b/UI/UI.vcxproj.filters @@ -44,7 +44,6 @@ Screens - Screens @@ -76,6 +75,9 @@ Screens + + Screens + @@ -120,7 +122,6 @@ Screens - Screens @@ -153,10 +154,13 @@ Screens + + Screens + {faee5dce-633b-4ba6-b19d-ea70ee3c1c38} - + \ No newline at end of file diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 2e46086a0b93..8b40a36f2e1d 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 ea66a385985a..c688cd49f595 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/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 39d29e7f62ad..d420ac0dd192 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -851,17 +851,17 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) void CDisasm::Show(bool bShow) { if (deferredSymbolFill_ && bShow) { - if (g_symbolMap) + if (g_symbolMap) { g_symbolMap->FillSymbolListBox(GetDlgItem(m_hDlg, IDC_FUNCTIONLIST), ST_FUNCTION); - deferredSymbolFill_ = false; + deferredSymbolFill_ = false; + } } Dialog::Show(bShow); } void CDisasm::NotifyMapLoaded() { - if (m_bShowState == SW_SHOW) { - if (g_symbolMap) - g_symbolMap->FillSymbolListBox(GetDlgItem(m_hDlg, IDC_FUNCTIONLIST), ST_FUNCTION); + if (m_bShowState != SW_HIDE && g_symbolMap) { + g_symbolMap->FillSymbolListBox(GetDlgItem(m_hDlg, IDC_FUNCTIONLIST), ST_FUNCTION); } else { deferredSymbolFill_ = true; } diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index d7b3d93396de..5a14a4538073 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -261,7 +261,8 @@ void WindowsHost::PollControllers() { } void WindowsHost::BootDone() { - g_symbolMap->SortSymbols(); + if (g_symbolMap) + g_symbolMap->SortSymbols(); PostMessage(mainWindow_, WM_USER + 1, 0, 0); SetDebugMode(!g_Config.bAutoRun); @@ -295,6 +296,8 @@ static std::string SymbolMapFilename(const char *currentFilename, const char* ex } bool WindowsHost::AttemptLoadSymbolMap() { + if (!g_symbolMap) + return false; bool result1 = g_symbolMap->LoadSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart.c_str(),".ppmap").c_str()); // Load the old-style map file. if (!result1) @@ -304,11 +307,13 @@ bool WindowsHost::AttemptLoadSymbolMap() { } void WindowsHost::SaveSymbolMap() { - g_symbolMap->SaveSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart.c_str(),".ppmap").c_str()); + if (g_symbolMap) + g_symbolMap->SaveSymbolMap(SymbolMapFilename(PSP_CoreParameter().fileToStart.c_str(),".ppmap").c_str()); } void WindowsHost::NotifySymbolMapUpdated() { - g_symbolMap->SortSymbols(); + if (g_symbolMap) + g_symbolMap->SortSymbols(); PostMessage(mainWindow_, WM_USER + 1, 0, 0); } diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 964747a80418..82b5fb00822f 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="111010000" + android:versionName="1.11.1.0"> diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 4579fd9b7701..e3e9ba19ff25 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -413,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 \ diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index e2908badb9b6..c050b21c7755 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -84,6 +84,10 @@ bool RunTests() { } #endif + GraphicsContext *tempCtx = PSP_CoreParameter().graphicsContext; + // Clear the context during tests. We set it back later. + PSP_CoreParameter().graphicsContext = nullptr; + CoreParameter coreParam; coreParam.cpuCore = (CPUCore)g_Config.iCpuCore; coreParam.gpuCore = GPUCORE_SOFTWARE; @@ -175,6 +179,8 @@ bool RunTests() { PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; PSP_CoreParameter().headLess = false; + PSP_CoreParameter().graphicsContext = tempCtx; + g_Config.sReportHost = savedReportHost; return true; // Managed to execute the tests. Says nothing about the result. } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index a5a172432597..10fdf97e57a8 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -174,6 +174,7 @@ static float dp_yscale = 1.0f; static bool renderer_inited = false; static bool sustainedPerfSupported = false; +static std::mutex renderLock; // See NativeQueryConfig("androidJavaGL") to change this value. static bool javaGL = true; @@ -567,6 +568,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init EARLY_LOG("NativeApp.init() -- begin"); PROFILE_INIT(); + std::lock_guard guard(renderLock); renderer_inited = false; androidVersion = jAndroidVersion; deviceType = jdeviceType; @@ -776,9 +778,14 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { INFO_LOG(G3D, "Not shutting down renderer - not initialized"); } - inputBoxCallbacks.clear(); - NativeShutdown(); - VFSShutdown(); + { + std::lock_guard guard(renderLock); + inputBoxCallbacks.clear(); + NativeShutdown(); + VFSShutdown(); + } + + std::lock_guard guard(frameCommandLock); while (frameCommands.size()) frameCommands.pop(); INFO_LOG(SYSTEM, "NativeApp.shutdown() -- end"); @@ -929,10 +936,14 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv *en NativeInputBoxReceived(entry->second, result, value); } -void UpdateRunLoopAndroid(JNIEnv *env) { +void LockedNativeUpdateRender() { + std::lock_guard renderGuard(renderLock); NativeUpdate(); - NativeRender(graphicsContext); +} + +void UpdateRunLoopAndroid(JNIEnv *env) { + LockedNativeUpdateRender(); std::lock_guard guard(frameCommandLock); if (!nativeActivity) { @@ -1352,10 +1363,7 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J } } else { while (!exitRenderLoop) { - NativeUpdate(); - - NativeRender(graphicsContext); - + LockedNativeUpdateRender(); graphicsContext->SwapBuffers(); ProcessFrameCommands(env); diff --git a/assets/compat.ini b/assets/compat.ini index 96782365c5a0..ea7d5a42a728 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -489,6 +489,15 @@ 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] @@ -904,6 +913,17 @@ ULES00235 = true ULJM05225 = true CPCS01043 = true ULUS10062 = true +# LEGO Star Wars II: The Original Trilogy +ULES00479 = true +ULUS10155 = true +# Tony Hawk's Underground 2 Remix +ULES00033 = true +ULUS10014 = true +# Virtua Tennis: World Tour +ULES00126 = true +ULJM05079 = true +ULKS46023 = true +ULUS10037 = true [MemstickFixedFree] ULJM05571 = true @@ -940,6 +960,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 @@ -965,3 +991,28 @@ UCJS10007 = true UCES00001 = true UCKS45008 = true NPJG00059 = true + +[MpegAvcWarmUp] +# God Eater issue #13527 ,It is custom mpeg library that required sceMpegGetAvcAu return ERROR_MPEG_NO_DATA but break FIFA 14 issue #14086 +# God Eater 1 +ULJS00237 = true +ULKS46238 = true + +# God Eater 2 +ULJS00597 = true +NPJH50832 = true +ULJS19093 = true +NPJH50832 = true + +# God Eater Burst +ULJS00351 = true +NPJH50352 = true +ULJS00350 = true +ULKS46263 = true +ULUS10563 = true +ULES01519 = true +ULJS19056 = true +NPJH50352 = true +ULUS10563FV = true +ULJS19081 = true +NPJH50352 = true \ No newline at end of file diff --git a/assets/flash0/font/jpn0.pgf b/assets/flash0/font/jpn0.pgf index aaa56273f494..8ffe8678fe64 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 94d2a78baac3..9f8cce0390c8 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 8ededb5b975b..fbd61c3e55a6 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 bc28ad28e895..80159ef0a189 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 742c4ebf0aca..affcb5dbe1ea 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 1f718a4f7508..7ede4b750b54 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 0b8b2424cdf5..6f74068c2910 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 125d50d08dcf..5396321a6019 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 6da9a3e214a9..723feb795cb2 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 4b26ef5db029..36ca826ab752 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 8dcf9c4e5ae6..097e4114b867 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 d8d070f2c813..3be60de84978 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 d6d625f6cf17..04af73d16879 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 944b2b7c6156..e65835455aa0 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 fd4c27d129ff..0a28069c2668 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 8ce77b357982..8a284ce90795 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 4a55fdd32ec1..a58450c7675c 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 e444c250ab59..1518008a6707 100644 Binary files a/assets/flash0/font/ltn9.pgf and b/assets/flash0/font/ltn9.pgf differ diff --git a/assets/lang b/assets/lang index 17516d229eb3..6bd5b4bc9839 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 17516d229eb31e08d2029b225d4d831dc497376f +Subproject commit 6bd5b4bc983917ea8402f73c726b46e36f3de0b4 diff --git a/assets/shaders/defaultshaders.ini b/assets/shaders/defaultshaders.ini index 17b447b7c9b9..de05ac74daff 100644 --- a/assets/shaders/defaultshaders.ini +++ b/assets/shaders/defaultshaders.ini @@ -154,3 +154,9 @@ Type=Texture Name=4xBRZ Author=Hyllian Compute=tex_4xbrz.csh +[TexMMPX] +Type=Texture +Name=MMPX +Author=Morgan McGuire and Mara Gagiu +Compute=tex_mmpx.csh +MaxScale=2 diff --git a/assets/shaders/tex_mmpx.csh b/assets/shaders/tex_mmpx.csh new file mode 100644 index 000000000000..1ba45d1ffe85 --- /dev/null +++ b/assets/shaders/tex_mmpx.csh @@ -0,0 +1,117 @@ +/* MMPX.glc + Copyright 2020 Morgan McGuire & Mara Gagiu. + Provided under the Open Source MIT license https://opensource.org/licenses/MIT + + See js-demo.html for the commented source code. + This is an optimized GLSL port of that version + by Morgan McGuire and Mara Gagiu. +*/ + +#define ABGR8 uint + +ABGR8 src(int x, int y) { + return readColoru(uvec2(clamp(x, 0, params.width - 1), clamp(y, 0, params.height - 1))); +} + +uint luma(ABGR8 C) { + uint alpha = (C & 0xFF000000u) >> 24; + return (((C & 0x00FF0000u) >> 16) + ((C & 0x0000FF00u) >> 8) + (C & 0x000000FFu) + 1u) * (256u - alpha); +} + +bool all_eq2(ABGR8 B, ABGR8 A0, ABGR8 A1) { + return ((B ^ A0) | (B ^ A1)) == 0u; +} + +bool all_eq3(ABGR8 B, ABGR8 A0, ABGR8 A1, ABGR8 A2) { + return ((B ^ A0) | (B ^ A1) | (B ^ A2)) == 0u; +} + +bool all_eq4(ABGR8 B, ABGR8 A0, ABGR8 A1, ABGR8 A2, ABGR8 A3) { + return ((B ^ A0) | (B ^ A1) | (B ^ A2) | (B ^ A3)) == 0u; +} + +bool any_eq3(ABGR8 B, ABGR8 A0, ABGR8 A1, ABGR8 A2) { + return B == A0 || B == A1 || B == A2; +} + +bool none_eq2(ABGR8 B, ABGR8 A0, ABGR8 A1) { + return (B != A0) && (B != A1); +} + +bool none_eq4(ABGR8 B, ABGR8 A0, ABGR8 A1, ABGR8 A2, ABGR8 A3) { + return B != A0 && B != A1 && B != A2 && B != A3; +} + +uint applyScalingu(uvec2 origxy, uvec2 xy) { + int srcX = int(origxy.x); + int srcY = int(origxy.y); + + ABGR8 A = src(srcX - 1, srcY - 1), B = src(srcX, srcY - 1), C = src(srcX + 1, srcY - 1); + ABGR8 D = src(srcX - 1, srcY + 0), E = src(srcX, srcY + 0), F = src(srcX + 1, srcY + 0); + ABGR8 G = src(srcX - 1, srcY + 1), H = src(srcX, srcY + 1), I = src(srcX + 1, srcY + 1); + + ABGR8 J = E, K = E, L = E, M = E; + + if (((A ^ E) | (B ^ E) | (C ^ E) | (D ^ E) | (F ^ E) | (G ^ E) | (H ^ E) | (I ^ E)) != 0u) { + ABGR8 P = src(srcX, srcY - 2), S = src(srcX, srcY + 2); + ABGR8 Q = src(srcX - 2, srcY), R = src(srcX + 2, srcY); + ABGR8 Bl = luma(B), Dl = luma(D), El = luma(E), Fl = luma(F), Hl = luma(H); + + // 1:1 slope rules + if ((D == B && D != H && D != F) && (El >= Dl || E == A) && any_eq3(E, A, C, G) && ((El < Dl) || A != D || E != P || E != Q)) J = D; + if ((B == F && B != D && B != H) && (El >= Bl || E == C) && any_eq3(E, A, C, I) && ((El < Bl) || C != B || E != P || E != R)) K = B; + if ((H == D && H != F && H != B) && (El >= Hl || E == G) && any_eq3(E, A, G, I) && ((El < Hl) || G != H || E != S || E != Q)) L = H; + if ((F == H && F != B && F != D) && (El >= Fl || E == I) && any_eq3(E, C, G, I) && ((El < Fl) || I != H || E != R || E != S)) M = F; + + // Intersection rules + if ((E != F && all_eq4(E, C, I, D, Q) && all_eq2(F, B, H)) && (F != src(srcX + 3, srcY))) K = M = F; + if ((E != D && all_eq4(E, A, G, F, R) && all_eq2(D, B, H)) && (D != src(srcX - 3, srcY))) J = L = D; + if ((E != H && all_eq4(E, G, I, B, P) && all_eq2(H, D, F)) && (H != src(srcX, srcY + 3))) L = M = H; + if ((E != B && all_eq4(E, A, C, H, S) && all_eq2(B, D, F)) && (B != src(srcX, srcY - 3))) J = K = B; + if (Bl < El && all_eq4(E, G, H, I, S) && none_eq4(E, A, D, C, F)) J = K = B; + if (Hl < El && all_eq4(E, A, B, C, P) && none_eq4(E, D, G, I, F)) L = M = H; + if (Fl < El && all_eq4(E, A, D, G, Q) && none_eq4(E, B, C, I, H)) K = M = F; + if (Dl < El && all_eq4(E, C, F, I, R) && none_eq4(E, B, A, G, H)) J = L = D; + + // 2:1 slope rules + if (H != B) { + if (H != A && H != E && H != C) { + if (all_eq3(H, G, F, R) && none_eq2(H, D, src(srcX + 2, srcY - 1))) L = M; + if (all_eq3(H, I, D, Q) && none_eq2(H, F, src(srcX - 2, srcY - 1))) M = L; + } + + if (B != I && B != G && B != E) { + if (all_eq3(B, A, F, R) && none_eq2(B, D, src(srcX + 2, srcY + 1))) J = K; + if (all_eq3(B, C, D, Q) && none_eq2(B, F, src(srcX - 2, srcY + 1))) K = J; + } + } // H !== B + + if (F != D) { + if (D != I && D != E && D != C) { + if (all_eq3(D, A, H, S) && none_eq2(D, B, src(srcX + 1, srcY + 2))) J = L; + if (all_eq3(D, G, B, P) && none_eq2(D, H, src(srcX + 1, srcY - 2))) L = J; + } + + if (F != E && F != A && F != G) { + if (all_eq3(F, C, H, S) && none_eq2(F, B, src(srcX - 1, srcY + 2))) K = M; + if (all_eq3(F, I, B, P) && none_eq2(F, H, src(srcX - 1, srcY - 2))) M = K; + } + } // F !== D + } // not constant + + // TODO: Write four pixels at once. For now, 1/4x speed. + if ((xy.y & 1u) == 0u) { + if ((xy.x & 1u) == 0u) { + return J; + } + return K; + } + if ((xy.x & 1u) == 0u) { + return L; + } + return M; +} + +vec4 applyScalingf(uvec2 origxy, uvec2 xy) { + return unpackUnorm4x8(applyScalingu(origxy, xy)); +} diff --git a/b.sh b/b.sh index 55ab6e642d50..83aad6ba0248 100755 --- a/b.sh +++ b/b.sh @@ -61,7 +61,7 @@ do export CXX=/usr/bin/clang++ ;; --sanitize) echo "Enabling address-sanitizer if available" - CMAKE_ARGS="-DUSE_ADDRESS_SANITIZER=ON ${CMAKE_ARGS}" + CMAKE_ARGS="-DUSE_ASAN=ON ${CMAKE_ARGS}" ;; *) MAKE_OPT="$1 ${MAKE_OPT}" ;; diff --git a/cmake/Modules/FindFFmpeg.cmake b/cmake/Modules/FindFFmpeg.cmake index a8c482dc218e..648c0bacf39b 100644 --- a/cmake/Modules/FindFFmpeg.cmake +++ b/cmake/Modules/FindFFmpeg.cmake @@ -46,6 +46,15 @@ set(_FFmpeg_DEPS_postproc avutil) set(_FFmpeg_DEPS_swresample avutil) set(_FFmpeg_DEPS_swscale avutil) +set(_FFmpeg_HEADER_avcodec avcodec) +set(_FFmpeg_HEADER_avdevice avdevice) +set(_FFmpeg_HEADER_avfilter avfilter) +set(_FFmpeg_HEADER_avformat avformat) +set(_FFmpeg_HEADER_avutil avutil) +set(_FFmpeg_HEADER_postproc postprocess) +set(_FFmpeg_HEADER_swresample swresample) +set(_FFmpeg_HEADER_swscale swscale) + function(find_ffmpeg LIBNAME) if(DEFINED ENV{FFMPEG_DIR}) set(FFMPEG_DIR $ENV{FFMPEG_DIR}) @@ -84,7 +93,7 @@ function(find_ffmpeg LIBNAME) ) endif() - find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${_FFmpeg_HEADER_${LIBNAME}}.h HINTS ${INCLUDE_PATHS} ) @@ -92,12 +101,14 @@ function(find_ffmpeg LIBNAME) HINTS ${LIB_PATHS} ) - if(NOT FFMPEG_DIR AND (NOT FFmpeg_LIBRARY_${LIBNAME} OR NOT FFmpeg_INCLUDE_${LIBNAME})) + if(NOT FFMPEG_DIR AND ( + FFmpeg_LIBRARY_${LIBNAME} STREQUAL FFmpeg_LIBRARY_${LIBNAME}-NOTFOUND + OR FFmpeg_INCLUDE_${LIBNAME} STREQUAL FFmpeg_INCLUDE_${LIBNAME}-NOTFOUND)) # Didn't find it in the usual paths, try pkg-config find_package(PkgConfig QUIET) pkg_check_modules(FFmpeg_PKGCONFIG_${LIBNAME} REQUIRED QUIET lib${LIBNAME}) - find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${LIBNAME}.h + find_path(FFmpeg_INCLUDE_${LIBNAME} lib${LIBNAME}/${_FFmpeg_HEADER_${LIBNAME}}.h ${FFmpeg_PKGCONFIG_${LIBNAME}_INCLUDE_DIRS} ) diff --git a/ffmpeg b/ffmpeg index 4738685f4ac2..0b28335acea4 160000 --- a/ffmpeg +++ b/ffmpeg @@ -1 +1 @@ -Subproject commit 4738685f4ac27c1775a238d1e602f399627b5e6f +Subproject commit 0b28335acea4f429ae798c5e75232e54881bf164 diff --git a/headless/Compare.cpp b/headless/Compare.cpp index e9fa38be78e8..5c96a7cce436 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 +#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" -#include -#include -#include -#include -#include -#include bool teamCityMode = false; std::string currentTestName = ""; @@ -184,14 +187,24 @@ struct BufferedLineReaderFile : public BufferedLineReader { 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/StubHost.cpp b/headless/StubHost.cpp index 1219d42365b5..27832dc2a028 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/libretro/Makefile b/libretro/Makefile index a047d9a2e881..0497a5b1ebdb 100644 --- a/libretro/Makefile +++ b/libretro/Makefile @@ -304,7 +304,7 @@ else ifneq (,$(findstring windows_msvc2017,$(platform))) filter_out1 = $(filter-out $(firstword $1),$1) filter_out2 = $(call filter_out1,$(call filter_out1,$1)) - reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul))) + reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>/dev/null))) fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1)) b1 := ( @@ -415,7 +415,7 @@ else ifneq (,$(findstring windows_msvc2019,$(platform))) filter_out1 = $(filter-out $(firstword $1),$1) filter_out2 = $(call filter_out1,$(call filter_out1,$1)) - reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>nul))) + reg_query = $(call filter_out2,$(subst $2,,$(shell reg query "$2" -v "$1" 2>/dev/null))) fix_path = $(subst $(SPACE),\ ,$(subst \,/,$1)) b1 := ( diff --git a/libretro/README_WINDOWS.txt b/libretro/README_WINDOWS.txt index 755658dca4bb..5bdc37e13511 100644 --- a/libretro/README_WINDOWS.txt +++ b/libretro/README_WINDOWS.txt @@ -13,7 +13,7 @@ pacman -S make Then use the following in msys: cd libretro -make platform=windows_msvc2019_desktop_x64 -j32 && cp ppsspp_libretro.* /d/retroarch/cores && rm nul +make platform=windows_msvc2019_desktop_x64 -j32 && cp ppsspp_libretro.* /d/retroarch/cores Note that the latter part copies the DLL/PDB into wherever retroarch reads it from. Might need to adjust the path, and adjust -j32 depending on your number of logical CPUs - might not need that many threads (or you might need more...). diff --git a/pspautotests b/pspautotests index 7e4a1e750997..1047400eaec6 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 7e4a1e750997e95df8a1f04f9a6396801adf7113 +Subproject commit 1047400eaec6bcbdb2a64d326375ef6a6617c4ac