diff --git a/.github/linux-appimage-sdl.sh b/.github/linux-appimage-sdl.sh index 7961f5312..d85aa6c4c 100755 --- a/.github/linux-appimage-sdl.sh +++ b/.github/linux-appimage-sdl.sh @@ -8,8 +8,8 @@ if [[ -z $GITHUB_WORKSPACE ]]; then fi # Prepare Tools for building the AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget --waitretry=3 --read-timeout=20 --timeout=15 --tries=5 -q https://github.com/linuxdeploy/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt-x86_64.sh chmod a+x linuxdeploy-x86_64.AppImage chmod a+x linuxdeploy-plugin-checkrt-x86_64.sh diff --git a/src/common/config.h b/src/common/config.h index 7a351d424..b341030e0 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -25,8 +25,8 @@ enum HideCursorState : int { Never, Idle, Always }; enum GpuReadbacksMode : int { Disabled, - Low, - High, + Relaxed, + Precise, }; void load(const std::filesystem::path& path, bool is_game_specific = false); diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index cf36da0cc..247c387fe 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -276,7 +276,10 @@ int PS4_SYSV_ABI sceKernelRemoveExceptionHandler(s32 signum) { return ORBIS_KERNEL_ERROR_EINVAL; } int const native_signum = OrbisToNativeSignal(signum); - ASSERT_MSG(Handlers[native_signum], "Invalid parameters"); + if (!Handlers[native_signum]) { + LOG_WARNING(Lib_Kernel, "removing non-installed handler for signum {}", signum); + return ORBIS_KERNEL_ERROR_EINVAL; + } Handlers[native_signum] = nullptr; #ifndef _WIN64 if (native_signum == SIGSEGV || native_signum == SIGBUS || native_signum == SIGILL) { diff --git a/src/core/libraries/network/ssl2.cpp b/src/core/libraries/network/ssl2.cpp index 682095801..0b408d094 100644 --- a/src/core/libraries/network/ssl2.cpp +++ b/src/core/libraries/network/ssl2.cpp @@ -5,6 +5,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/network/ssl2.h" +#include "core/libraries/network/ssl2_error.h" namespace Libraries::Ssl2 { @@ -108,8 +109,13 @@ int PS4_SYSV_ABI sceSslEnableVerifyOption() { return ORBIS_OK; } -int PS4_SYSV_ABI sceSslFreeCaCerts() { - LOG_ERROR(Lib_Ssl2, "(STUBBED) called"); +int PS4_SYSV_ABI sceSslFreeCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { + LOG_ERROR(Lib_Ssl2, "(DUMMY) called"); + if (certs == nullptr) { + return ORBIS_SSL_ERROR_INVALID_ARGUMENT; + } + delete (certs->certs); + // delete (certs->pool); return ORBIS_OK; } @@ -128,17 +134,13 @@ int PS4_SYSV_ABI sceSslGetAlpnSelected() { return ORBIS_OK; } -struct OrbisSslCaCerts { - void* certs; - u64 num; - void* pool; -}; - -int PS4_SYSV_ABI sceSslGetCaCerts(int sslCtxId, OrbisSslCaCerts* certs) { - // check if it is same as libSceSsl +int PS4_SYSV_ABI sceSslGetCaCerts(s32 ssl_ctx_id, OrbisSslCaCerts* certs) { LOG_ERROR(Lib_Ssl2, "(DUMMY) called"); - certs->certs = nullptr; - certs->num = 0; + if (certs == nullptr) { + return ORBIS_SSL_ERROR_INVALID_ARGUMENT; + } + certs->certs = new OrbisSslData{nullptr, 0}; + certs->num = 1; certs->pool = nullptr; return ORBIS_OK; } diff --git a/src/core/libraries/network/ssl2.h b/src/core/libraries/network/ssl2.h index 754dda40c..18fb205d3 100644 --- a/src/core/libraries/network/ssl2.h +++ b/src/core/libraries/network/ssl2.h @@ -10,5 +10,17 @@ class SymbolsResolver; } namespace Libraries::Ssl2 { + +struct OrbisSslData { + char* ptr; + u64 size; +}; + +struct OrbisSslCaCerts { + OrbisSslData* certs; + u64 num; + void* pool; +}; + void RegisterLib(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Ssl2 \ No newline at end of file diff --git a/src/core/libraries/network/ssl2_error.h b/src/core/libraries/network/ssl2_error.h new file mode 100644 index 000000000..03bf94256 --- /dev/null +++ b/src/core/libraries/network/ssl2_error.h @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_SSL_ERROR_INVALID_ARGUMENT = 0x8095F007; \ No newline at end of file diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index a5199c297..48b086457 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -333,6 +333,9 @@ static bool match(std::string_view str, std::string_view pattern) { for (auto str_wild_it = str_it; str_wild_it <= str.end(); ++str_wild_it) { if (match({str_wild_it, str.end()}, {pat_it + 1, pattern.end()})) { return true; + } else if (str_wild_it == str.end()) { + // Avoid incrementing str_wild_it past str.end(). + break; } } return false; diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index e0c64ff4a..72286b29c 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -458,6 +458,7 @@ void Translator::IMAGE_STORE(bool has_mip, const GcnInst& inst) { IR::TextureInstInfo info{}; info.has_lod.Assign(has_mip); info.is_array.Assign(mimg.da); + info.is_r128.Assign(mimg.r128); boost::container::static_vector comps; for (u32 i = 0; i < 4; i++) { diff --git a/src/shader_recompiler/resource.h b/src/shader_recompiler/resource.h index 5d9965105..5ae3179f6 100644 --- a/src/shader_recompiler/resource.h +++ b/src/shader_recompiler/resource.h @@ -86,6 +86,7 @@ struct ImageResource { } else { const auto raw = info.template ReadUdSharp(sharp_idx); std::memcpy(&image, &raw, sizeof(raw)); + image.pitch = image.width; } if (!image.Valid()) { LOG_DEBUG(Render_Vulkan, "Encountered invalid image sharp"); diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index f9da020d1..ecf9406af 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -95,7 +95,7 @@ public: } if constexpr (type == Type::CPU) { UpdateProtection(); - } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::High) { + } else if (Config::getReadbacksMode() == Config::GpuReadbacksMode::Precise) { UpdateProtection(); } } diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index 418641bc3..972f028d4 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -461,33 +461,36 @@ static std::pair SanitizeCopyLayers(const ImageInfo& src_info, const I void Image::CopyImage(Image& src_image) { const auto& src_info = src_image.info; + const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); - // Check format compatibility + // Format mismatch warning (safe but useful) if (src_info.pixel_format != info.pixel_format) { LOG_DEBUG(Render_Vulkan, - "Copy between different formats: src={}, dst={}. Color may be incorrect.", + "Copy between different formats: src={}, dst={}. " + "Result may be undefined.", vk::to_string(src_info.pixel_format), vk::to_string(info.pixel_format)); } - const u32 width = src_info.size.width; - const u32 height = src_info.size.height; + const u32 base_width = src_info.size.width; + const u32 base_height = src_info.size.height; const u32 base_depth = info.type == AmdGpu::ImageType::Color3D ? info.size.depth : src_info.size.depth; - auto [test_src_layers, test_dst_layers] = SanitizeCopyLayers(src_info, info, base_depth); - - ASSERT(test_src_layers == test_dst_layers || num_mips == 1 || - (ConvertImageType(src_info.type) != ConvertImageType(info.type) && - (test_src_layers == 1 || test_dst_layers == 1))); - + // Match sample count before copying SetBackingSamples(info.num_samples, false); src_image.SetBackingSamples(src_info.num_samples); - boost::container::small_vector image_copies; + boost::container::small_vector regions; + + const vk::ImageAspectFlags src_aspect = + src_image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil; + + const vk::ImageAspectFlags dst_aspect = aspect_mask & ~vk::ImageAspectFlagBits::eStencil; const bool src_is_2d = ConvertImageType(src_info.type) == vk::ImageType::e2D; const bool src_is_3d = ConvertImageType(src_info.type) == vk::ImageType::e3D; + const bool dst_is_2d = ConvertImageType(info.type) == vk::ImageType::e2D; const bool dst_is_3d = ConvertImageType(info.type) == vk::ImageType::e3D; @@ -495,103 +498,68 @@ void Image::CopyImage(Image& src_image) { const bool is_3d_to_2d = src_is_3d && dst_is_2d; const bool is_same_type = !is_2d_to_3d && !is_3d_to_2d; - // Determine aspect mask - exclude stencil - vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; - - // For depth/stencil images, only copy the depth aspect (skip stencil) - if (src_image.aspect_mask & vk::ImageAspectFlagBits::eDepth) { - aspect = vk::ImageAspectFlagBits::eDepth; - } - for (u32 mip = 0; mip < num_mips; ++mip) { - const auto mip_w = std::max(width >> mip, 1u); - const auto mip_h = std::max(height >> mip, 1u); - const auto mip_d = std::max(base_depth >> mip, 1u); + const u32 mip_w = std::max(base_width >> mip, 1u); + const u32 mip_h = std::max(base_height >> mip, 1u); + const u32 mip_d = std::max(base_depth >> mip, 1u); auto [src_layers, dst_layers] = SanitizeCopyLayers(src_info, info, mip_d); + vk::ImageCopy region{}; + + region.srcSubresource.aspectMask = src_aspect; + region.srcSubresource.mipLevel = mip; + region.srcSubresource.baseArrayLayer = 0; + + region.dstSubresource.aspectMask = dst_aspect; + region.dstSubresource.mipLevel = mip; + region.dstSubresource.baseArrayLayer = 0; + if (is_same_type) { - u32 copy_layers = std::min(src_layers, dst_layers); - - if (src_is_3d) - src_layers = 1; - if (dst_is_3d) - dst_layers = 1; - - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = copy_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, mip_d), - }; - image_copies.push_back(copy_region); + // 2D->2D OR 3D->3D + if (src_is_3d) { + // 3D images must use layerCount=1 + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, mip_d); + } else { + // Array images + const u32 copy_layers = std::min(src_layers, dst_layers); + region.srcSubresource.layerCount = copy_layers; + region.dstSubresource.layerCount = copy_layers; + region.extent = vk::Extent3D(mip_w, mip_h, 1); + } } else if (is_2d_to_3d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = src_layers, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .extent = vk::Extent3D(mip_w, mip_h, src_layers), - }; - image_copies.push_back(copy_region); + // 2D array -> 3D volume + region.srcSubresource.layerCount = src_layers; + region.dstSubresource.layerCount = 1; + region.extent = vk::Extent3D(mip_w, mip_h, src_layers); } else if (is_3d_to_2d) { - vk::ImageCopy copy_region = { - .srcSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = 1, - }, - .dstSubresource{ - .aspectMask = aspect, - .mipLevel = mip, - .baseArrayLayer = 0, - .layerCount = dst_layers, - }, - .extent = vk::Extent3D(mip_w, mip_h, dst_layers), - }; - image_copies.push_back(copy_region); + // 3D volume -> 2D array + region.srcSubresource.layerCount = 1; + region.dstSubresource.layerCount = dst_layers; + region.extent = vk::Extent3D(mip_w, mip_h, dst_layers); } + + regions.push_back(region); } scheduler->EndRendering(); - // Remove the pipeline stage flags - they don't belong here src_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); auto cmdbuf = scheduler->CommandBuffer(); - if (!image_copies.empty()) { - cmdbuf.copyImage(src_image.GetImage(), vk::ImageLayout::eTransferSrcOptimal, GetImage(), - vk::ImageLayout::eTransferDstOptimal, image_copies); + if (!regions.empty()) { + cmdbuf.copyImage(src_image.GetImage(), src_image.backing->state.layout, GetImage(), + backing->state.layout, regions); } - // Remove pipeline stage flags here too - src_image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, - {}); - - Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); + Transit(vk::ImageLayout::eGeneral, + vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eTransferRead, {}); } - void Image::CopyImageWithBuffer(Image& src_image, vk::Buffer buffer, u64 offset) { const auto& src_info = src_image.info; const u32 num_mips = std::min(src_info.resources.levels, info.resources.levels); diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 0da9c8bfb..543e144d2 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -48,7 +48,7 @@ struct ImageInfo { } Extent2D BlockDim() const { const auto dim = props.is_block ? 2 : 0; - return Extent2D{size.width >> dim, size.height >> dim}; + return Extent2D{pitch >> dim, size.height >> dim}; } s32 MipOf(const ImageInfo& info) const; diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a8d846cfc..8163902cc 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -297,14 +297,6 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == cache_image.info.guest_address) { const u32 lhs_block_size = image_info.num_bits * image_info.num_samples; const u32 rhs_block_size = cache_image.info.num_bits * cache_image.info.num_samples; - - if (image_info.pitch != cache_image.info.pitch) { - if (safe_to_delete) { - FreeImage(cache_image_id); - } - return {merged_image_id, -1, -1}; - } - if (image_info.BlockDim() != cache_image.info.BlockDim() || lhs_block_size != rhs_block_size) { // Very likely this kind of overlap is caused by allocation from a pool.