From b3739bea92d9028419b295742c15203b150d5b4d Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:14:34 -0800 Subject: [PATCH 01/65] renderer_vulkan: Simplify debug marker settings. (#2159) * renderer_vulkan: Simplify debug marker settings. * liverpool: Add scope markers for graphics/compute queues. * liverpool: Remove unneeded extra label from command buffer markers. * vk_rasterizer: Add scopes around filtered draw passes. --- src/common/config.cpp | 38 +++++++----- src/common/config.h | 3 +- src/emulator.cpp | 5 +- src/imgui/renderer/imgui_core.cpp | 4 +- src/video_core/amdgpu/liverpool.cpp | 46 +++++++++----- src/video_core/buffer_cache/buffer.cpp | 2 + .../renderer_vulkan/vk_instance.cpp | 11 +--- src/video_core/renderer_vulkan/vk_instance.h | 11 +--- src/video_core/renderer_vulkan/vk_platform.h | 7 +++ .../renderer_vulkan/vk_rasterizer.cpp | 62 ++++++++++++------- .../renderer_vulkan/vk_rasterizer.h | 9 +-- .../renderer_vulkan/vk_resource_pool.cpp | 10 +-- .../renderer_vulkan/vk_swapchain.cpp | 15 ++--- src/video_core/texture_cache/image_view.cpp | 7 +++ 14 files changed, 132 insertions(+), 98 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 158bfeddf..9c842f8b7 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -61,9 +61,10 @@ static u32 vblankDivider = 1; static bool vkValidation = false; static bool vkValidationSync = false; static bool vkValidationGpu = false; -static bool rdocEnable = false; -static bool vkMarkers = false; static bool vkCrashDiagnostic = false; +static bool vkHostMarkers = false; +static bool vkGuestMarkers = false; +static bool rdocEnable = false; static s16 cursorState = HideCursorState::Idle; static int cursorHideTimeout = 5; // 5 seconds (default) static bool separateupdatefolder = false; @@ -227,10 +228,6 @@ bool isRdocEnabled() { return rdocEnable; } -bool isMarkersEnabled() { - return vkMarkers; -} - u32 vblankDiv() { return vblankDivider; } @@ -247,14 +244,20 @@ bool vkValidationGpuEnabled() { return vkValidationGpu; } -bool vkMarkersEnabled() { - return vkMarkers || vkCrashDiagnostic; // Crash diagnostic forces markers on -} - bool vkCrashDiagnosticEnabled() { return vkCrashDiagnostic; } +bool vkHostMarkersEnabled() { + // Forced on when crash diagnostic enabled. + return vkHostMarkers || vkCrashDiagnostic; +} + +bool vkGuestMarkersEnabled() { + // Forced on when crash diagnostic enabled. + return vkGuestMarkers || vkCrashDiagnostic; +} + bool getSeparateUpdateEnabled() { return separateupdatefolder; } @@ -644,9 +647,10 @@ void load(const std::filesystem::path& path) { vkValidation = toml::find_or(vk, "validation", false); vkValidationSync = toml::find_or(vk, "validation_sync", false); vkValidationGpu = toml::find_or(vk, "validation_gpu", true); - rdocEnable = toml::find_or(vk, "rdocEnable", false); - vkMarkers = toml::find_or(vk, "rdocMarkersEnable", false); vkCrashDiagnostic = toml::find_or(vk, "crashDiagnostic", false); + vkHostMarkers = toml::find_or(vk, "hostMarkers", false); + vkGuestMarkers = toml::find_or(vk, "guestMarkers", false); + rdocEnable = toml::find_or(vk, "rdocEnable", false); } if (data.contains("Debug")) { @@ -752,9 +756,10 @@ void save(const std::filesystem::path& path) { data["Vulkan"]["validation"] = vkValidation; data["Vulkan"]["validation_sync"] = vkValidationSync; data["Vulkan"]["validation_gpu"] = vkValidationGpu; - data["Vulkan"]["rdocEnable"] = rdocEnable; - data["Vulkan"]["rdocMarkersEnable"] = vkMarkers; data["Vulkan"]["crashDiagnostic"] = vkCrashDiagnostic; + data["Vulkan"]["hostMarkers"] = vkHostMarkers; + data["Vulkan"]["guestMarkers"] = vkGuestMarkers; + data["Vulkan"]["rdocEnable"] = rdocEnable; data["Debug"]["DebugDump"] = isDebugDump; data["Debug"]["CollectShader"] = isShaderDebug; @@ -852,9 +857,10 @@ void setDefaultValues() { vkValidation = false; vkValidationSync = false; vkValidationGpu = false; - rdocEnable = false; - vkMarkers = false; vkCrashDiagnostic = false; + vkHostMarkers = false; + vkGuestMarkers = false; + rdocEnable = false; emulator_language = "en"; m_language = 1; gpuId = -1; diff --git a/src/common/config.h b/src/common/config.h index c86e35ebc..f9e4c2815 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -100,8 +100,9 @@ void setRdocEnabled(bool enable); bool vkValidationEnabled(); bool vkValidationSyncEnabled(); bool vkValidationGpuEnabled(); -bool vkMarkersEnabled(); bool vkCrashDiagnosticEnabled(); +bool vkHostMarkersEnabled(); +bool vkGuestMarkersEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); diff --git a/src/emulator.cpp b/src/emulator.cpp index dbe693340..61d6d3862 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -66,9 +66,10 @@ Emulator::Emulator() { LOG_INFO(Config, "Vulkan vkValidation: {}", Config::vkValidationEnabled()); LOG_INFO(Config, "Vulkan vkValidationSync: {}", Config::vkValidationSyncEnabled()); LOG_INFO(Config, "Vulkan vkValidationGpu: {}", Config::vkValidationGpuEnabled()); - LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled()); - LOG_INFO(Config, "Vulkan rdocMarkersEnable: {}", Config::vkMarkersEnabled()); LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::vkCrashDiagnosticEnabled()); + LOG_INFO(Config, "Vulkan hostMarkers: {}", Config::vkHostMarkersEnabled()); + LOG_INFO(Config, "Vulkan guestMarkers: {}", Config::vkGuestMarkersEnabled()); + LOG_INFO(Config, "Vulkan rdocEnable: {}", Config::isRdocEnabled()); // Create stdin/stdout/stderr Common::Singleton::Instance()->CreateStdHandles(); diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 46391faef..335185473 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -199,7 +199,7 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { return; } - if (Config::vkMarkersEnabled()) { + if (Config::vkHostMarkersEnabled()) { cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ .pLabelName = "ImGui Render", }); @@ -224,7 +224,7 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { cmdbuf.beginRendering(render_info); Vulkan::RenderDrawData(*draw_data, cmdbuf); cmdbuf.endRendering(); - if (Config::vkMarkersEnabled()) { + if (Config::vkHostMarkersEnabled()) { cmdbuf.endDebugUtilsLabelEXT(); } } diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 16ed84f74..036a031a7 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -224,6 +224,10 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanScopeMarkerBegin("gfx"); + } + const auto base_addr = reinterpret_cast(dcb.data()); while (!dcb.empty()) { const auto* header = reinterpret_cast(dcb.data()); @@ -260,7 +264,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(&nop->data_block[1]), marker_sz}; if (rasterizer) { - rasterizer->ScopeMarkerBegin(label); + rasterizer->ScopeMarkerBegin(label, true); } break; } @@ -271,13 +275,13 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span( reinterpret_cast(&nop->data_block[1]) + marker_sz); if (rasterizer) { - rasterizer->ScopedMarkerInsertColor(label, color); + rasterizer->ScopedMarkerInsertColor(label, color, true); } break; } case PM4CmdNop::PayloadType::DebugMarkerPop: { if (rasterizer) { - rasterizer->ScopeMarkerEnd(); + rasterizer->ScopeMarkerEnd(true); } break; } @@ -412,7 +416,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndex2", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndex2", cmd_address)); rasterizer->Draw(true); rasterizer->ScopeMarkerEnd(); } @@ -429,8 +433,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DrawIndexOffset2", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexOffset2", cmd_address)); rasterizer->Draw(true, draw_index_off->index_offset); rasterizer->ScopeMarkerEnd(); } @@ -445,7 +448,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndexAuto", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexAuto", cmd_address)); rasterizer->Draw(false); rasterizer->ScopeMarkerEnd(); } @@ -460,7 +463,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:DrawIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndirect", cmd_address)); rasterizer->DrawIndirect(false, indirect_args_addr, offset, size, 1, 0); rasterizer->ScopeMarkerEnd(); } @@ -476,8 +479,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DrawIndexIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexIndirect", cmd_address)); rasterizer->DrawIndirect(true, indirect_args_addr, offset, size, 1, 0); rasterizer->ScopeMarkerEnd(); } @@ -493,7 +495,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DrawIndexIndirectCountMulti", cmd_address)); + fmt::format("{}:DrawIndexIndirectCountMulti", cmd_address)); rasterizer->DrawIndirect( true, indirect_args_addr, offset, draw_index_indirect->stride, draw_index_indirect->count, draw_index_indirect->countAddr); @@ -514,7 +516,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("dcb:{}:Dispatch", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchDirect", cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -532,8 +534,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin( - fmt::format("dcb:{}:DispatchIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchIndirect", cmd_address)); rasterizer->DispatchIndirect(indirect_args_addr, offset, size); rasterizer->ScopeMarkerEnd(); } @@ -701,6 +702,10 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanScopeMarkerEnd(); + } + if (ce_task.handle) { ASSERT_MSG(ce_task.handle.done(), "Partially processed CCB"); ce_task.handle.destroy(); @@ -714,6 +719,10 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); const auto& queue = asc_queues[{vqid}]; + if (rasterizer) { + rasterizer->ScopeMarkerBegin(fmt::format("asc[{}]", vqid)); + } + auto base_addr = reinterpret_cast(acb.data()); while (!acb.empty()) { const auto* header = reinterpret_cast(acb.data()); @@ -811,8 +820,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin( - fmt::format("acb[{}]:{}:DispatchIndirect", vqid, cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchDirect", cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -830,7 +838,7 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchIndirect", cmd_address)); rasterizer->DispatchIndirect(ib_address, 0, size); rasterizer->ScopeMarkerEnd(); } @@ -878,6 +886,10 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } } + if (rasterizer) { + rasterizer->ScopeMarkerEnd(); + } + FIBER_EXIT; } diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index 5a049c185..a8d1271c6 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -131,6 +131,8 @@ vk::BufferView Buffer::View(u32 offset, u32 size, bool is_written, AmdGpu::DataF vk::to_string(view_result)); scheduler->DeferOperation( [view, device = instance->GetDevice()] { device.destroyBufferView(view); }); + Vulkan::SetObjectName(instance->GetDevice(), view, "BufferView {:#x}:{:#x}", cpu_addr + offset, + size); return view; } diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 6c3e066c6..f5bcb54b6 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -92,15 +92,13 @@ std::string GetReadableVersion(u32 version) { Instance::Instance(bool enable_validation, bool enable_crash_diagnostic) : instance{CreateInstance(Frontend::WindowSystemType::Headless, enable_validation, enable_crash_diagnostic)}, - physical_devices{EnumeratePhysicalDevices(instance)}, - crash_diagnostic{enable_crash_diagnostic} {} + physical_devices{EnumeratePhysicalDevices(instance)} {} Instance::Instance(Frontend::WindowSDL& window, s32 physical_device_index, bool enable_validation /*= false*/, bool enable_crash_diagnostic /*= false*/) : instance{CreateInstance(window.GetWindowInfo().type, enable_validation, enable_crash_diagnostic)}, - physical_devices{EnumeratePhysicalDevices(instance)}, - crash_diagnostic{enable_crash_diagnostic} { + physical_devices{EnumeratePhysicalDevices(instance)} { if (enable_validation) { debug_callback = CreateDebugCallback(*instance); } @@ -562,10 +560,7 @@ void Instance::CollectToolingInfo() { return; } for (const vk::PhysicalDeviceToolProperties& tool : tools) { - const std::string_view name = tool.name; - LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); - has_renderdoc = has_renderdoc || name == "RenderDoc"; - has_nsight_graphics = has_nsight_graphics || name == "NVIDIA Nsight Graphics"; + LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", tool.name); } } diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 8928b4267..e0d4d0b4d 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -79,11 +79,6 @@ public: return profiler_context; } - /// Returns true when a known debugging tool is attached. - bool HasDebuggingToolAttached() const { - return crash_diagnostic || has_renderdoc || has_nsight_graphics; - } - /// Returns true if anisotropic filtering is supported bool IsAnisotropicFilteringSupported() const { return features.samplerAnisotropy; @@ -340,13 +335,9 @@ private: bool legacy_vertex_attributes{}; bool image_load_store_lod{}; bool amd_gcn_shader{}; + bool tooling_info{}; u64 min_imported_host_pointer_alignment{}; u32 subgroup_size{}; - bool tooling_info{}; - bool debug_utils_supported{}; - bool crash_diagnostic{}; - bool has_nsight_graphics{}; - bool has_renderdoc{}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h index 6b425b6d8..d05d12997 100644 --- a/src/video_core/renderer_vulkan/vk_platform.h +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -7,6 +7,7 @@ #include #include +#include "common/config.h" #include "common/logging/log.h" #include "common/types.h" #include "video_core/renderer_vulkan/vk_common.h" @@ -32,6 +33,9 @@ concept VulkanHandleType = vk::isVulkanHandleType::value; template void SetObjectName(vk::Device device, const HandleType& handle, std::string_view debug_name) { + if (!Config::vkHostMarkersEnabled()) { + return; + } const vk::DebugUtilsObjectNameInfoEXT name_info = { .objectType = HandleType::objectType, .objectHandle = reinterpret_cast(static_cast(handle)), @@ -46,6 +50,9 @@ void SetObjectName(vk::Device device, const HandleType& handle, std::string_view template void SetObjectName(vk::Device device, const HandleType& handle, const char* format, const Args&... args) { + if (!Config::vkHostMarkersEnabled()) { + return; + } const std::string debug_name = fmt::vformat(format, fmt::make_format_args(args...)); SetObjectName(device, handle, debug_name); } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 06cfbedac..bac647125 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -58,6 +58,7 @@ bool Rasterizer::FilterDraw() { if (regs.color_control.mode == Liverpool::ColorControl::OperationMode::FmaskDecompress) { // TODO: check for a valid MRT1 to promote the draw to the resolve pass. LOG_TRACE(Render_Vulkan, "FMask decompression pass skipped"); + ScopedMarkerInsert("FmaskDecompress"); return false; } if (regs.color_control.mode == Liverpool::ColorControl::OperationMode::Resolve) { @@ -67,6 +68,7 @@ bool Rasterizer::FilterDraw() { } if (regs.primitive_type == AmdGpu::PrimitiveType::None) { LOG_TRACE(Render_Vulkan, "Primitive type 'None' skipped"); + ScopedMarkerInsert("PrimitiveTypeNone"); return false; } @@ -244,10 +246,13 @@ void Rasterizer::EliminateFastClear() { .layerCount = col_buf.view.slice_max - col_buf.view.slice_start + 1, }; scheduler.EndRendering(); + ScopeMarkerBegin(fmt::format("EliminateFastClear:MRT={:#x}:M={:#x}", col_buf.Address(), + col_buf.CmaskAddress())); image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, {}); scheduler.CommandBuffer().clearColorImage(image.image, image.last_state.layout, LiverpoolToVK::ColorBufferClearValue(col_buf).color, range); + ScopeMarkerEnd(); } void Rasterizer::Draw(bool is_indexed, u32 index_offset) { @@ -842,8 +847,6 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s } void Rasterizer::Resolve() { - const auto cmdbuf = scheduler.CommandBuffer(); - // Read from MRT0, average all samples, and write to MRT1, which is one-sample const auto& mrt0_hint = liverpool->last_cb_extent[0]; const auto& mrt1_hint = liverpool->last_cb_extent[1]; @@ -863,9 +866,12 @@ void Rasterizer::Resolve() { mrt1_range.base.layer = liverpool->regs.color_buffers[1].view.slice_start; mrt1_range.extent.layers = liverpool->regs.color_buffers[1].NumSlices() - mrt1_range.base.layer; + ScopeMarkerBegin(fmt::format("Resolve:MRT0={:#x}:MRT1={:#x}", + liverpool->regs.color_buffers[0].Address(), + liverpool->regs.color_buffers[1].Address())); + mrt0_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, mrt0_range); - mrt1_image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, mrt1_range); @@ -892,8 +898,9 @@ void Rasterizer::Resolve() { .dstOffset = {0, 0, 0}, .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, }; - cmdbuf.copyImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, - vk::ImageLayout::eTransferDstOptimal, region); + scheduler.CommandBuffer().copyImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, + mrt1_image.image, vk::ImageLayout::eTransferDstOptimal, + region); } else { vk::ImageResolve region = { .srcSubresource = @@ -914,9 +921,12 @@ void Rasterizer::Resolve() { .dstOffset = {0, 0, 0}, .extent = {mrt1_image.info.size.width, mrt1_image.info.size.height, 1}, }; - cmdbuf.resolveImage(mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, - mrt1_image.image, vk::ImageLayout::eTransferDstOptimal, region); + scheduler.CommandBuffer().resolveImage( + mrt0_image.image, vk::ImageLayout::eTransferSrcOptimal, mrt1_image.image, + vk::ImageLayout::eTransferDstOptimal, region); } + + ScopeMarkerEnd(); } void Rasterizer::DepthStencilCopy(bool is_depth, bool is_stencil) { @@ -936,6 +946,11 @@ void Rasterizer::DepthStencilCopy(bool is_depth, bool is_stencil) { sub_range.base.layer = liverpool->regs.depth_view.slice_start; sub_range.extent.layers = liverpool->regs.depth_view.NumSlices() - sub_range.base.layer; + ScopeMarkerBegin(fmt::format( + "DepthStencilCopy:DR={:#x}:SR={:#x}:DW={:#x}:SW={:#x}", regs.depth_buffer.DepthAddress(), + regs.depth_buffer.StencilAddress(), regs.depth_buffer.DepthWriteAddress(), + regs.depth_buffer.StencilWriteAddress())); + read_image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, sub_range); write_image.Transit(vk::ImageLayout::eTransferDstOptimal, vk::AccessFlagBits2::eTransferWrite, @@ -967,9 +982,11 @@ void Rasterizer::DepthStencilCopy(bool is_depth, bool is_stencil) { .dstOffset = {0, 0, 0}, .extent = {write_image.info.size.width, write_image.info.size.height, 1}, }; - const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyImage(read_image.image, vk::ImageLayout::eTransferSrcOptimal, write_image.image, - vk::ImageLayout::eTransferDstOptimal, region); + scheduler.CommandBuffer().copyImage(read_image.image, vk::ImageLayout::eTransferSrcOptimal, + write_image.image, vk::ImageLayout::eTransferDstOptimal, + region); + + ScopeMarkerEnd(); } void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { @@ -1195,42 +1212,43 @@ void Rasterizer::UpdateViewportScissorState() { cmdbuf.setScissorWithCountEXT(scissors); } -void Rasterizer::ScopeMarkerBegin(const std::string_view& str) { - if (Config::nullGpu() || !Config::vkMarkersEnabled()) { +void Rasterizer::ScopeMarkerBegin(const std::string_view& str, bool from_guest) { + if ((from_guest && !Config::vkGuestMarkersEnabled()) || + (!from_guest && !Config::vkHostMarkersEnabled())) { return; } - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ .pLabelName = str.data(), }); } -void Rasterizer::ScopeMarkerEnd() { - if (Config::nullGpu() || !Config::vkMarkersEnabled()) { +void Rasterizer::ScopeMarkerEnd(bool from_guest) { + if ((from_guest && !Config::vkGuestMarkersEnabled()) || + (!from_guest && !Config::vkHostMarkersEnabled())) { return; } - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.endDebugUtilsLabelEXT(); } -void Rasterizer::ScopedMarkerInsert(const std::string_view& str) { - if (Config::nullGpu() || !Config::vkMarkersEnabled()) { +void Rasterizer::ScopedMarkerInsert(const std::string_view& str, bool from_guest) { + if ((from_guest && !Config::vkGuestMarkersEnabled()) || + (!from_guest && !Config::vkHostMarkersEnabled())) { return; } - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ .pLabelName = str.data(), }); } -void Rasterizer::ScopedMarkerInsertColor(const std::string_view& str, const u32 color) { - if (Config::nullGpu() || !Config::vkMarkersEnabled()) { +void Rasterizer::ScopedMarkerInsertColor(const std::string_view& str, const u32 color, + bool from_guest) { + if ((from_guest && !Config::vkGuestMarkersEnabled()) || + (!from_guest && !Config::vkHostMarkersEnabled())) { return; } - const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ .pLabelName = str.data(), diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 1e4a210bb..abf58e522 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -47,10 +47,11 @@ public: void DispatchDirect(); void DispatchIndirect(VAddr address, u32 offset, u32 size); - void ScopeMarkerBegin(const std::string_view& str); - void ScopeMarkerEnd(); - void ScopedMarkerInsert(const std::string_view& str); - void ScopedMarkerInsertColor(const std::string_view& str, const u32 color); + void ScopeMarkerBegin(const std::string_view& str, bool from_guest = false); + void ScopeMarkerEnd(bool from_guest = false); + void ScopedMarkerInsert(const std::string_view& str, bool from_guest = false); + void ScopedMarkerInsertColor(const std::string_view& str, const u32 color, + bool from_guest = false); void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); u32 ReadDataFromGds(u32 gsd_offset); diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp index dba603e71..5eae32e70 100644 --- a/src/video_core/renderer_vulkan/vk_resource_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp @@ -73,9 +73,7 @@ CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semap ASSERT_MSG(pool_result == vk::Result::eSuccess, "Failed to create command pool: {}", vk::to_string(pool_result)); cmd_pool = std::move(pool); - if (instance.HasDebuggingToolAttached()) { - SetObjectName(device, *cmd_pool, "CommandPool"); - } + SetObjectName(device, *cmd_pool, "CommandPool"); } CommandPool::~CommandPool() = default; @@ -94,10 +92,8 @@ void CommandPool::Allocate(std::size_t begin, std::size_t end) { device.allocateCommandBuffers(&buffer_alloc_info, cmd_buffers.data() + begin); ASSERT(result == vk::Result::eSuccess); - if (instance.HasDebuggingToolAttached()) { - for (std::size_t i = begin; i < end; ++i) { - SetObjectName(device, cmd_buffers[i], "CommandPool: Command Buffer {}", i); - } + for (std::size_t i = begin; i < end; ++i) { + SetObjectName(device, cmd_buffers[i], "CommandPool: Command Buffer {}", i); } } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 44f4be6dd..8278252ad 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -4,6 +4,7 @@ #include #include #include "common/assert.h" +#include "common/config.h" #include "common/logging/log.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -235,11 +236,9 @@ void Swapchain::RefreshSemaphores() { semaphore = sem; } - if (instance.HasDebuggingToolAttached()) { - for (u32 i = 0; i < image_count; ++i) { - SetObjectName(device, image_acquired[i], "Swapchain Semaphore: image_acquired {}", i); - SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", i); - } + for (u32 i = 0; i < image_count; ++i) { + SetObjectName(device, image_acquired[i], "Swapchain Semaphore: image_acquired {}", i); + SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", i); } } @@ -251,10 +250,8 @@ void Swapchain::SetupImages() { images = std::move(imgs); image_count = static_cast(images.size()); - if (instance.HasDebuggingToolAttached()) { - for (u32 i = 0; i < image_count; ++i) { - SetObjectName(device, images[i], "Swapchain Image {}", i); - } + for (u32 i = 0; i < image_count; ++i) { + SetObjectName(device, images[i], "Swapchain Image {}", i); } } diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index e935c7d2e..6b1349386 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -110,6 +110,13 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}", vk::to_string(view_result)); image_view = std::move(view); + + const auto view_aspect = aspect & vk::ImageAspectFlagBits::eDepth ? "Depth" + : aspect & vk::ImageAspectFlagBits::eStencil ? "Stencil" + : "Color"; + Vulkan::SetObjectName(instance.GetDevice(), *image_view, "ImageView {}x{}x{} {:#x}:{:#x} ({})", + image.info.size.width, image.info.size.height, image.info.size.depth, + image.info.guest_address, image.info.guest_size, view_aspect); } ImageView::~ImageView() = default; From da2b58f66edd38b4e3cc851f6b26cc61f34736a9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 16 Jan 2025 02:36:41 -0800 Subject: [PATCH 02/65] resource_tracking_pass: Persist image resource atomic designation. (#2158) --- src/shader_recompiler/ir/passes/resource_tracking_pass.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 10d685ed1..a132cac2c 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -164,6 +164,7 @@ public: return desc.sharp_idx == existing.sharp_idx && desc.is_array == existing.is_array; })}; auto& image = image_resources[index]; + image.is_atomic |= desc.is_atomic; image.is_written |= desc.is_written; return index; } From 34a5f2319cb7e3e201239e25b3562ea0781760c9 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 16 Jan 2025 03:17:07 -0800 Subject: [PATCH 03/65] network: Remove firing Np callbacks from check stubs. (#2161) --- src/core/libraries/network/netctl.cpp | 4 ++-- src/core/libraries/np_manager/np_manager.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/libraries/network/netctl.cpp b/src/core/libraries/network/netctl.cpp index b167d2789..00d980663 100644 --- a/src/core/libraries/network/netctl.cpp +++ b/src/core/libraries/network/netctl.cpp @@ -93,7 +93,7 @@ int PS4_SYSV_ABI sceNetCtlUnregisterCallbackV6() { } int PS4_SYSV_ABI sceNetCtlCheckCallback() { - netctl.CheckCallback(); + LOG_DEBUG(Lib_NetCtl, "(STUBBED) called"); return ORBIS_OK; } @@ -373,7 +373,7 @@ int PS4_SYSV_ABI Func_D8DCB6973537A3DC() { } int PS4_SYSV_ABI sceNetCtlCheckCallbackForNpToolkit() { - netctl.CheckNpToolkitCallback(); + LOG_DEBUG(Lib_NetCtl, "(STUBBED) called"); return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index 3489e3e41..e26c5a830 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -2509,7 +2509,7 @@ struct NpStateCallbackForNpToolkit { NpStateCallbackForNpToolkit NpStateCbForNp; int PS4_SYSV_ABI sceNpCheckCallbackForLib() { - Core::ExecuteGuest(NpStateCbForNp.func, 1, OrbisNpState::SignedOut, NpStateCbForNp.userdata); + LOG_DEBUG(Lib_NpManager, "(STUBBED) called"); return ORBIS_OK; } From 4695aaa8306158e94df8292106bdc3889976dd1e Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 16 Jan 2025 18:27:52 +0200 Subject: [PATCH 04/65] sceKernelAio* implementation (#2160) * draft Aio from https://github.com/GoldHEN/GoldHEN_Plugins_Repository * cleanup and fixes to Aio --- CMakeLists.txt | 2 + src/core/libraries/kernel/aio.cpp | 339 ++++++++++++++++++++++++ src/core/libraries/kernel/aio.h | 43 +++ src/core/libraries/kernel/file_system.h | 3 +- src/core/libraries/kernel/kernel.cpp | 2 + src/core/libraries/kernel/time.h | 1 + 6 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 src/core/libraries/kernel/aio.cpp create mode 100644 src/core/libraries/kernel/aio.h diff --git a/CMakeLists.txt b/CMakeLists.txt index be87de119..30cb033ed 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,6 +250,8 @@ set(KERNEL_LIB src/core/libraries/kernel/sync/mutex.cpp src/core/libraries/kernel/time.h src/core/libraries/kernel/orbis_error.h src/core/libraries/kernel/posix_error.h + src/core/libraries/kernel/aio.cpp + src/core/libraries/kernel/aio.h ) set(NETWORK_LIBS src/core/libraries/network/http.cpp diff --git a/src/core/libraries/kernel/aio.cpp b/src/core/libraries/kernel/aio.cpp new file mode 100644 index 000000000..e017010cb --- /dev/null +++ b/src/core/libraries/kernel/aio.cpp @@ -0,0 +1,339 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "aio.h" +#include "common/assert.h" +#include "common/debug.h" +#include "common/logging/log.h" +#include "core/libraries/kernel/equeue.h" +#include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/libs.h" +#include "file_system.h" + +namespace Libraries::Kernel { + +#define MAX_QUEUE 512 + +static s32* id_state; +static s32 id_index; + +s32 sceKernelAioInitializeImpl(void* p, s32 size) { + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioDeleteRequest(OrbisKernelAioSubmitId id, s32* ret) { + if (ret == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + id_state[id] = ORBIS_KERNEL_AIO_STATE_ABORTED; + *ret = 0; + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioDeleteRequests(OrbisKernelAioSubmitId id[], s32 num, s32 ret[]) { + if (ret == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < num; i++) { + id_state[id[i]] = ORBIS_KERNEL_AIO_STATE_ABORTED; + ret[i] = 0; + } + + return 0; +} +s32 PS4_SYSV_ABI sceKernelAioPollRequest(OrbisKernelAioSubmitId id, s32* state) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + *state = id_state[id]; + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioPollRequests(OrbisKernelAioSubmitId id[], s32 num, s32 state[]) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < num; i++) { + state[i] = id_state[id[i]]; + } + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioCancelRequest(OrbisKernelAioSubmitId id, s32* state) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (id) { + id_state[id] = ORBIS_KERNEL_AIO_STATE_ABORTED; + *state = ORBIS_KERNEL_AIO_STATE_ABORTED; + } else { + *state = ORBIS_KERNEL_AIO_STATE_PROCESSING; + } + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioCancelRequests(OrbisKernelAioSubmitId id[], s32 num, s32 state[]) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < num; i++) { + if (id[i]) { + id_state[id[i]] = ORBIS_KERNEL_AIO_STATE_ABORTED; + state[i] = ORBIS_KERNEL_AIO_STATE_ABORTED; + } else { + state[i] = ORBIS_KERNEL_AIO_STATE_PROCESSING; + } + } + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioWaitRequest(OrbisKernelAioSubmitId id, s32* state, u32* usec) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + u32 timer = 0; + + s32 timeout = 0; + + while (id_state[id] == ORBIS_KERNEL_AIO_STATE_PROCESSING) { + sceKernelUsleep(10); + + timer += 10; + if (*usec) { + if (timer > *usec) { + timeout = 1; + break; + } + } + } + + *state = id_state[id]; + + if (timeout) + return ORBIS_KERNEL_ERROR_ETIMEDOUT; + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioWaitRequests(OrbisKernelAioSubmitId id[], s32 num, s32 state[], + u32 mode, u32* usec) { + if (state == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + u32 timer = 0; + s32 timeout = 0; + s32 completion = 0; + + for (s32 i = 0; i < num; i++) { + if (!completion && !timeout) { + while (id_state[id[i]] == ORBIS_KERNEL_AIO_STATE_PROCESSING) { + sceKernelUsleep(10); + timer += 10; + + if (*usec) { + if (timer > *usec) { + timeout = 1; + break; + } + } + } + } + + if (mode == 0x02) { + if (id_state[id[i]] == ORBIS_KERNEL_AIO_STATE_COMPLETED) + completion = 1; + } + + state[i] = id_state[id[i]]; + } + + if (timeout) + return ORBIS_KERNEL_ERROR_ETIMEDOUT; + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioSubmitReadCommands(OrbisKernelAioRWRequest req[], s32 size, s32 prio, + OrbisKernelAioSubmitId* id) { + if (req == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (id == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_PROCESSING; + + for (s32 i = 0; i < size; i++) { + + s64 ret = sceKernelPread(req[i].fd, req[i].buf, req[i].nbyte, req[i].offset); + + if (ret < 0) { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_ABORTED; + req[i].result->returnValue = ret; + + } else { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_COMPLETED; + req[i].result->returnValue = ret; + } + } + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_COMPLETED; + + *id = id_index; + + id_index = (id_index + 1) % MAX_QUEUE; + + if (!id_index) + id_index++; + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioSubmitReadCommandsMultiple(OrbisKernelAioRWRequest req[], s32 size, + s32 prio, OrbisKernelAioSubmitId id[]) { + if (req == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (id == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < size; i++) { + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_PROCESSING; + + s64 ret = sceKernelPread(req[i].fd, req[i].buf, req[i].nbyte, req[i].offset); + + if (ret < 0) { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_ABORTED; + req[i].result->returnValue = ret; + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_ABORTED; + + } else { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_COMPLETED; + req[i].result->returnValue = ret; + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_COMPLETED; + } + + id[i] = id_index; + + id_index = (id_index + 1) % MAX_QUEUE; + + if (!id_index) + id_index++; + } + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioSubmitWriteCommands(OrbisKernelAioRWRequest req[], s32 size, s32 prio, + OrbisKernelAioSubmitId* id) { + if (req == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (id == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < size; i++) { + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_PROCESSING; + + s64 ret = sceKernelPwrite(req[i].fd, req[i].buf, req[i].nbyte, req[i].offset); + + if (ret < 0) { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_ABORTED; + req[i].result->returnValue = ret; + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_ABORTED; + + } else { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_COMPLETED; + req[i].result->returnValue = ret; + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_COMPLETED; + } + } + + *id = id_index; + + id_index = (id_index + 1) % MAX_QUEUE; + + // skip id_index equals 0 , because sceKernelAioCancelRequest will submit id + // equal to 0 + if (!id_index) + id_index++; + + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioSubmitWriteCommandsMultiple(OrbisKernelAioRWRequest req[], s32 size, + s32 prio, OrbisKernelAioSubmitId id[]) { + if (req == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + if (id == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + for (s32 i = 0; i < size; i++) { + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_PROCESSING; + s64 ret = sceKernelPwrite(req[i].fd, req[i].buf, req[i].nbyte, req[i].offset); + + if (ret < 0) { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_ABORTED; + req[i].result->returnValue = ret; + + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_ABORTED; + + } else { + req[i].result->state = ORBIS_KERNEL_AIO_STATE_COMPLETED; + req[i].result->returnValue = ret; + id_state[id_index] = ORBIS_KERNEL_AIO_STATE_COMPLETED; + } + + id[i] = id_index; + id_index = (id_index + 1) % MAX_QUEUE; + + if (!id_index) + id_index++; + } + return 0; +} + +s32 PS4_SYSV_ABI sceKernelAioSetParam() { + LOG_ERROR(Kernel, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelAioInitializeParam() { + LOG_ERROR(Kernel, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterAio(Core::Loader::SymbolsResolver* sym) { + id_index = 1; + id_state = (int*)malloc(sizeof(int) * MAX_QUEUE); + memset(id_state, 0, sizeof(sizeof(int) * MAX_QUEUE)); + + LIB_FUNCTION("fR521KIGgb8", "libkernel", 1, "libkernel", 1, 1, sceKernelAioCancelRequest); + LIB_FUNCTION("3Lca1XBrQdY", "libkernel", 1, "libkernel", 1, 1, sceKernelAioCancelRequests); + LIB_FUNCTION("5TgME6AYty4", "libkernel", 1, "libkernel", 1, 1, sceKernelAioDeleteRequest); + LIB_FUNCTION("Ft3EtsZzAoY", "libkernel", 1, "libkernel", 1, 1, sceKernelAioDeleteRequests); + LIB_FUNCTION("vYU8P9Td2Zo", "libkernel", 1, "libkernel", 1, 1, sceKernelAioInitializeImpl); + LIB_FUNCTION("nu4a0-arQis", "libkernel", 1, "libkernel", 1, 1, sceKernelAioInitializeParam); + LIB_FUNCTION("2pOuoWoCxdk", "libkernel", 1, "libkernel", 1, 1, sceKernelAioPollRequest); + LIB_FUNCTION("o7O4z3jwKzo", "libkernel", 1, "libkernel", 1, 1, sceKernelAioPollRequests); + LIB_FUNCTION("9WK-vhNXimw", "libkernel", 1, "libkernel", 1, 1, sceKernelAioSetParam); + LIB_FUNCTION("HgX7+AORI58", "libkernel", 1, "libkernel", 1, 1, sceKernelAioSubmitReadCommands); + LIB_FUNCTION("lXT0m3P-vs4", "libkernel", 1, "libkernel", 1, 1, + sceKernelAioSubmitReadCommandsMultiple); + LIB_FUNCTION("XQ8C8y+de+E", "libkernel", 1, "libkernel", 1, 1, sceKernelAioSubmitWriteCommands); + LIB_FUNCTION("xT3Cpz0yh6Y", "libkernel", 1, "libkernel", 1, 1, + sceKernelAioSubmitWriteCommandsMultiple); + LIB_FUNCTION("KOF-oJbQVvc", "libkernel", 1, "libkernel", 1, 1, sceKernelAioWaitRequest); + LIB_FUNCTION("lgK+oIWkJyA", "libkernel", 1, "libkernel", 1, 1, sceKernelAioWaitRequests); +} + +} // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/aio.h b/src/core/libraries/kernel/aio.h new file mode 100644 index 000000000..0ad21e938 --- /dev/null +++ b/src/core/libraries/kernel/aio.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Kernel { + +enum AioState { + ORBIS_KERNEL_AIO_STATE_SUBMITTED = 1, + ORBIS_KERNEL_AIO_STATE_PROCESSING = 2, + ORBIS_KERNEL_AIO_STATE_COMPLETED = 3, + ORBIS_KERNEL_AIO_STATE_ABORTED = 4 +}; + +struct OrbisKernelAioResult { + s64 returnValue; + u32 state; +}; + +typedef s32 OrbisKernelAioSubmitId; + +struct OrbisKernelAioRWRequest { + s64 offset; + s64 nbyte; + void* buf; + OrbisKernelAioResult* result; + s32 fd; +}; + +void RegisterAio(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Kernel \ No newline at end of file diff --git a/src/core/libraries/kernel/file_system.h b/src/core/libraries/kernel/file_system.h index 6443962ff..1838df2fe 100644 --- a/src/core/libraries/kernel/file_system.h +++ b/src/core/libraries/kernel/file_system.h @@ -67,7 +67,8 @@ constexpr int ORBIS_KERNEL_O_DIRECTORY = 0x00020000; s64 PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes); s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes); - +s64 PS4_SYSV_ABI sceKernelPread(int d, void* buf, size_t nbytes, s64 offset); +s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset); void RegisterFileSystem(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index b05c96fad..a9d04ca38 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -28,6 +28,7 @@ #include #endif #include +#include "aio.h" namespace Libraries::Kernel { @@ -218,6 +219,7 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { Libraries::Kernel::RegisterEventQueue(sym); Libraries::Kernel::RegisterProcess(sym); Libraries::Kernel::RegisterException(sym); + Libraries::Kernel::RegisterAio(sym); LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard); LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", 1, 1, kernel_ioctl); diff --git a/src/core/libraries/kernel/time.h b/src/core/libraries/kernel/time.h index 6aa281aaf..407b6f9ed 100644 --- a/src/core/libraries/kernel/time.h +++ b/src/core/libraries/kernel/time.h @@ -82,6 +82,7 @@ int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, OrbisTimesec* st, u64* dst_sec); +int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); void RegisterTime(Core::Loader::SymbolsResolver* sym); From 440a693fae49468ea1b46d4afb548e5697f946de Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:22:39 -0600 Subject: [PATCH 05/65] Crash on sceKernelDebugRaiseExceptionOnReleaseMode (#2163) --- src/core/libraries/kernel/threads/exception.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/libraries/kernel/threads/exception.cpp b/src/core/libraries/kernel/threads/exception.cpp index cc391e928..5e2f35d69 100644 --- a/src/core/libraries/kernel/threads/exception.cpp +++ b/src/core/libraries/kernel/threads/exception.cpp @@ -153,6 +153,11 @@ int PS4_SYSV_ABI sceKernelDebugRaiseException() { return 0; } +int PS4_SYSV_ABI sceKernelDebugRaiseExceptionOnReleaseMode() { + UNREACHABLE(); + return 0; +} + void RegisterException(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("il03nluKfMk", "libkernel_unity", 1, "libkernel", 1, 1, sceKernelRaiseException); LIB_FUNCTION("WkwEd3N7w0Y", "libkernel_unity", 1, "libkernel", 1, 1, @@ -160,6 +165,8 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("Qhv5ARAoOEc", "libkernel_unity", 1, "libkernel", 1, 1, sceKernelRemoveExceptionHandler) LIB_FUNCTION("OMDRKKAZ8I4", "libkernel", 1, "libkernel", 1, 1, sceKernelDebugRaiseException); + LIB_FUNCTION("zE-wXIZjLoM", "libkernel", 1, "libkernel", 1, 1, + sceKernelDebugRaiseExceptionOnReleaseMode); } } // namespace Libraries::Kernel From 56a6c95730a89aec7056a12603ae63a01a8b4a8f Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 16 Jan 2025 16:27:23 -0300 Subject: [PATCH 06/65] Render without rendering (#2152) * presenter: render the game inside a ImGui window * presenter: render the previous frame to keep the render rendering * swapchain: fix swapchain image view format not being converted to unorm * devtools: fix frame graph timing --- src/core/debug_state.h | 9 + src/core/devtools/layer.cpp | 15 +- src/core/devtools/widget/frame_graph.cpp | 6 +- src/core/devtools/widget/frame_graph.h | 3 + src/core/libraries/videoout/driver.cpp | 20 ++- src/core/libraries/videoout/driver.h | 3 +- src/imgui/imgui_config.h | 6 + src/imgui/renderer/imgui_core.cpp | 22 ++- src/imgui/renderer/imgui_core.h | 7 +- src/imgui/renderer/imgui_impl_sdl3.cpp | 23 ++- src/imgui/renderer/imgui_impl_sdl3.h | 2 +- src/imgui/renderer/imgui_impl_vulkan.cpp | 48 +++--- src/imgui/renderer/imgui_impl_vulkan.h | 18 +- src/imgui/renderer/texture_manager.cpp | 5 +- .../renderer_vulkan/vk_instance.cpp | 1 + .../renderer_vulkan/vk_presenter.cpp | 161 +++++++++++++----- src/video_core/renderer_vulkan/vk_presenter.h | 7 +- .../renderer_vulkan/vk_swapchain.cpp | 42 ++++- src/video_core/renderer_vulkan/vk_swapchain.h | 18 +- 19 files changed, 306 insertions(+), 110 deletions(-) diff --git a/src/core/debug_state.h b/src/core/debug_state.h index 6a8e15baa..aab741fd0 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -131,6 +131,8 @@ class DebugStateImpl { friend class Core::Devtools::Widget::FrameGraph; friend class Core::Devtools::Widget::ShaderList; + bool showing_debug_menu_bar = false; + std::queue debug_message_popup; std::mutex guest_threads_mutex{}; @@ -153,6 +155,9 @@ class DebugStateImpl { std::vector shader_dump_list{}; public: + float Framerate = 1.0f / 60.0f; + float FrameDeltaTime; + void ShowDebugMessage(std::string message) { if (message.empty()) { return; @@ -160,6 +165,10 @@ public: debug_message_popup.push(std::move(message)); } + bool& ShowingDebugMenuBar() { + return showing_debug_menu_bar; + } + void AddCurrentThreadToGuestList(); void RemoveCurrentThreadFromGuestList(); diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 776f3377d..11990a56f 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -28,7 +28,6 @@ static bool show_simple_fps = false; static bool visibility_toggled = false; static float fps_scale = 1.0f; -static bool show_advanced_debug = false; static int dump_frame_count = 1; static Widget::FrameGraph frame_graph; @@ -253,8 +252,8 @@ void L::DrawAdvanced() { } void L::DrawSimple() { - const auto io = GetIO(); - Text("%.1f FPS (%.2f ms)", io.Framerate, 1000.0f / io.Framerate); + const float frameRate = DebugState.Framerate; + Text("%d FPS (%.1f ms)", static_cast(std::round(1.0f / frameRate)), frameRate * 1000.0f); } static void LoadSettings(const char* line) { @@ -265,7 +264,7 @@ static void LoadSettings(const char* line) { return; } if (sscanf(line, "show_advanced_debug=%d", &i) == 1) { - show_advanced_debug = i != 0; + DebugState.ShowingDebugMenuBar() = i != 0; return; } if (sscanf(line, "show_frame_graph=%d", &i) == 1) { @@ -310,7 +309,7 @@ void L::SetupSettings() { handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { buf->appendf("[%s][Data]\n", handler->TypeName); buf->appendf("fps_scale=%f\n", fps_scale); - buf->appendf("show_advanced_debug=%d\n", show_advanced_debug); + buf->appendf("show_advanced_debug=%d\n", DebugState.ShowingDebugMenuBar()); buf->appendf("show_frame_graph=%d\n", frame_graph.is_open); buf->appendf("dump_frame_count=%d\n", dump_frame_count); buf->append("\n"); @@ -336,12 +335,12 @@ void L::Draw() { if (!DebugState.IsGuestThreadsPaused()) { const auto fn = DebugState.flip_frame_count.load(); - frame_graph.AddFrame(fn, io.DeltaTime); + frame_graph.AddFrame(fn, DebugState.FrameDeltaTime); } if (IsKeyPressed(ImGuiKey_F10, false)) { if (io.KeyCtrl) { - show_advanced_debug = !show_advanced_debug; + DebugState.ShowingDebugMenuBar() ^= true; } else { show_simple_fps = !show_simple_fps; } @@ -376,7 +375,7 @@ void L::Draw() { End(); } - if (show_advanced_debug) { + if (DebugState.ShowingDebugMenuBar()) { PushFont(io.Fonts->Fonts[IMGUI_FONT_MONO]); PushID("DevtoolsLayer"); DrawAdvanced(); diff --git a/src/core/devtools/widget/frame_graph.cpp b/src/core/devtools/widget/frame_graph.cpp index 0e170db38..d93de571a 100644 --- a/src/core/devtools/widget/frame_graph.cpp +++ b/src/core/devtools/widget/frame_graph.cpp @@ -83,15 +83,13 @@ void FrameGraph::Draw() { auto isSystemPaused = DebugState.IsGuestThreadsPaused(); - static float deltaTime; - static float frameRate; - if (!isSystemPaused) { - deltaTime = io.DeltaTime * 1000.0f; + deltaTime = DebugState.FrameDeltaTime * 1000.0f; frameRate = 1000.0f / deltaTime; } Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate); + Text("Presenter time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, 1.0f / io.DeltaTime); Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(), DebugState.gnm_frame_count.load()); diff --git a/src/core/devtools/widget/frame_graph.h b/src/core/devtools/widget/frame_graph.h index 40a68ffa7..aef3c0747 100644 --- a/src/core/devtools/widget/frame_graph.h +++ b/src/core/devtools/widget/frame_graph.h @@ -16,6 +16,9 @@ class FrameGraph { std::array frame_list{}; + float deltaTime{}; + float frameRate{}; + void DrawFrameGraph(); public: diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index f6c25afe3..29948f242 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include - #include "common/assert.h" #include "common/config.h" #include "common/debug.h" @@ -207,6 +205,13 @@ void VideoOutDriver::DrawBlankFrame() { presenter->Present(empty_frame); } +void VideoOutDriver::DrawLastFrame() { + const auto frame = presenter->PrepareLastFrame(); + if (frame != nullptr) { + presenter->Present(frame, true); + } +} + bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop /*= false*/) { { @@ -278,17 +283,24 @@ void VideoOutDriver::PresentThread(std::stop_token token) { return {}; }; - auto delay = std::chrono::microseconds{0}; while (!token.stop_requested()) { timer.Start(); + if (DebugState.IsGuestThreadsPaused()) { + DrawLastFrame(); + timer.End(); + continue; + } + // Check if it's time to take a request. auto& vblank_status = main_port.vblank_status; if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); if (!request) { - if (!main_port.is_open || DebugState.IsGuestThreadsPaused()) { + if (!main_port.is_open) { DrawBlankFrame(); + } else { + DrawLastFrame(); } } else { Flip(request); diff --git a/src/core/libraries/videoout/driver.h b/src/core/libraries/videoout/driver.h index ec01b621f..ad7c7bec2 100644 --- a/src/core/libraries/videoout/driver.h +++ b/src/core/libraries/videoout/driver.h @@ -102,7 +102,8 @@ private: }; void Flip(const Request& req); - void DrawBlankFrame(); // Used when there is no flip request to keep ImGui up to date + void DrawBlankFrame(); // Video port out not open + void DrawLastFrame(); // Used when there is no flip request void SubmitFlipInternal(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false); void PresentThread(std::stop_token token); diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index ccb084d94..7b03a4bab 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -30,6 +30,12 @@ extern void assert_fail_debug_msg(const char* msg); #define IM_VEC4_CLASS_EXTRA \ constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} +namespace ImGui { +struct Texture; +} +#define ImTextureID ImTextureID +using ImTextureID = ::ImGui::Texture*; + #ifdef IMGUI_USE_WCHAR32 #error "This project uses 16 bits wchar standard like Orbis" #endif \ No newline at end of file diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index 335185473..b63f50340 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -6,6 +6,7 @@ #include "common/config.h" #include "common/path_util.h" +#include "core/debug_state.h" #include "core/devtools/layer.h" #include "imgui/imgui_layer.h" #include "imgui_core.h" @@ -167,7 +168,7 @@ bool ProcessEvent(SDL_Event* event) { } } -void NewFrame() { +ImGuiID NewFrame(bool is_reusing_frame) { { std::scoped_lock lock{change_layers_mutex}; while (!change_layers.empty()) { @@ -182,17 +183,24 @@ void NewFrame() { } } - Sdl::NewFrame(); + Sdl::NewFrame(is_reusing_frame); ImGui::NewFrame(); - DockSpaceOverViewport(0, GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode); + ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode; + if (!DebugState.ShowingDebugMenuBar()) { + flags |= ImGuiDockNodeFlags_NoTabBar; + } + ImGuiID dockId = DockSpaceOverViewport(0, GetMainViewport(), flags); for (auto* layer : layers) { layer->Draw(); } + + return dockId; } -void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { +void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view, + const vk::Extent2D& extent) { ImGui::Render(); ImDrawData* draw_data = GetDrawData(); if (draw_data->CmdListsCount == 0) { @@ -207,16 +215,16 @@ void Render(const vk::CommandBuffer& cmdbuf, ::Vulkan::Frame* frame) { vk::RenderingAttachmentInfo color_attachments[1]{ { - .imageView = frame->image_view, + .imageView = image_view, .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eLoad, + .loadOp = vk::AttachmentLoadOp::eClear, .storeOp = vk::AttachmentStoreOp::eStore, }, }; vk::RenderingInfo render_info{}; render_info.renderArea = vk::Rect2D{ .offset = {0, 0}, - .extent = {frame->width, frame->height}, + .extent = extent, }; render_info.layerCount = 1; render_info.colorAttachmentCount = 1; diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h index 9ad708f81..7d5279bd6 100644 --- a/src/imgui/renderer/imgui_core.h +++ b/src/imgui/renderer/imgui_core.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "video_core/renderer_vulkan/vk_instance.h" #include "vulkan/vulkan_handles.hpp" @@ -24,8 +26,9 @@ void Shutdown(const vk::Device& device); bool ProcessEvent(SDL_Event* event); -void NewFrame(); +ImGuiID NewFrame(bool is_reusing_frame = false); -void Render(const vk::CommandBuffer& cmdbuf, Vulkan::Frame* frame); +void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view, + const vk::Extent2D& extent); } // namespace ImGui::Core diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index e67bdc775..ddd532cd0 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -5,6 +5,7 @@ #include #include "common/config.h" +#include "core/debug_state.h" #include "imgui_impl_sdl3.h" // SDL @@ -26,6 +27,7 @@ struct SdlData { SDL_Window* window{}; SDL_WindowID window_id{}; Uint64 time{}; + Uint64 nonReusedtime{}; const char* clipboard_text_data{}; // IME handling @@ -785,7 +787,7 @@ static void UpdateGamepads() { +thumb_dead_zone, +32767); } -void NewFrame() { +void NewFrame(bool is_reusing_frame) { SdlData* bd = GetBackendData(); IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?"); ImGuiIO& io = ImGui::GetIO(); @@ -798,9 +800,26 @@ void NewFrame() { if (current_time <= bd->time) current_time = bd->time + 1; io.DeltaTime = bd->time > 0 ? (float)((double)(current_time - bd->time) / (double)frequency) - : (float)(1.0f / 60.0f); + : 1.0f / 60.0f; bd->time = current_time; + if (!is_reusing_frame) { + if (current_time <= bd->nonReusedtime) + current_time = bd->nonReusedtime + 1; + float deltaTime = + bd->nonReusedtime > 0 + ? (float)((double)(current_time - bd->nonReusedtime) / (double)frequency) + : 1.0f / 60.0f; + bd->nonReusedtime = current_time; + DebugState.FrameDeltaTime = deltaTime; + float distribution = 0.016f / deltaTime / 10.0f; + if (distribution > 1.0f) { + distribution = 1.0f; + } + DebugState.Framerate = + deltaTime * distribution + DebugState.Framerate * (1.0f - distribution); + } + if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && bd->mouse_buttons_down == 0) { bd->mouse_window_id = 0; diff --git a/src/imgui/renderer/imgui_impl_sdl3.h b/src/imgui/renderer/imgui_impl_sdl3.h index 59b1a6856..fe626a962 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.h +++ b/src/imgui/renderer/imgui_impl_sdl3.h @@ -14,7 +14,7 @@ namespace ImGui::Sdl { bool Init(SDL_Window* window); void Shutdown(); -void NewFrame(); +void NewFrame(bool is_reusing); bool ProcessEvent(const SDL_Event* event); void OnResize(); diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp index 7f7ade2a5..bd98fa004 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.cpp +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -57,11 +57,12 @@ struct VkData { vk::DeviceMemory font_memory{}; vk::Image font_image{}; vk::ImageView font_view{}; - vk::DescriptorSet font_descriptor_set{}; + ImTextureID font_texture{}; vk::CommandBuffer font_command_buffer{}; // Render buffers WindowRenderBuffers render_buffers{}; + bool enabled_blending{true}; VkData(const InitInfo init_info) : init_info(init_info) { render_buffers.count = init_info.image_count; @@ -252,8 +253,8 @@ void UploadTextureData::Destroy() { const InitInfo& v = bd->init_info; CheckVkErr(v.device.waitIdle()); - RemoveTexture(descriptor_set); - descriptor_set = VK_NULL_HANDLE; + RemoveTexture(im_texture); + im_texture = nullptr; v.device.destroyImageView(image_view, v.allocator); image_view = VK_NULL_HANDLE; @@ -264,8 +265,8 @@ void UploadTextureData::Destroy() { } // Register a texture -vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, - vk::Sampler sampler) { +ImTextureID AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler) { VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; @@ -303,7 +304,9 @@ vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_lay }; v.device.updateDescriptorSets({write_desc}, {}); } - return descriptor_set; + return new Texture{ + .descriptor_set = descriptor_set, + }; } UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, size_t size) { @@ -370,7 +373,7 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, } // Create descriptor set (ImTextureID) - info.descriptor_set = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); + info.im_texture = AddTexture(info.image_view, vk::ImageLayout::eShaderReadOnlyOptimal); // Create Upload Buffer { @@ -464,10 +467,12 @@ UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, return info; } -void RemoveTexture(vk::DescriptorSet descriptor_set) { +void RemoveTexture(ImTextureID texture) { + IM_ASSERT(texture != nullptr); VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; - v.device.freeDescriptorSets(bd->descriptor_pool, {descriptor_set}); + v.device.freeDescriptorSets(bd->descriptor_pool, {texture->descriptor_set}); + delete texture; } static void CreateOrResizeBuffer(RenderBuffer& rb, size_t new_size, vk::BufferUsageFlagBits usage) { @@ -679,15 +684,11 @@ void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, command_buffer.setScissor(0, 1, &scissor); // Bind DescriptorSet with font or user texture - vk::DescriptorSet desc_set[1]{(VkDescriptorSet)pcmd->TextureId}; - if (sizeof(ImTextureID) < sizeof(ImU64)) { - // We don't support texture switches if ImTextureID hasn't been redefined to be - // 64-bit. Do a flaky check that other textures haven't been used. - IM_ASSERT(pcmd->TextureId == (ImTextureID)bd->font_descriptor_set); - desc_set[0] = bd->font_descriptor_set; - } + vk::DescriptorSet desc_set[1]{pcmd->TextureId->descriptor_set}; command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, bd->pipeline_layout, 0, {desc_set}, {}); + command_buffer.setColorBlendEnableEXT( + 0, {pcmd->TextureId->disable_blend ? vk::False : vk::True}); // Draw command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, @@ -709,7 +710,7 @@ static bool CreateFontsTexture() { const InitInfo& v = bd->init_info; // Destroy existing texture (if any) - if (bd->font_view || bd->font_image || bd->font_memory || bd->font_descriptor_set) { + if (bd->font_view || bd->font_image || bd->font_memory || bd->font_texture) { CheckVkErr(v.queue.waitIdle()); DestroyFontsTexture(); } @@ -782,7 +783,7 @@ static bool CreateFontsTexture() { } // Create the Descriptor Set: - bd->font_descriptor_set = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); + bd->font_texture = AddTexture(bd->font_view, vk::ImageLayout::eShaderReadOnlyOptimal); // Create the Upload Buffer: vk::DeviceMemory upload_buffer_memory{}; @@ -874,7 +875,7 @@ static bool CreateFontsTexture() { } // Store our identifier - io.Fonts->SetTexID(bd->font_descriptor_set); + io.Fonts->SetTexID(bd->font_texture); // End command buffer vk::SubmitInfo end_info = {}; @@ -898,9 +899,9 @@ static void DestroyFontsTexture() { VkData* bd = GetBackendData(); const InitInfo& v = bd->init_info; - if (bd->font_descriptor_set) { - RemoveTexture(bd->font_descriptor_set); - bd->font_descriptor_set = VK_NULL_HANDLE; + if (bd->font_texture) { + RemoveTexture(bd->font_texture); + bd->font_texture = nullptr; io.Fonts->SetTexID(nullptr); } @@ -1057,9 +1058,10 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all .pAttachments = color_attachment, }; - vk::DynamicState dynamic_states[2]{ + vk::DynamicState dynamic_states[3]{ vk::DynamicState::eViewport, vk::DynamicState::eScissor, + vk::DynamicState::eColorBlendEnableEXT, }; vk::PipelineDynamicStateCreateInfo dynamic_state{ .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h index e325e2a8d..05f4489da 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.h +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -10,6 +10,13 @@ struct ImDrawData; +namespace ImGui { +struct Texture { + vk::DescriptorSet descriptor_set{nullptr}; + bool disable_blend{false}; +}; +} // namespace ImGui + namespace ImGui::Vulkan { struct InitInfo { @@ -34,29 +41,32 @@ struct InitInfo { struct UploadTextureData { vk::Image image; vk::ImageView image_view; - vk::DescriptorSet descriptor_set; vk::DeviceMemory image_memory; vk::CommandBuffer command_buffer; // Submit to the queue vk::Buffer upload_buffer; vk::DeviceMemory upload_buffer_memory; + ImTextureID im_texture; + void Upload(); void Destroy(); }; -vk::DescriptorSet AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, - vk::Sampler sampler = VK_NULL_HANDLE); +ImTextureID AddTexture(vk::ImageView image_view, vk::ImageLayout image_layout, + vk::Sampler sampler = VK_NULL_HANDLE); UploadTextureData UploadTexture(const void* data, vk::Format format, u32 width, u32 height, size_t size); -void RemoveTexture(vk::DescriptorSet descriptor_set); +void RemoveTexture(ImTextureID descriptor_set); bool Init(InitInfo info); void Shutdown(); void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, vk::Pipeline pipeline = VK_NULL_HANDLE); +void SetBlendEnabled(bool enabled); + } // namespace ImGui::Vulkan \ No newline at end of file diff --git a/src/imgui/renderer/texture_manager.cpp b/src/imgui/renderer/texture_manager.cpp index f13c995be..d7516a3a5 100644 --- a/src/imgui/renderer/texture_manager.cpp +++ b/src/imgui/renderer/texture_manager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "common/assert.h" #include "common/config.h" #include "common/io_file.h" @@ -123,7 +124,7 @@ static std::deque g_upload_list; namespace Core::TextureManager { Inner::~Inner() { - if (upload_data.descriptor_set != nullptr) { + if (upload_data.im_texture != nullptr) { std::unique_lock lk{g_upload_mtx}; g_upload_list.emplace_back(UploadJob{ .data = this->upload_data, @@ -239,7 +240,7 @@ void Submit() { } if (upload.core != nullptr) { upload.core->upload_data.Upload(); - upload.core->texture_id = upload.core->upload_data.descriptor_set; + upload.core->texture_id = upload.core->upload_data.im_texture; if (upload.core->count.fetch_sub(1) == 1) { delete upload.core; } diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index f5bcb54b6..323b30816 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -383,6 +383,7 @@ bool Instance::CreateDevice() { .extendedDynamicState = true, }, vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{ + .extendedDynamicState3ColorBlendEnable = true, .extendedDynamicState3ColorWriteMask = true, }, vk::PhysicalDeviceDepthClipControlFeaturesEXT{ diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 1679aa691..4d76eb8db 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -20,6 +20,9 @@ #include +#include +#include "imgui/renderer/imgui_impl_vulkan.h" + namespace Vulkan { bool CanBlitToSwapchain(const vk::PhysicalDevice physical_device, vk::Format format) { @@ -103,17 +106,6 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt dst_rect.offset.x, dst_rect.offset.y); } -static vk::Format FormatToUnorm(vk::Format fmt) { - switch (fmt) { - case vk::Format::eR8G8B8A8Srgb: - return vk::Format::eR8G8B8A8Unorm; - case vk::Format::eB8G8R8A8Srgb: - return vk::Format::eB8G8R8A8Unorm; - default: - UNREACHABLE(); - } -} - void Presenter::CreatePostProcessPipeline() { static const std::array pp_shaders{ HostShaders::FS_TRI_VERT, @@ -324,9 +316,6 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_ CreatePostProcessPipeline(); - // Setup ImGui - ImGui::Core::Initialize(instance, window, num_images, - FormatToUnorm(swapchain.GetSurfaceFormat().format)); ImGui::Layer::AddLayer(Common::Singleton::Instance()); } @@ -344,6 +333,9 @@ Presenter::~Presenter() { void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { const vk::Device device = instance.GetDevice(); + if (frame->imgui_texture) { + ImGui::Vulkan::RemoveTexture(frame->imgui_texture); + } if (frame->image_view) { device.destroyImageView(frame->image_view); } @@ -361,7 +353,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst | - vk::ImageUsageFlagBits::eTransferSrc, + vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eSampled, }; const VmaAllocationCreateInfo alloc_info = { @@ -403,6 +395,64 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { frame->image_view = view; frame->width = width; frame->height = height; + + frame->imgui_texture = ImGui::Vulkan::AddTexture(view, vk::ImageLayout::eShaderReadOnlyOptimal); + frame->imgui_texture->disable_blend = true; +} + +Frame* Presenter::PrepareLastFrame() { + if (last_submit_frame == nullptr) { + return nullptr; + } + + Frame* frame = last_submit_frame; + + while (true) { + vk::Result result = instance.GetDevice().waitForFences(frame->present_done, false, + std::numeric_limits::max()); + if (result == vk::Result::eSuccess) { + break; + } + if (result == vk::Result::eTimeout) { + continue; + } + ASSERT_MSG(result != vk::Result::eErrorDeviceLost, + "Device lost during waiting for a frame"); + } + + auto& scheduler = flip_scheduler; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + + const auto frame_subresources = vk::ImageSubresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }; + + const auto pre_barrier = + vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead, + .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, + .oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + .newLayout = vk::ImageLayout::eGeneral, + .image = frame->image, + .subresourceRange{frame_subresources}}; + + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .imageMemoryBarrierCount = 1, + .pImageMemoryBarriers = &pre_barrier, + }); + + // Flush frame creation commands. + frame->ready_semaphore = scheduler.GetMasterSemaphore()->Handle(); + frame->ready_tick = scheduler.CurrentTick(); + SubmitInfo info{}; + scheduler.Flush(info); + return frame; } bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { @@ -499,6 +549,14 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) // Request a free presentation frame. Frame* frame = GetRenderFrame(); + if (image_id != VideoCore::NULL_IMAGE_ID) { + const auto& image = texture_cache.GetImage(image_id); + const auto extent = image.info.size; + if (frame->width != extent.width || frame->height != extent.height) { + RecreateFrame(frame, extent.width, extent.height); + } + } + // EOP flips are triggered from GPU thread so use the drawing scheduler to record // commands. Otherwise we are dealing with a CPU flip which could have arrived // from any guest thread. Use a separate scheduler for that. @@ -515,8 +573,8 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) }; const auto pre_barrier = - vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferRead, + vk::ImageMemoryBarrier2{.srcStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, + .srcAccessMask = vk::AccessFlagBits2::eColorAttachmentRead, .dstStageMask = vk::PipelineStageFlagBits2::eColorAttachmentOutput, .dstAccessMask = vk::AccessFlagBits2::eColorAttachmentWrite, .oldLayout = vk::ImageLayout::eUndefined, @@ -627,17 +685,19 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) return frame; } -void Presenter::Present(Frame* frame) { +void Presenter::Present(Frame* frame, bool is_reusing_frame) { // Free the frame for reuse const auto free_frame = [&] { - std::scoped_lock fl{free_mutex}; - free_queue.push(frame); - free_cv.notify_one(); + if (!is_reusing_frame) { + last_submit_frame = frame; + std::scoped_lock fl{free_mutex}; + free_queue.push(frame); + free_cv.notify_one(); + } }; // Recreate the swapchain if the window was resized. - if (window.GetWidth() != swapchain.GetExtent().width || - window.GetHeight() != swapchain.GetExtent().height) { + if (window.GetWidth() != swapchain.GetWidth() || window.GetHeight() != swapchain.GetHeight()) { swapchain.Recreate(window.GetWidth(), window.GetHeight()); } @@ -656,15 +716,14 @@ void Presenter::Present(Frame* frame) { // the frame's present fence and future GetRenderFrame() call will hang waiting for this frame. instance.GetDevice().resetFences(frame->present_done); - ImGui::Core::NewFrame(); + ImGuiID dockId = ImGui::Core::NewFrame(is_reusing_frame); const vk::Image swapchain_image = swapchain.Image(); + const vk::ImageView swapchain_image_view = swapchain.ImageView(); auto& scheduler = present_scheduler; const auto cmdbuf = scheduler.CommandBuffer(); - ImGui::Core::Render(cmdbuf, frame); - { auto* profiler_ctx = instance.GetProfilerContext(); TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", @@ -674,9 +733,9 @@ void Presenter::Present(Frame* frame) { const std::array pre_barriers{ vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eNone, - .dstAccessMask = vk::AccessFlagBits::eTransferWrite, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, .oldLayout = vk::ImageLayout::eUndefined, - .newLayout = vk::ImageLayout::eTransferDstOptimal, + .newLayout = vk::ImageLayout::eColorAttachmentOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = swapchain_image, @@ -690,9 +749,9 @@ void Presenter::Present(Frame* frame) { }, vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, - .dstAccessMask = vk::AccessFlagBits::eTransferRead, + .dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead, .oldLayout = vk::ImageLayout::eGeneral, - .newLayout = vk::ImageLayout::eTransferSrcOptimal, + .newLayout = vk::ImageLayout::eShaderReadOnlyOptimal, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = frame->image, @@ -705,10 +764,11 @@ void Presenter::Present(Frame* frame) { }, }, }; + const vk::ImageMemoryBarrier post_barrier{ - .srcAccessMask = vk::AccessFlagBits::eTransferWrite, + .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite, .dstAccessMask = vk::AccessFlagBits::eMemoryRead, - .oldLayout = vk::ImageLayout::eTransferDstOptimal, + .oldLayout = vk::ImageLayout::eColorAttachmentOptimal, .newLayout = vk::ImageLayout::ePresentSrcKHR, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, @@ -723,14 +783,29 @@ void Presenter::Present(Frame* frame) { }; cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers); - cmdbuf.blitImage( - frame->image, vk::ImageLayout::eTransferSrcOptimal, swapchain_image, - vk::ImageLayout::eTransferDstOptimal, - MakeImageBlitStretch(frame->width, frame->height, extent.width, extent.height), - vk::Filter::eLinear); + { // Draw the game + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f}); + ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once); + ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav); + + ImVec2 contentArea = ImGui::GetContentRegionAvail(); + const vk::Rect2D imgRect = + FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y); + ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{ + (float)imgRect.offset.x, + (float)imgRect.offset.y, + }); + ImGui::Image(frame->imgui_texture, { + static_cast(imgRect.extent.width), + static_cast(imgRect.extent.height), + }); + ImGui::End(); + ImGui::PopStyleVar(); + } + ImGui::Core::Render(cmdbuf, swapchain_image_view, swapchain.GetExtent()); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, @@ -756,7 +831,9 @@ void Presenter::Present(Frame* frame) { } free_frame(); - DebugState.IncFlipFrameNum(); + if (!is_reusing_frame) { + DebugState.IncFlipFrameNum(); + } } Frame* Presenter::GetRenderFrame() { @@ -790,9 +867,9 @@ Frame* Presenter::GetRenderFrame() { } } - // If the window dimensions changed, recreate this frame - if (frame->width != window.GetWidth() || frame->height != window.GetHeight()) { - RecreateFrame(frame, window.GetWidth(), window.GetHeight()); + // Initialize default frame image + if (frame->width == 0 || frame->height == 0) { + RecreateFrame(frame, 1920, 1080); } return frame; diff --git a/src/video_core/renderer_vulkan/vk_presenter.h b/src/video_core/renderer_vulkan/vk_presenter.h index 4c29af0f0..63cb30834 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.h +++ b/src/video_core/renderer_vulkan/vk_presenter.h @@ -5,6 +5,7 @@ #include +#include "imgui/imgui_config.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_scheduler.h" @@ -30,6 +31,8 @@ struct Frame { vk::Fence present_done; vk::Semaphore ready_semaphore; u64 ready_tick; + + ImTextureID imgui_texture; }; enum SchedulerType { @@ -86,8 +89,9 @@ public: } bool ShowSplash(Frame* frame = nullptr); - void Present(Frame* frame); + void Present(Frame* frame, bool is_reusing_frame = false); void RecreateFrame(Frame* frame, u32 width, u32 height); + Frame* PrepareLastFrame(); void FlushDraw() { SubmitInfo info{}; @@ -121,6 +125,7 @@ private: vk::UniqueCommandPool command_pool; std::vector present_frames; std::queue free_queue; + Frame* last_submit_frame; std::mutex free_mutex; std::condition_variable free_cv; std::condition_variable_any frame_cv; diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 8278252ad..556cccd32 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -4,8 +4,8 @@ #include #include #include "common/assert.h" -#include "common/config.h" #include "common/logging/log.h" +#include "imgui/renderer/imgui_core.h" #include "sdl_window.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_swapchain.h" @@ -15,7 +15,9 @@ namespace Vulkan { Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& window) : instance{instance_}, surface{CreateSurface(instance.GetInstance(), window)} { FindPresentFormat(); - Create(window.GetWidth(), window.GetHeight(), surface); + + Create(window.GetWidth(), window.GetHeight()); + ImGui::Core::Initialize(instance, window, image_count, surface_format.format); } Swapchain::~Swapchain() { @@ -23,10 +25,9 @@ Swapchain::~Swapchain() { instance.GetInstance().destroySurfaceKHR(surface); } -void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { +void Swapchain::Create(u32 width_, u32 height_) { width = width_; height = height_; - surface = surface_; needs_recreation = false; Destroy(); @@ -86,7 +87,7 @@ void Swapchain::Create(u32 width_, u32 height_, vk::SurfaceKHR surface_) { void Swapchain::Recreate(u32 width_, u32 height_) { LOG_DEBUG(Render_Vulkan, "Recreate the swapchain: width={} height={}", width_, height_); - Create(width_, height_, surface); + Create(width_, height_); } bool Swapchain::AcquireNextImage() { @@ -209,10 +210,14 @@ void Swapchain::Destroy() { if (swapchain) { device.destroySwapchainKHR(swapchain); } - for (u32 i = 0; i < image_count; i++) { - device.destroySemaphore(image_acquired[i]); - device.destroySemaphore(present_ready[i]); + + for (const auto& sem : image_acquired) { + device.destroySemaphore(sem); } + for (const auto& sem : present_ready) { + device.destroySemaphore(sem); + } + image_acquired.clear(); present_ready.clear(); } @@ -249,9 +254,30 @@ void Swapchain::SetupImages() { vk::to_string(images_result)); images = std::move(imgs); image_count = static_cast(images.size()); + images_view.resize(image_count); + for (u32 i = 0; i < image_count; ++i) { + if (images_view[i]) { + device.destroyImageView(images_view[i]); + } + auto [im_view_result, im_view] = device.createImageView(vk::ImageViewCreateInfo{ + .image = images[i], + .viewType = vk::ImageViewType::e2D, + .format = FormatToUnorm(surface_format.format), + .subresourceRange = + { + .aspectMask = vk::ImageAspectFlagBits::eColor, + .levelCount = 1, + .layerCount = 1, + }, + }); + ASSERT_MSG(im_view_result == vk::Result::eSuccess, "Failed to create image view: {}", + vk::to_string(im_view_result)); + images_view[i] = im_view; + } for (u32 i = 0; i < image_count; ++i) { SetObjectName(device, images[i], "Swapchain Image {}", i); + SetObjectName(device, images_view[i], "Swapchain ImageView {}", i); } } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 19ae9b2d2..192271f32 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -17,13 +17,24 @@ namespace Vulkan { class Instance; class Scheduler; +inline vk::Format FormatToUnorm(vk::Format fmt) { + switch (fmt) { + case vk::Format::eR8G8B8A8Srgb: + return vk::Format::eR8G8B8A8Unorm; + case vk::Format::eB8G8R8A8Srgb: + return vk::Format::eB8G8R8A8Unorm; + default: + UNREACHABLE(); + } +} + class Swapchain { public: explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window); ~Swapchain(); /// Creates (or recreates) the swapchain with a given size. - void Create(u32 width, u32 height, vk::SurfaceKHR surface); + void Create(u32 width, u32 height); /// Recreates the swapchain with a given size and current surface. void Recreate(u32 width, u32 height); @@ -42,6 +53,10 @@ public: return images[image_index]; } + vk::ImageView ImageView() const { + return images_view[image_index]; + } + vk::SurfaceFormatKHR GetSurfaceFormat() const { return surface_format; } @@ -103,6 +118,7 @@ private: vk::SurfaceTransformFlagBitsKHR transform; vk::CompositeAlphaFlagBitsKHR composite_alpha; std::vector images; + std::vector images_view; std::vector image_acquired; std::vector present_ready; u32 width = 0; From 8695383d3597d5b625c9ac4d0ad7c5feebf02cac Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Thu, 16 Jan 2025 21:10:17 -0300 Subject: [PATCH 07/65] keep framerate stable even without vsync (#2165) --- src/common/thread.h | 4 ++++ src/core/debug_state.h | 2 +- src/core/devtools/layer.cpp | 10 +++++----- src/core/libraries/videoout/driver.cpp | 11 +++++++---- src/imgui/renderer/imgui_core.cpp | 6 +++++- src/imgui/renderer/imgui_core.h | 2 ++ src/imgui/renderer/imgui_impl_sdl3.cpp | 20 ++++++++++++++------ 7 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/common/thread.h b/src/common/thread.h index 175ba9445..92cc0c59d 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -37,6 +37,10 @@ public: void Start(); void End(); + + std::chrono::nanoseconds GetTotalWait() const { + return total_wait; + } }; } // namespace Common diff --git a/src/core/debug_state.h b/src/core/debug_state.h index aab741fd0..a9e6f48b7 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -165,7 +165,7 @@ public: debug_message_popup.push(std::move(message)); } - bool& ShowingDebugMenuBar() { + bool& IsShowingDebugMenuBar() { return showing_debug_menu_bar; } diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index 11990a56f..c652849e7 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -253,7 +253,7 @@ void L::DrawAdvanced() { void L::DrawSimple() { const float frameRate = DebugState.Framerate; - Text("%d FPS (%.1f ms)", static_cast(std::round(1.0f / frameRate)), frameRate * 1000.0f); + Text("%d FPS (%.1f ms)", static_cast(std::round(frameRate)), 1000.0f / frameRate); } static void LoadSettings(const char* line) { @@ -264,7 +264,7 @@ static void LoadSettings(const char* line) { return; } if (sscanf(line, "show_advanced_debug=%d", &i) == 1) { - DebugState.ShowingDebugMenuBar() = i != 0; + DebugState.IsShowingDebugMenuBar() = i != 0; return; } if (sscanf(line, "show_frame_graph=%d", &i) == 1) { @@ -309,7 +309,7 @@ void L::SetupSettings() { handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) { buf->appendf("[%s][Data]\n", handler->TypeName); buf->appendf("fps_scale=%f\n", fps_scale); - buf->appendf("show_advanced_debug=%d\n", DebugState.ShowingDebugMenuBar()); + buf->appendf("show_advanced_debug=%d\n", DebugState.IsShowingDebugMenuBar()); buf->appendf("show_frame_graph=%d\n", frame_graph.is_open); buf->appendf("dump_frame_count=%d\n", dump_frame_count); buf->append("\n"); @@ -340,7 +340,7 @@ void L::Draw() { if (IsKeyPressed(ImGuiKey_F10, false)) { if (io.KeyCtrl) { - DebugState.ShowingDebugMenuBar() ^= true; + DebugState.IsShowingDebugMenuBar() ^= true; } else { show_simple_fps = !show_simple_fps; } @@ -375,7 +375,7 @@ void L::Draw() { End(); } - if (DebugState.ShowingDebugMenuBar()) { + if (DebugState.IsShowingDebugMenuBar()) { PushFont(io.Fonts->Fonts[IMGUI_FONT_MONO]); PushID("DevtoolsLayer"); DrawAdvanced(); diff --git a/src/core/libraries/videoout/driver.cpp b/src/core/libraries/videoout/driver.cpp index 29948f242..de5421fd7 100644 --- a/src/core/libraries/videoout/driver.cpp +++ b/src/core/libraries/videoout/driver.cpp @@ -9,6 +9,7 @@ #include "core/libraries/kernel/time.h" #include "core/libraries/videoout/driver.h" #include "core/libraries/videoout/videoout_error.h" +#include "imgui/renderer/imgui_core.h" #include "video_core/renderer_vulkan/vk_presenter.h" extern std::unique_ptr presenter; @@ -297,10 +298,12 @@ void VideoOutDriver::PresentThread(std::stop_token token) { if (vblank_status.count % (main_port.flip_rate + 1) == 0) { const auto request = receive_request(); if (!request) { - if (!main_port.is_open) { - DrawBlankFrame(); - } else { - DrawLastFrame(); + if (timer.GetTotalWait().count() < 0) { // Dont draw too fast + if (!main_port.is_open) { + DrawBlankFrame(); + } else if (ImGui::Core::MustKeepDrawing()) { + DrawLastFrame(); + } } } else { Flip(request); diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index b63f50340..f3e4609ba 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -187,7 +187,7 @@ ImGuiID NewFrame(bool is_reusing_frame) { ImGui::NewFrame(); ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode; - if (!DebugState.ShowingDebugMenuBar()) { + if (!DebugState.IsShowingDebugMenuBar()) { flags |= ImGuiDockNodeFlags_NoTabBar; } ImGuiID dockId = DockSpaceOverViewport(0, GetMainViewport(), flags); @@ -237,6 +237,10 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view, } } +bool MustKeepDrawing() { + return layers.size() > 1 || DebugState.IsShowingDebugMenuBar(); +} + } // namespace Core void Layer::AddLayer(Layer* layer) { diff --git a/src/imgui/renderer/imgui_core.h b/src/imgui/renderer/imgui_core.h index 7d5279bd6..ffee62cf8 100644 --- a/src/imgui/renderer/imgui_core.h +++ b/src/imgui/renderer/imgui_core.h @@ -31,4 +31,6 @@ ImGuiID NewFrame(bool is_reusing_frame = false); void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view, const vk::Extent2D& extent); +bool MustKeepDrawing(); // Force the emulator redraw + } // namespace ImGui::Core diff --git a/src/imgui/renderer/imgui_impl_sdl3.cpp b/src/imgui/renderer/imgui_impl_sdl3.cpp index ddd532cd0..ccd31d03a 100644 --- a/src/imgui/renderer/imgui_impl_sdl3.cpp +++ b/src/imgui/renderer/imgui_impl_sdl3.cpp @@ -46,6 +46,11 @@ struct SdlData { ImVector gamepads{}; GamepadMode gamepad_mode{}; bool want_update_gamepads_list{}; + + // Framerate counting (based on ImGui impl) + std::array framerateSecPerFrame; + int framerateSecPerFrameIdx{}; + float framerateSecPerFrameAcc{}; }; // Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui @@ -812,12 +817,15 @@ void NewFrame(bool is_reusing_frame) { : 1.0f / 60.0f; bd->nonReusedtime = current_time; DebugState.FrameDeltaTime = deltaTime; - float distribution = 0.016f / deltaTime / 10.0f; - if (distribution > 1.0f) { - distribution = 1.0f; - } - DebugState.Framerate = - deltaTime * distribution + DebugState.Framerate * (1.0f - distribution); + + int& frameIdx = bd->framerateSecPerFrameIdx; + float& framerateSec = bd->framerateSecPerFrame[frameIdx]; + float& acc = bd->framerateSecPerFrameAcc; + int count = bd->framerateSecPerFrame.size(); + acc += deltaTime - framerateSec; + framerateSec = deltaTime; + frameIdx = (frameIdx + 1) % count; + DebugState.Framerate = acc > 0.0f ? 1.0f / (acc / (float)count) : FLT_MAX; } if (bd->mouse_pending_leave_frame && bd->mouse_pending_leave_frame >= ImGui::GetFrameCount() && From eb49193309f2b40ffe06e74060939761142d8f24 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:24:29 -0800 Subject: [PATCH 08/65] liverpool: Revert queue scope markers. (#2166) --- src/video_core/amdgpu/liverpool.cpp | 41 +++++++++++------------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 036a031a7..8355dd1e6 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -224,10 +224,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanScopeMarkerBegin("gfx"); - } - const auto base_addr = reinterpret_cast(dcb.data()); while (!dcb.empty()) { const auto* header = reinterpret_cast(dcb.data()); @@ -416,7 +412,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndex2", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndex2", cmd_address)); rasterizer->Draw(true); rasterizer->ScopeMarkerEnd(); } @@ -433,7 +429,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexOffset2", cmd_address)); + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexOffset2", cmd_address)); rasterizer->Draw(true, draw_index_off->index_offset); rasterizer->ScopeMarkerEnd(); } @@ -448,7 +445,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexAuto", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndexAuto", cmd_address)); rasterizer->Draw(false); rasterizer->ScopeMarkerEnd(); } @@ -463,7 +460,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DrawIndirect", cmd_address)); rasterizer->DrawIndirect(false, indirect_args_addr, offset, size, 1, 0); rasterizer->ScopeMarkerEnd(); } @@ -479,7 +476,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DrawIndexIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DrawIndexIndirect", cmd_address)); rasterizer->DrawIndirect(true, indirect_args_addr, offset, size, 1, 0); rasterizer->ScopeMarkerEnd(); } @@ -495,7 +493,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); rasterizer->ScopeMarkerBegin( - fmt::format("{}:DrawIndexIndirectCountMulti", cmd_address)); + fmt::format("gfx:{}:DrawIndexIndirectCountMulti", cmd_address)); rasterizer->DrawIndirect( true, indirect_args_addr, offset, draw_index_indirect->stride, draw_index_indirect->count, draw_index_indirect->countAddr); @@ -516,7 +514,7 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchDirect", cmd_address)); + rasterizer->ScopeMarkerBegin(fmt::format("gfx:{}:DispatchDirect", cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -534,7 +532,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin( + fmt::format("gfx:{}:DispatchIndirect", cmd_address)); rasterizer->DispatchIndirect(indirect_args_addr, offset, size); rasterizer->ScopeMarkerEnd(); } @@ -702,10 +701,6 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanScopeMarkerEnd(); - } - if (ce_task.handle) { ASSERT_MSG(ce_task.handle.done(), "Partially processed CCB"); ce_task.handle.destroy(); @@ -719,10 +714,6 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); const auto& queue = asc_queues[{vqid}]; - if (rasterizer) { - rasterizer->ScopeMarkerBegin(fmt::format("asc[{}]", vqid)); - } - auto base_addr = reinterpret_cast(acb.data()); while (!acb.empty()) { const auto* header = reinterpret_cast(acb.data()); @@ -820,7 +811,8 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchDirect", cmd_address)); + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchDirect", vqid, cmd_address)); rasterizer->DispatchDirect(); rasterizer->ScopeMarkerEnd(); } @@ -838,7 +830,8 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } if (rasterizer && (cs_program.dispatch_initiator & 1)) { const auto cmd_address = reinterpret_cast(header); - rasterizer->ScopeMarkerBegin(fmt::format("{}:DispatchIndirect", cmd_address)); + rasterizer->ScopeMarkerBegin( + fmt::format("asc[{}]:{}:DispatchIndirect", vqid, cmd_address)); rasterizer->DispatchIndirect(ib_address, 0, size); rasterizer->ScopeMarkerEnd(); } @@ -886,10 +879,6 @@ Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { } } - if (rasterizer) { - rasterizer->ScopeMarkerEnd(); - } - FIBER_EXIT; } From 3b474a12f99571a70ef526dfe5aa5c633ef2c6bf Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 16 Jan 2025 18:40:03 -0800 Subject: [PATCH 09/65] shader_recompiler: Improvements to buffer addressing implementation. (#2123) --- .../frontend/translate/vector_memory.cpp | 96 +++++++++------- .../ir/passes/constant_propagation_pass.cpp | 12 +- .../ir/passes/resource_tracking_pass.cpp | 103 +++++++++++------- src/shader_recompiler/specialization.h | 11 ++ src/video_core/amdgpu/resource.h | 10 ++ 5 files changed, 149 insertions(+), 83 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index a5b54dff7..8fa0f3f87 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -164,8 +164,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { } void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) { - const auto& mtbuf = inst.control.mtbuf; - const bool is_ring = mtbuf.glc && mtbuf.slc; + const auto& mubuf = inst.control.mubuf; + const bool is_ring = mubuf.glc && mubuf.slc; const IR::VectorReg vaddr{inst.src[0].code}; const IR::ScalarReg sharp{inst.src[2].code * 4}; const IR::Value soffset{GetSrc(inst.src[3])}; @@ -178,22 +178,23 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_typed, const GcnInst& inst) if (is_ring) { return ir.CompositeConstruct(ir.GetVectorReg(vaddr), soffset); } - if (mtbuf.idxen && mtbuf.offen) { + if (mubuf.idxen && mubuf.offen) { return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); } - if (mtbuf.idxen || mtbuf.offen) { + if (mubuf.idxen || mubuf.offen) { return ir.GetVectorReg(vaddr); } return {}; }(); IR::BufferInstInfo buffer_info{}; - buffer_info.index_enable.Assign(mtbuf.idxen); - buffer_info.offset_enable.Assign(mtbuf.offen); - buffer_info.inst_offset.Assign(mtbuf.offset); - buffer_info.globally_coherent.Assign(mtbuf.glc); - buffer_info.system_coherent.Assign(mtbuf.slc); + buffer_info.index_enable.Assign(mubuf.idxen); + buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.inst_offset.Assign(mubuf.offset); + buffer_info.globally_coherent.Assign(mubuf.glc); + buffer_info.system_coherent.Assign(mubuf.slc); if (is_typed) { + const auto& mtbuf = inst.control.mtbuf; const auto dmft = static_cast(mtbuf.dfmt); const auto nfmt = static_cast(mtbuf.nfmt); ASSERT(nfmt == AmdGpu::NumberFormat::Float && @@ -220,9 +221,11 @@ void Translator::BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst) { const auto& mubuf = inst.control.mubuf; const IR::VectorReg vaddr{inst.src[0].code}; const IR::ScalarReg sharp{inst.src[2].code * 4}; - ASSERT_MSG(!mubuf.offen && mubuf.offset == 0, "Offsets for image buffers are not supported"); const IR::Value address = [&] -> IR::Value { - if (mubuf.idxen) { + if (mubuf.idxen && mubuf.offen) { + return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); + } + if (mubuf.idxen || mubuf.offen) { return ir.GetVectorReg(vaddr); } return {}; @@ -230,13 +233,17 @@ void Translator::BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst) { const IR::Value soffset{GetSrc(inst.src[3])}; ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported"); - IR::BufferInstInfo info{}; - info.index_enable.Assign(mubuf.idxen); + IR::BufferInstInfo buffer_info{}; + buffer_info.index_enable.Assign(mubuf.idxen); + buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.inst_offset.Assign(mubuf.offset); + buffer_info.globally_coherent.Assign(mubuf.glc); + buffer_info.system_coherent.Assign(mubuf.slc); const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(sharp), ir.GetScalarReg(sharp + 1), ir.GetScalarReg(sharp + 2), ir.GetScalarReg(sharp + 3)); - const IR::Value value = ir.LoadBufferFormat(handle, address, info); + const IR::Value value = ir.LoadBufferFormat(handle, address, buffer_info); const IR::VectorReg dst_reg{inst.src[1].code}; for (u32 i = 0; i < num_dwords; i++) { ir.SetVectorReg(dst_reg + i, IR::F32{ir.CompositeExtract(value, i)}); @@ -244,8 +251,8 @@ void Translator::BUFFER_LOAD_FORMAT(u32 num_dwords, const GcnInst& inst) { } void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst) { - const auto& mtbuf = inst.control.mtbuf; - const bool is_ring = mtbuf.glc && mtbuf.slc; + const auto& mubuf = inst.control.mubuf; + const bool is_ring = mubuf.glc && mubuf.slc; const IR::VectorReg vaddr{inst.src[0].code}; const IR::ScalarReg sharp{inst.src[2].code * 4}; const IR::Value soffset{GetSrc(inst.src[3])}; @@ -259,22 +266,23 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst if (is_ring) { return ir.CompositeConstruct(ir.GetVectorReg(vaddr), soffset); } - if (mtbuf.idxen && mtbuf.offen) { + if (mubuf.idxen && mubuf.offen) { return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); } - if (mtbuf.idxen || mtbuf.offen) { + if (mubuf.idxen || mubuf.offen) { return ir.GetVectorReg(vaddr); } return {}; }(); IR::BufferInstInfo buffer_info{}; - buffer_info.index_enable.Assign(mtbuf.idxen); - buffer_info.offset_enable.Assign(mtbuf.offen); - buffer_info.inst_offset.Assign(mtbuf.offset); - buffer_info.globally_coherent.Assign(mtbuf.glc); - buffer_info.system_coherent.Assign(mtbuf.slc); + buffer_info.index_enable.Assign(mubuf.idxen); + buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.inst_offset.Assign(mubuf.offset); + buffer_info.globally_coherent.Assign(mubuf.glc); + buffer_info.system_coherent.Assign(mubuf.slc); if (is_typed) { + const auto& mtbuf = inst.control.mtbuf; const auto dmft = static_cast(mtbuf.dfmt); const auto nfmt = static_cast(mtbuf.nfmt); ASSERT(nfmt == AmdGpu::NumberFormat::Float && @@ -321,8 +329,12 @@ void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst) { const IR::Value soffset{GetSrc(inst.src[3])}; ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported"); - IR::BufferInstInfo info{}; - info.index_enable.Assign(mubuf.idxen); + IR::BufferInstInfo buffer_info{}; + buffer_info.index_enable.Assign(mubuf.idxen); + buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.inst_offset.Assign(mubuf.offset); + buffer_info.globally_coherent.Assign(mubuf.glc); + buffer_info.system_coherent.Assign(mubuf.slc); const IR::VectorReg src_reg{inst.src[1].code}; @@ -338,7 +350,7 @@ void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst) { const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(sharp), ir.GetScalarReg(sharp + 1), ir.GetScalarReg(sharp + 2), ir.GetScalarReg(sharp + 3)); - ir.StoreBufferFormat(handle, address, value, info); + ir.StoreBufferFormat(handle, address, value, buffer_info); } void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { @@ -358,10 +370,12 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { const IR::U32 soffset{GetSrc(inst.src[3])}; ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, "Non immediate offset not supported"); - IR::BufferInstInfo info{}; - info.index_enable.Assign(mubuf.idxen); - info.inst_offset.Assign(mubuf.offset); - info.offset_enable.Assign(mubuf.offen); + IR::BufferInstInfo buffer_info{}; + buffer_info.index_enable.Assign(mubuf.idxen); + buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.inst_offset.Assign(mubuf.offset); + buffer_info.globally_coherent.Assign(mubuf.glc); + buffer_info.system_coherent.Assign(mubuf.slc); IR::Value vdata_val = ir.GetVectorReg(vdata); const IR::Value handle = @@ -371,27 +385,27 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { const IR::Value original_val = [&] { switch (op) { case AtomicOp::Swap: - return ir.BufferAtomicSwap(handle, address, vdata_val, info); + return ir.BufferAtomicSwap(handle, address, vdata_val, buffer_info); case AtomicOp::Add: - return ir.BufferAtomicIAdd(handle, address, vdata_val, info); + return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info); case AtomicOp::Smin: - return ir.BufferAtomicIMin(handle, address, vdata_val, true, info); + return ir.BufferAtomicIMin(handle, address, vdata_val, true, buffer_info); case AtomicOp::Umin: - return ir.BufferAtomicIMin(handle, address, vdata_val, false, info); + return ir.BufferAtomicIMin(handle, address, vdata_val, false, buffer_info); case AtomicOp::Smax: - return ir.BufferAtomicIMax(handle, address, vdata_val, true, info); + return ir.BufferAtomicIMax(handle, address, vdata_val, true, buffer_info); case AtomicOp::Umax: - return ir.BufferAtomicIMax(handle, address, vdata_val, false, info); + return ir.BufferAtomicIMax(handle, address, vdata_val, false, buffer_info); case AtomicOp::And: - return ir.BufferAtomicAnd(handle, address, vdata_val, info); + return ir.BufferAtomicAnd(handle, address, vdata_val, buffer_info); case AtomicOp::Or: - return ir.BufferAtomicOr(handle, address, vdata_val, info); + return ir.BufferAtomicOr(handle, address, vdata_val, buffer_info); case AtomicOp::Xor: - return ir.BufferAtomicXor(handle, address, vdata_val, info); + return ir.BufferAtomicXor(handle, address, vdata_val, buffer_info); case AtomicOp::Inc: - return ir.BufferAtomicInc(handle, address, vdata_val, info); + return ir.BufferAtomicInc(handle, address, vdata_val, buffer_info); case AtomicOp::Dec: - return ir.BufferAtomicDec(handle, address, vdata_val, info); + return ir.BufferAtomicDec(handle, address, vdata_val, buffer_info); default: UNREACHABLE(); } diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index fcf2f7d9f..26d819d8e 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -222,9 +222,15 @@ void FoldMul(IR::Block& block, IR::Inst& inst) { return; } const IR::Value rhs{inst.Arg(1)}; - if (rhs.IsImmediate() && Arg(rhs) == 0) { - inst.ReplaceUsesWithAndRemove(IR::Value(0u)); - return; + if (rhs.IsImmediate()) { + if (Arg(rhs) == 0) { + inst.ReplaceUsesWithAndRemove(IR::Value(0u)); + return; + } + if (Arg(rhs) == 1) { + inst.ReplaceUsesWithAndRemove(inst.Arg(0)); + return; + } } } diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index a132cac2c..d94c5223a 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -484,55 +484,73 @@ void PatchDataRingAccess(IR::Block& block, IR::Inst& inst, Info& info, Descripto inst.SetArg(1, ir.Imm32(binding)); } +IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const Info& info, + const AmdGpu::Buffer& buffer, u32 stride) { + const auto inst_info = inst.Flags(); + + // index = (inst_idxen ? vgpr_index : 0) + (const_add_tid_enable ? thread_id[5:0] : 0) + IR::U32 index = ir.Imm32(0U); + if (inst_info.index_enable) { + const IR::U32 vgpr_index{inst_info.offset_enable + ? IR::U32{ir.CompositeExtract(inst.Arg(1), 0)} + : IR::U32{inst.Arg(1)}}; + index = ir.IAdd(index, vgpr_index); + } + if (buffer.add_tid_enable) { + ASSERT_MSG(info.l_stage == LogicalStage::Compute, + "Thread ID buffer addressing is not supported outside of compute."); + const IR::U32 thread_id{ir.LaneId()}; + index = ir.IAdd(index, thread_id); + } + // offset = (inst_offen ? vgpr_offset : 0) + inst_offset + IR::U32 offset = ir.Imm32(inst_info.inst_offset.Value()); + if (inst_info.offset_enable) { + const IR::U32 vgpr_offset = inst_info.index_enable + ? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)} + : IR::U32{inst.Arg(1)}; + offset = ir.IAdd(offset, vgpr_offset); + } + const IR::U32 const_stride = ir.Imm32(stride); + IR::U32 buffer_offset; + if (buffer.swizzle_enable) { + const IR::U32 const_index_stride = ir.Imm32(buffer.GetIndexStride()); + const IR::U32 const_element_size = ir.Imm32(buffer.GetElementSize()); + // index_msb = index / const_index_stride + const IR::U32 index_msb{ir.IDiv(index, const_index_stride)}; + // index_lsb = index % const_index_stride + const IR::U32 index_lsb{ir.IMod(index, const_index_stride)}; + // offset_msb = offset / const_element_size + const IR::U32 offset_msb{ir.IDiv(offset, const_element_size)}; + // offset_lsb = offset % const_element_size + const IR::U32 offset_lsb{ir.IMod(offset, const_element_size)}; + // buffer_offset = + // (index_msb * const_stride + offset_msb * const_element_size) * const_index_stride + // + index_lsb * const_element_size + offset_lsb + const IR::U32 buffer_offset_msb = ir.IMul( + ir.IAdd(ir.IMul(index_msb, const_stride), ir.IMul(offset_msb, const_element_size)), + const_index_stride); + const IR::U32 buffer_offset_lsb = + ir.IAdd(ir.IMul(index_lsb, const_element_size), offset_lsb); + buffer_offset = ir.IAdd(buffer_offset_msb, buffer_offset_lsb); + } else { + // buffer_offset = index * const_stride + offset + buffer_offset = ir.IAdd(ir.IMul(index, const_stride), offset); + } + return buffer_offset; +} + void PatchBufferArgs(IR::Block& block, IR::Inst& inst, Info& info) { const auto handle = inst.Arg(0); const auto buffer_res = info.buffers[handle.U32()]; const auto buffer = buffer_res.GetSharp(info); - ASSERT(!buffer.add_tid_enable); - // Address of constant buffer reads can be calculated at IR emission time. if (inst.GetOpcode() == IR::Opcode::ReadConstBuffer) { return; } IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; - const auto inst_info = inst.Flags(); - - const IR::U32 index_stride = ir.Imm32(buffer.index_stride); - const IR::U32 element_size = ir.Imm32(buffer.element_size); - - // Compute address of the buffer using the stride. - IR::U32 address = ir.Imm32(inst_info.inst_offset.Value()); - if (inst_info.index_enable) { - const IR::U32 index = inst_info.offset_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 0)} - : IR::U32{inst.Arg(1)}; - if (buffer.swizzle_enable) { - const IR::U32 stride_index_stride = - ir.Imm32(static_cast(buffer.stride * buffer.index_stride)); - const IR::U32 index_msb = ir.IDiv(index, index_stride); - const IR::U32 index_lsb = ir.IMod(index, index_stride); - address = ir.IAdd(address, ir.IAdd(ir.IMul(index_msb, stride_index_stride), - ir.IMul(index_lsb, element_size))); - } else { - address = ir.IAdd(address, ir.IMul(index, ir.Imm32(buffer.GetStride()))); - } - } - if (inst_info.offset_enable) { - const IR::U32 offset = inst_info.index_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)} - : IR::U32{inst.Arg(1)}; - if (buffer.swizzle_enable) { - const IR::U32 element_size_index_stride = - ir.Imm32(buffer.element_size * buffer.index_stride); - const IR::U32 offset_msb = ir.IDiv(offset, element_size); - const IR::U32 offset_lsb = ir.IMod(offset, element_size); - address = ir.IAdd(address, - ir.IAdd(ir.IMul(offset_msb, element_size_index_stride), offset_lsb)); - } else { - address = ir.IAdd(address, offset); - } - } - inst.SetArg(1, address); + inst.SetArg(1, CalculateBufferAddress(ir, inst, info, buffer, buffer.stride)); } void PatchTextureBufferArgs(IR::Block& block, IR::Inst& inst, Info& info) { @@ -540,8 +558,15 @@ void PatchTextureBufferArgs(IR::Block& block, IR::Inst& inst, Info& info) { const auto buffer_res = info.texture_buffers[handle.U32()]; const auto buffer = buffer_res.GetSharp(info); - ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable); + // Only linear addressing with index is supported currently, since we cannot yet + // address with sub-texel granularity. + const auto inst_info = inst.Flags(); + ASSERT_MSG(!buffer.swizzle_enable && !inst_info.offset_enable && inst_info.inst_offset == 0, + "Unsupported texture buffer address mode."); + IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; + // Stride of 1 to get an index into formatted data. See above addressing limitations. + inst.SetArg(1, CalculateBufferAddress(ir, inst, info, buffer, 1U)); if (inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32) { const auto swizzled = ApplySwizzle(ir, inst.Arg(2), buffer.DstSelect()); diff --git a/src/shader_recompiler/specialization.h b/src/shader_recompiler/specialization.h index 18b1df1f9..523e63497 100644 --- a/src/shader_recompiler/specialization.h +++ b/src/shader_recompiler/specialization.h @@ -21,10 +21,16 @@ struct VsAttribSpecialization { struct BufferSpecialization { u16 stride : 14; u16 is_storage : 1; + u16 swizzle_enable : 1; + u8 index_stride : 2 = 0; + u8 element_size : 2 = 0; u32 size = 0; bool operator==(const BufferSpecialization& other) const { return stride == other.stride && is_storage == other.is_storage && + swizzle_enable == other.swizzle_enable && + (!swizzle_enable || + (index_stride == other.index_stride && element_size == other.element_size)) && (size >= other.is_storage || is_storage); } }; @@ -101,6 +107,11 @@ struct StageSpecialization { [](auto& spec, const auto& desc, AmdGpu::Buffer sharp) { spec.stride = sharp.GetStride(); spec.is_storage = desc.IsStorage(sharp); + spec.swizzle_enable = sharp.swizzle_enable; + if (spec.swizzle_enable) { + spec.index_stride = sharp.index_stride; + spec.element_size = sharp.element_size; + } if (!spec.is_storage) { spec.size = sharp.GetSize(); } diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 75b8b2acf..fa8edb3e2 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -76,6 +76,16 @@ struct Buffer { u32 GetSize() const noexcept { return stride == 0 ? num_records : (stride * num_records); } + + u32 GetIndexStride() const noexcept { + // Index stride is 2 bits, meaning 8, 16, 32, or 64. + return 8 << index_stride; + } + + u32 GetElementSize() const noexcept { + // Element size is 2 bits, meaning 2, 4, 8, or 16. + return 2 << element_size; + } }; static_assert(sizeof(Buffer) == 16); // 128bits From c13b29662e23220ff88811173da13463deb1392e Mon Sep 17 00:00:00 2001 From: baggins183 Date: Fri, 17 Jan 2025 00:14:54 -0800 Subject: [PATCH 10/65] handle control point strides that arent a multiple of 16 (#2172) --- .../backend/spirv/spirv_emit_context.cpp | 12 +++++------ .../ir/passes/hull_shader_transform.cpp | 21 +++++++++---------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 7e86dfb4b..7d492a384 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -395,7 +395,7 @@ void EmitContext::DefineInputs() { DefineVariable(U32[1], spv::BuiltIn::PatchVertices, spv::StorageClass::Input); primitive_id = DefineVariable(U32[1], spv::BuiltIn::PrimitiveId, spv::StorageClass::Input); - const u32 num_attrs = runtime_info.hs_info.ls_stride >> 4; + const u32 num_attrs = Common::AlignUp(runtime_info.hs_info.ls_stride, 16) >> 4; if (num_attrs > 0) { const Id per_vertex_type{TypeArray(F32[4], ConstU32(num_attrs))}; // The input vertex count isn't statically known, so make length 32 (what glslang does) @@ -409,7 +409,7 @@ void EmitContext::DefineInputs() { tess_coord = DefineInput(F32[3], std::nullopt, spv::BuiltIn::TessCoord); primitive_id = DefineVariable(U32[1], spv::BuiltIn::PrimitiveId, spv::StorageClass::Input); - const u32 num_attrs = runtime_info.vs_info.hs_output_cp_stride >> 4; + const u32 num_attrs = Common::AlignUp(runtime_info.vs_info.hs_output_cp_stride, 16) >> 4; if (num_attrs > 0) { const Id per_vertex_type{TypeArray(F32[4], ConstU32(num_attrs))}; // The input vertex count isn't statically known, so make length 32 (what glslang does) @@ -418,7 +418,7 @@ void EmitContext::DefineInputs() { Name(input_attr_array, "in_attrs"); } - u32 patch_base_location = runtime_info.vs_info.hs_output_cp_stride >> 4; + const u32 patch_base_location = num_attrs; for (size_t index = 0; index < 30; ++index) { if (!(info.uses_patches & (1U << index))) { continue; @@ -453,7 +453,7 @@ void EmitContext::DefineOutputs() { DefineVariable(type, spv::BuiltIn::CullDistance, spv::StorageClass::Output); } if (stage == Shader::Stage::Local && runtime_info.ls_info.links_with_tcs) { - const u32 num_attrs = runtime_info.ls_info.ls_stride >> 4; + const u32 num_attrs = Common::AlignUp(runtime_info.ls_info.ls_stride, 16) >> 4; if (num_attrs > 0) { const Id type{TypeArray(F32[4], ConstU32(num_attrs))}; output_attr_array = DefineOutput(type, 0); @@ -488,7 +488,7 @@ void EmitContext::DefineOutputs() { Decorate(output_tess_level_inner, spv::Decoration::Patch); } - const u32 num_attrs = runtime_info.hs_info.hs_output_cp_stride >> 4; + const u32 num_attrs = Common::AlignUp(runtime_info.hs_info.hs_output_cp_stride, 16) >> 4; if (num_attrs > 0) { const Id per_vertex_type{TypeArray(F32[4], ConstU32(num_attrs))}; // The input vertex count isn't statically known, so make length 32 (what glslang does) @@ -498,7 +498,7 @@ void EmitContext::DefineOutputs() { Name(output_attr_array, "out_attrs"); } - u32 patch_base_location = runtime_info.hs_info.hs_output_cp_stride >> 4; + const u32 patch_base_location = num_attrs; for (size_t index = 0; index < 30; ++index) { if (!(info.uses_patches & (1U << index))) { continue; diff --git a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp index 6164fec12..b41e38339 100644 --- a/src/shader_recompiler/ir/passes/hull_shader_transform.cpp +++ b/src/shader_recompiler/ir/passes/hull_shader_transform.cpp @@ -349,11 +349,11 @@ static IR::F32 ReadTessControlPointAttribute(IR::U32 addr, const u32 stride, IR: addr = ir.IAdd(addr, ir.Imm32(off_dw)); } const IR::U32 control_point_index = ir.IDiv(addr, ir.Imm32(stride)); - const IR::U32 addr_for_attrs = TryOptimizeAddressModulo(addr, stride, ir); - const IR::U32 attr_index = - ir.ShiftRightLogical(ir.IMod(addr_for_attrs, ir.Imm32(stride)), ir.Imm32(4u)); + const IR::U32 opt_addr = TryOptimizeAddressModulo(addr, stride, ir); + const IR::U32 offset = ir.IMod(opt_addr, ir.Imm32(stride)); + const IR::U32 attr_index = ir.ShiftRightLogical(offset, ir.Imm32(4u)); const IR::U32 comp_index = - ir.ShiftRightLogical(ir.BitwiseAnd(addr_for_attrs, ir.Imm32(0xFU)), ir.Imm32(2u)); + ir.ShiftRightLogical(ir.BitwiseAnd(offset, ir.Imm32(0xFU)), ir.Imm32(2u)); if (is_output_read_in_tcs) { return ir.ReadTcsGenericOuputAttribute(control_point_index, attr_index, comp_index); } else { @@ -452,13 +452,13 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { if (off_dw > 0) { addr = ir.IAdd(addr, ir.Imm32(off_dw)); } - u32 stride = runtime_info.hs_info.hs_output_cp_stride; + const u32 stride = runtime_info.hs_info.hs_output_cp_stride; // Invocation ID array index is implicit, handled by SPIRV backend - const IR::U32 addr_for_attrs = TryOptimizeAddressModulo(addr, stride, ir); - const IR::U32 attr_index = ir.ShiftRightLogical( - ir.IMod(addr_for_attrs, ir.Imm32(stride)), ir.Imm32(4u)); + const IR::U32 opt_addr = TryOptimizeAddressModulo(addr, stride, ir); + const IR::U32 offset = ir.IMod(opt_addr, ir.Imm32(stride)); + const IR::U32 attr_index = ir.ShiftRightLogical(offset, ir.Imm32(4u)); const IR::U32 comp_index = ir.ShiftRightLogical( - ir.BitwiseAnd(addr_for_attrs, ir.Imm32(0xFU)), ir.Imm32(2u)); + ir.BitwiseAnd(offset, ir.Imm32(0xFU)), ir.Imm32(2u)); ir.SetTcsGenericAttribute(data_component, attr_index, comp_index); } else { ASSERT(output_kind == AttributeRegion::PatchConst); @@ -535,8 +535,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) { // ... IR::IREmitter ir{*entry_block, it}; - ASSERT(runtime_info.hs_info.ls_stride % 16 == 0); - u32 num_attributes = runtime_info.hs_info.ls_stride / 16; + u32 num_attributes = Common::AlignUp(runtime_info.hs_info.ls_stride, 16) >> 4; const auto invocation_id = ir.GetAttributeU32(IR::Attribute::InvocationId); for (u32 attr_no = 0; attr_no < num_attributes; attr_no++) { for (u32 comp = 0; comp < 4; comp++) { From 1e5b316ac4f7b4e4a5f9d521812b425dc0f0b94f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:15:43 -0800 Subject: [PATCH 11/65] renderer_vulkan: Add debug markers for presenter. (#2167) --- .../renderer_vulkan/vk_presenter.cpp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 4d76eb8db..95c7c4ea1 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -468,6 +468,12 @@ bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { draw_scheduler.EndRendering(); const auto cmdbuf = draw_scheduler.CommandBuffer(); + if (Config::vkHostMarkersEnabled()) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "ShowSplash", + }); + } + if (!frame) { if (!splash_img.has_value()) { VideoCore::ImageInfo info{}; @@ -535,6 +541,10 @@ bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { .pImageMemoryBarriers = &post_barrier, }); + if (Config::vkHostMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } + // Flush frame creation commands. frame->ready_semaphore = draw_scheduler.GetMasterSemaphore()->Handle(); frame->ready_tick = draw_scheduler.CurrentTick(); @@ -563,6 +573,12 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) auto& scheduler = is_eop ? draw_scheduler : flip_scheduler; scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); + if (Config::vkHostMarkersEnabled()) { + const auto label = fmt::format("PrepareFrameInternal:{}", image_id.index); + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = label.c_str(), + }); + } const auto frame_subresources = vk::ImageSubresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, @@ -677,6 +693,10 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) .pImageMemoryBarriers = &post_barrier, }); + if (Config::vkHostMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } + // Flush frame creation commands. frame->ready_semaphore = scheduler.GetMasterSemaphore()->Handle(); frame->ready_tick = scheduler.CurrentTick(); @@ -724,6 +744,12 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { auto& scheduler = present_scheduler; const auto cmdbuf = scheduler.CommandBuffer(); + if (Config::vkHostMarkersEnabled()) { + cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{ + .pLabelName = "Present", + }); + } + { auto* profiler_ctx = instance.GetProfilerContext(); TracyVkNamedZoneC(profiler_ctx, renderer_gpu_zone, cmdbuf, "Host frame", @@ -816,6 +842,10 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { } } + if (Config::vkHostMarkersEnabled()) { + cmdbuf.endDebugUtilsLabelEXT(); + } + // Flush vulkan commands. SubmitInfo info{}; info.AddWait(swapchain.GetImageAcquiredSemaphore()); From 1d3427780a118532d6c74788f4bc7f8fc78d12f4 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:16:03 -0800 Subject: [PATCH 12/65] renderer_vulkan: Fix present related validation errors. (#2169) --- src/imgui/renderer/imgui_impl_vulkan.cpp | 5 +---- src/imgui/renderer/imgui_impl_vulkan.h | 1 - src/video_core/renderer_vulkan/vk_instance.cpp | 2 +- src/video_core/renderer_vulkan/vk_presenter.cpp | 10 ++++++++-- src/video_core/renderer_vulkan/vk_swapchain.cpp | 14 +++++++++++++- src/video_core/renderer_vulkan/vk_swapchain.h | 5 +++++ 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/imgui/renderer/imgui_impl_vulkan.cpp b/src/imgui/renderer/imgui_impl_vulkan.cpp index bd98fa004..104ce4b52 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.cpp +++ b/src/imgui/renderer/imgui_impl_vulkan.cpp @@ -687,8 +687,6 @@ void RenderDrawData(ImDrawData& draw_data, vk::CommandBuffer command_buffer, vk::DescriptorSet desc_set[1]{pcmd->TextureId->descriptor_set}; command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, bd->pipeline_layout, 0, {desc_set}, {}); - command_buffer.setColorBlendEnableEXT( - 0, {pcmd->TextureId->disable_blend ? vk::False : vk::True}); // Draw command_buffer.drawIndexed(pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, @@ -1058,10 +1056,9 @@ static void CreatePipeline(vk::Device device, const vk::AllocationCallbacks* all .pAttachments = color_attachment, }; - vk::DynamicState dynamic_states[3]{ + vk::DynamicState dynamic_states[2]{ vk::DynamicState::eViewport, vk::DynamicState::eScissor, - vk::DynamicState::eColorBlendEnableEXT, }; vk::PipelineDynamicStateCreateInfo dynamic_state{ .dynamicStateCount = (uint32_t)IM_ARRAYSIZE(dynamic_states), diff --git a/src/imgui/renderer/imgui_impl_vulkan.h b/src/imgui/renderer/imgui_impl_vulkan.h index 05f4489da..9b15dcae6 100644 --- a/src/imgui/renderer/imgui_impl_vulkan.h +++ b/src/imgui/renderer/imgui_impl_vulkan.h @@ -13,7 +13,6 @@ struct ImDrawData; namespace ImGui { struct Texture { vk::DescriptorSet descriptor_set{nullptr}; - bool disable_blend{false}; }; } // namespace ImGui diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 323b30816..1600600b9 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -270,6 +270,7 @@ bool Instance::CreateDevice() { legacy_vertex_attributes = add_extension(VK_EXT_LEGACY_VERTEX_ATTRIBUTES_EXTENSION_NAME); image_load_store_lod = add_extension(VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_EXTENSION_NAME); amd_gcn_shader = add_extension(VK_AMD_GCN_SHADER_EXTENSION_NAME); + add_extension(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. @@ -383,7 +384,6 @@ bool Instance::CreateDevice() { .extendedDynamicState = true, }, vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{ - .extendedDynamicState3ColorBlendEnable = true, .extendedDynamicState3ColorWriteMask = true, }, vk::PhysicalDeviceDepthClipControlFeaturesEXT{ diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 95c7c4ea1..0f45574bc 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -380,7 +380,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { const vk::ImageViewCreateInfo view_info = { .image = frame->image, .viewType = vk::ImageViewType::e2D, - .format = FormatToUnorm(format), + .format = swapchain.GetViewFormat(), .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, @@ -397,7 +397,6 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { frame->height = height; frame->imgui_texture = ImGui::Vulkan::AddTexture(view, vk::ImageLayout::eShaderReadOnlyOptimal); - frame->imgui_texture->disable_blend = true; } Frame* Presenter::PrepareLastFrame() { @@ -615,6 +614,13 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) VideoCore::ImageViewInfo info{}; info.format = image.info.pixel_format; + // Exclude alpha from output frame to avoid blending with UI. + info.mapping = vk::ComponentMapping{ + .r = vk::ComponentSwizzle::eIdentity, + .g = vk::ComponentSwizzle::eIdentity, + .b = vk::ComponentSwizzle::eIdentity, + .a = vk::ComponentSwizzle::eOne, + }; if (auto view = image.FindView(info)) { image_info.imageView = *texture_cache.GetImageView(view).image_view; } else { diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 556cccd32..438fe30ce 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -17,7 +17,7 @@ Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& windo FindPresentFormat(); Create(window.GetWidth(), window.GetHeight()); - ImGui::Core::Initialize(instance, window, image_count, surface_format.format); + ImGui::Core::Initialize(instance, window, image_count, view_format); } Swapchain::~Swapchain() { @@ -57,7 +57,17 @@ void Swapchain::Create(u32 width_, u32 height_) { const u32 queue_family_indices_count = exclusive ? 1u : 2u; const vk::SharingMode sharing_mode = exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent; + const vk::Format view_formats[2] = { + surface_format.format, + view_format, + }; + const vk::ImageFormatListCreateInfo format_list = { + .viewFormatCount = 2, + .pViewFormats = view_formats, + }; const vk::SwapchainCreateInfoKHR swapchain_info = { + .pNext = &format_list, + .flags = vk::SwapchainCreateFlagBitsKHR::eMutableFormat, .surface = surface, .minImageCount = image_count, .imageFormat = surface_format.format, @@ -149,6 +159,7 @@ void Swapchain::FindPresentFormat() { if (formats[0].format == vk::Format::eUndefined) { surface_format.format = vk::Format::eR8G8B8A8Srgb; surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; + view_format = FormatToUnorm(surface_format.format); return; } @@ -161,6 +172,7 @@ void Swapchain::FindPresentFormat() { surface_format.format = format; surface_format.colorSpace = sformat.colorSpace; + view_format = FormatToUnorm(surface_format.format); return; } diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 192271f32..9da75c758 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -61,6 +61,10 @@ public: return surface_format; } + vk::Format GetViewFormat() const { + return view_format; + } + vk::SwapchainKHR GetHandle() const { return swapchain; } @@ -114,6 +118,7 @@ private: vk::SwapchainKHR swapchain{}; vk::SurfaceKHR surface{}; vk::SurfaceFormatKHR surface_format; + vk::Format view_format; vk::Extent2D extent; vk::SurfaceTransformFlagBitsKHR transform; vk::CompositeAlphaFlagBitsKHR composite_alpha; From 9e5b50c86681053aa05a798663c6eaac6581cd46 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 17 Jan 2025 00:16:15 -0800 Subject: [PATCH 13/65] vk_platform: Clean up unnecessary debug message filters. (#2171) --- src/video_core/renderer_vulkan/vk_platform.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index 7f0bcb5d2..fdd590e9d 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -31,17 +31,6 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { - switch (static_cast(callback_data->messageIdNumber)) { - case 0x609a13b: // Vertex attribute at location not consumed by shader - case 0xc81ad50e: - case 0xb7c39078: - case 0x32868fde: // vkCreateBufferView(): pCreateInfo->range does not equal VK_WHOLE_SIZE - case 0x1012616b: // `VK_FORMAT_UNDEFINED` does not match fragment shader output type - return VK_FALSE; - default: - break; - } - Common::Log::Level level{}; switch (severity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: From 0cee59cbe6b7ab2471484234e5e7f98c0f6c55e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pl=C3=ADnio=20Larrubia?= Date: Fri, 17 Jan 2025 11:56:52 -0300 Subject: [PATCH 14/65] ci: Don't use the same cache for clang and gcc on linux (#2173) --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c36c026fc..3da7163dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -390,7 +390,7 @@ jobs: - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-configuration + cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -401,7 +401,7 @@ jobs: - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{ runner.os }}-sdl-cache-cmake-build + cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} @@ -426,7 +426,7 @@ jobs: - name: Cache CMake Configuration uses: actions/cache@v4 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-configuration + cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-configuration with: path: | ${{github.workspace}}/build @@ -437,7 +437,7 @@ jobs: - name: Cache CMake Build uses: hendrikmuhs/ccache-action@v1.2.14 env: - cache-name: ${{ runner.os }}-qt-cache-cmake-build + cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build with: append-timestamp: false key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} From 4e8c178aecc2d968095a8cc3d88f4290b29b0ed3 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Fri, 17 Jan 2025 16:11:37 -0300 Subject: [PATCH 15/65] imgui: central node auto-hide tab (#2174) --- src/imgui/renderer/imgui_core.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/imgui/renderer/imgui_core.cpp b/src/imgui/renderer/imgui_core.cpp index f3e4609ba..26253822c 100644 --- a/src/imgui/renderer/imgui_core.cpp +++ b/src/imgui/renderer/imgui_core.cpp @@ -186,7 +186,8 @@ ImGuiID NewFrame(bool is_reusing_frame) { Sdl::NewFrame(is_reusing_frame); ImGui::NewFrame(); - ImGuiWindowFlags flags = ImGuiDockNodeFlags_PassthruCentralNode; + ImGuiWindowFlags flags = + ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_AutoHideTabBar; if (!DebugState.IsShowingDebugMenuBar()) { flags |= ImGuiDockNodeFlags_NoTabBar; } From 99a04357d139461e741c2003733e74af3ede6747 Mon Sep 17 00:00:00 2001 From: polybiusproxy <47796739+polybiusproxy@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:51:33 +0100 Subject: [PATCH 16/65] don't compile cs with higher shared memory than supported (#2175) --- src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | 4 ++++ src/shader_recompiler/runtime_info.h | 1 + src/video_core/renderer_vulkan/vk_instance.h | 5 +++++ src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 1 + 4 files changed, 11 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 7d492a384..295bef75d 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -847,6 +847,10 @@ void EmitContext::DefineSharedMemory() { if (shared_memory_size == 0) { shared_memory_size = DefaultSharedMemSize; } + + const u32 max_shared_memory_size = runtime_info.cs_info.max_shared_memory_size; + ASSERT(shared_memory_size <= max_shared_memory_size); + const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)}; const Id type{TypeArray(U32[1], ConstU32(num_elements))}; shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type); diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index cf49b0879..8a5e0a1a6 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -198,6 +198,7 @@ struct FragmentRuntimeInfo { struct ComputeRuntimeInfo { u32 shared_memory_size; + u32 max_shared_memory_size; std::array workgroup_size; std::array tgid_enable; diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index e0d4d0b4d..1b0c276dc 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -239,6 +239,11 @@ public: return subgroup_size; } + /// Returns the maximum size of compute shared memory. + u32 MaxComputeSharedMemorySize() const { + return properties.limits.maxComputeSharedMemorySize; + } + /// Returns the maximum supported elements in a texel buffer u32 MaxTexelBufferElements() const { return properties.limits.maxTexelBufferElements; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index f93494389..86f6dcc7a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -183,6 +183,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS info.cs_info.tgid_enable = {cs_pgm.IsTgidEnabled(0), cs_pgm.IsTgidEnabled(1), cs_pgm.IsTgidEnabled(2)}; info.cs_info.shared_memory_size = cs_pgm.SharedMemSize(); + info.cs_info.max_shared_memory_size = instance.MaxComputeSharedMemorySize(); break; } default: From e134fc5f1e0492102f75e30aa3cefd512128c59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Sat, 18 Jan 2025 07:09:10 +0100 Subject: [PATCH 17/65] Qt: Open shadPS4 Folder (#2107) * Qt: Open shadPS4 Folder * clang-format * Change order * Update pt_BR.ts --- src/qt_gui/main_window.cpp | 7 +++++++ src/qt_gui/main_window_ui.h | 8 ++++++++ src/qt_gui/translations/ar.ts | 4 ++++ src/qt_gui/translations/da_DK.ts | 4 ++++ src/qt_gui/translations/de.ts | 4 ++++ src/qt_gui/translations/el.ts | 4 ++++ src/qt_gui/translations/en.ts | 4 ++++ src/qt_gui/translations/es_ES.ts | 4 ++++ src/qt_gui/translations/fa_IR.ts | 4 ++++ src/qt_gui/translations/fi.ts | 4 ++++ src/qt_gui/translations/fr.ts | 4 ++++ src/qt_gui/translations/hu_HU.ts | 4 ++++ src/qt_gui/translations/id.ts | 4 ++++ src/qt_gui/translations/it.ts | 4 ++++ src/qt_gui/translations/ja_JP.ts | 4 ++++ src/qt_gui/translations/ko_KR.ts | 4 ++++ src/qt_gui/translations/lt_LT.ts | 4 ++++ src/qt_gui/translations/nb.ts | 4 ++++ src/qt_gui/translations/nl.ts | 4 ++++ src/qt_gui/translations/pl_PL.ts | 4 ++++ src/qt_gui/translations/pt_BR.ts | 6 +++++- src/qt_gui/translations/ro_RO.ts | 4 ++++ src/qt_gui/translations/ru_RU.ts | 4 ++++ src/qt_gui/translations/sq.ts | 4 ++++ src/qt_gui/translations/sv.ts | 4 ++++ src/qt_gui/translations/tr_TR.ts | 4 ++++ src/qt_gui/translations/uk_UA.ts | 4 ++++ src/qt_gui/translations/vi_VN.ts | 4 ++++ src/qt_gui/translations/zh_CN.ts | 4 ++++ src/qt_gui/translations/zh_TW.ts | 4 ++++ 30 files changed, 128 insertions(+), 1 deletion(-) diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index bd3c27809..3ee392613 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -247,6 +247,12 @@ void MainWindow::CreateConnects() { } }); + connect(ui->shadFolderAct, &QAction::triggered, this, [this]() { + QString userPath; + Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); + QDesktopServices::openUrl(QUrl::fromLocalFile(userPath)); + }); + connect(ui->playButton, &QPushButton::clicked, this, &MainWindow::StartGame); connect(m_game_grid_frame.get(), &QTableWidget::cellDoubleClicked, this, &MainWindow::StartGame); @@ -982,6 +988,7 @@ QIcon MainWindow::RecolorIcon(const QIcon& icon, bool isWhite) { void MainWindow::SetUiIcons(bool isWhite) { ui->bootInstallPkgAct->setIcon(RecolorIcon(ui->bootInstallPkgAct->icon(), isWhite)); ui->bootGameAct->setIcon(RecolorIcon(ui->bootGameAct->icon(), isWhite)); + ui->shadFolderAct->setIcon(RecolorIcon(ui->shadFolderAct->icon(), isWhite)); ui->exitAct->setIcon(RecolorIcon(ui->exitAct->icon(), isWhite)); #ifdef ENABLE_UPDATER ui->updaterAct->setIcon(RecolorIcon(ui->updaterAct->icon(), isWhite)); diff --git a/src/qt_gui/main_window_ui.h b/src/qt_gui/main_window_ui.h index 0d5038d7e..7de166121 100644 --- a/src/qt_gui/main_window_ui.h +++ b/src/qt_gui/main_window_ui.h @@ -12,6 +12,7 @@ public: QAction* bootInstallPkgAct; QAction* bootGameAct; QAction* addElfFolderAct; + QAction* shadFolderAct; QAction* exitAct; QAction* showGameListAct; QAction* refreshGameListAct; @@ -89,6 +90,9 @@ public: addElfFolderAct = new QAction(MainWindow); addElfFolderAct->setObjectName("addElfFolderAct"); addElfFolderAct->setIcon(QIcon(":images/folder_icon.png")); + shadFolderAct = new QAction(MainWindow); + shadFolderAct->setObjectName("shadFolderAct"); + shadFolderAct->setIcon(QIcon(":images/folder_icon.png")); exitAct = new QAction(MainWindow); exitAct->setObjectName("exitAct"); exitAct->setIcon(QIcon(":images/exit_icon.png")); @@ -274,7 +278,9 @@ public: menuBar->addAction(menuHelp->menuAction()); menuFile->addAction(bootInstallPkgAct); menuFile->addAction(bootGameAct); + menuFile->addSeparator(); menuFile->addAction(addElfFolderAct); + menuFile->addAction(shadFolderAct); menuFile->addSeparator(); menuFile->addAction(menuRecent->menuAction()); menuFile->addSeparator(); @@ -333,6 +339,8 @@ public: "MainWindow", "Install application from a .pkg file", nullptr)); #endif // QT_CONFIG(tooltip) menuRecent->setTitle(QCoreApplication::translate("MainWindow", "Recent Games", nullptr)); + shadFolderAct->setText( + QCoreApplication::translate("MainWindow", "Open shadPS4 Folder", nullptr)); exitAct->setText(QCoreApplication::translate("MainWindow", "Exit", nullptr)); #if QT_CONFIG(tooltip) exitAct->setToolTip(QCoreApplication::translate("MainWindow", "Exit shadPS4", nullptr)); diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index a4dadcb1a..47bd673b2 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -247,6 +247,10 @@ Recent Games الألعاب الأخيرة + + Open shadPS4 Folder + Open shadPS4 Folder + Exit خروج diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 70b7d3ecc..91a98abd4 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 7f1de3afd..b1e1d2664 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -247,6 +247,10 @@ Recent Games Zuletzt gespielt + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Beenden diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 84165536e..ecda0ede0 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index fad185d41..9127df7e3 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index a97d3d3c8..a47f7c577 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -247,6 +247,10 @@ Recent Games Juegos recientes + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Salir diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 697e615fb..976e7614e 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -247,6 +247,10 @@ Recent Games بازی های اخیر + + Open shadPS4 Folder + Open shadPS4 Folder + Exit خروج diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 51e85dfbb..abc091b7e 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -247,6 +247,10 @@ Recent Games Viimeisimmät Pelit + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Sulje diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index 35f3eb55f..d2a1c5307 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -247,6 +247,10 @@ Recent Games Jeux récents + + Open shadPS4 Folder + Ouvrir le dossier de shadPS4 + Exit Fermer diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index a2bd9c1da..dff6a3a18 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -247,6 +247,10 @@ Recent Games Legutóbbi Játékok + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Kilépés diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index b97914ca2..e6fb8b5aa 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index d4ea1c7e6..73dbdc603 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -247,6 +247,10 @@ Recent Games Giochi Recenti + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Uscita diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 359955765..e07d4eb25 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -247,6 +247,10 @@ Recent Games 最近のゲーム + + Open shadPS4 Folder + Open shadPS4 Folder + Exit 終了 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 9cca0b656..560b58340 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 0594bcbd2..e2ec1e5c3 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index 8ca8246ba..b94d29b23 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -247,6 +247,10 @@ Recent Games Nylige spill + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Avslutt diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index 12d644458..add27500f 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 782db12e2..3280beea7 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -247,6 +247,10 @@ Recent Games Ostatnie gry + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Wyjdź diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 94bbf028a..b9d889519 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -247,6 +247,10 @@ Recent Games Jogos Recentes + + Open shadPS4 Folder + Abrir pasta shadPS4 + Exit Sair @@ -1341,4 +1345,4 @@ TB - \ No newline at end of file + diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 3bd8e38b5..00a9eb179 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index a38e2fd98..4c90450dd 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -247,6 +247,10 @@ Recent Games Недавние игры + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Выход diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index a83dc9829..768db1e75 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -247,6 +247,10 @@ Recent Games Lojërat e fundit + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Dil diff --git a/src/qt_gui/translations/sv.ts b/src/qt_gui/translations/sv.ts index 9a244a9df..3781ba45c 100644 --- a/src/qt_gui/translations/sv.ts +++ b/src/qt_gui/translations/sv.ts @@ -722,6 +722,10 @@ Recent Games Senaste spel + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Avsluta diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index be50f935a..5e8499073 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -247,6 +247,10 @@ Recent Games Son Oyunlar + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Çıkış diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index ff4e48e80..a1c7e97e0 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -247,6 +247,10 @@ Recent Games Нещодавні ігри + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Вихід diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index e546d955c..a579a1983 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index ece5f9490..5450f3dfd 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -247,6 +247,10 @@ Recent Games 最近启动的游戏 + + Open shadPS4 Folder + Open shadPS4 Folder + Exit 退出 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 11642d52b..0ce0b4d69 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -247,6 +247,10 @@ Recent Games Recent Games + + Open shadPS4 Folder + Open shadPS4 Folder + Exit Exit From a5440e0e43f1d91fff0b23e0c0ba4103d5e8976e Mon Sep 17 00:00:00 2001 From: Ian Maclachlan <32774670+imaclachlan@users.noreply.github.com> Date: Sat, 18 Jan 2025 06:16:07 +0000 Subject: [PATCH 18/65] Update kernel.cpp (#2125) In kernel.cpp boost io_context.reset() deprecated/discontinued in latest versions. Changed to io_context.restart() as recommended. --- src/core/libraries/kernel/kernel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index a9d04ca38..2b7735219 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -60,7 +60,7 @@ static void KernelServiceThread(std::stop_token stoken) { } io_context.run(); - io_context.reset(); + io_context.restart(); asio_requests = 0; } From 7b8177f48e3d5dfa12b2d416fdf291572e56b905 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Sat, 18 Jan 2025 09:20:38 +0300 Subject: [PATCH 19/65] renderer: handle disabled clipping (#2146) Co-authored-by: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> --- .../backend/spirv/emit_spirv_special.cpp | 38 +++++++++++ .../backend/spirv/spirv_emit_context.cpp | 37 ++++++---- src/shader_recompiler/info.h | 8 +++ src/shader_recompiler/profile.h | 2 + src/shader_recompiler/runtime_info.h | 4 +- .../renderer_vulkan/vk_graphics_pipeline.h | 7 +- .../renderer_vulkan/vk_instance.cpp | 6 ++ src/video_core/renderer_vulkan/vk_instance.h | 8 +++ .../renderer_vulkan/vk_pipeline_cache.cpp | 5 ++ .../renderer_vulkan/vk_rasterizer.cpp | 67 ++++++++++++++----- .../renderer_vulkan/vk_rasterizer.h | 2 +- 11 files changed, 149 insertions(+), 35 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp index 4a22ba09f..a0a3ed8ff 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -24,10 +24,48 @@ void ConvertDepthMode(EmitContext& ctx) { ctx.OpStore(ctx.output_position, vector); } +void ConvertPositionToClipSpace(EmitContext& ctx) { + const Id type{ctx.F32[1]}; + Id position{ctx.OpLoad(ctx.F32[4], ctx.output_position)}; + const Id x{ctx.OpCompositeExtract(type, position, 0u)}; + const Id y{ctx.OpCompositeExtract(type, position, 1u)}; + const Id z{ctx.OpCompositeExtract(type, position, 2u)}; + const Id w{ctx.OpCompositeExtract(type, position, 3u)}; + const Id xoffset_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, type), + ctx.push_data_block, + ctx.ConstU32(PushData::XOffsetIndex))}; + const Id xoffset{ctx.OpLoad(type, xoffset_ptr)}; + const Id yoffset_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, type), + ctx.push_data_block, + ctx.ConstU32(PushData::YOffsetIndex))}; + const Id yoffset{ctx.OpLoad(type, yoffset_ptr)}; + const Id xscale_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, type), + ctx.push_data_block, + ctx.ConstU32(PushData::XScaleIndex))}; + const Id xscale{ctx.OpLoad(type, xscale_ptr)}; + const Id yscale_ptr{ctx.OpAccessChain(ctx.TypePointer(spv::StorageClass::PushConstant, type), + ctx.push_data_block, + ctx.ConstU32(PushData::YScaleIndex))}; + const Id yscale{ctx.OpLoad(type, yscale_ptr)}; + const Id vport_w = + ctx.Constant(type, float(std::min(ctx.profile.max_viewport_width / 2, 8_KB))); + const Id wnd_x = ctx.OpFAdd(type, ctx.OpFMul(type, x, xscale), xoffset); + const Id ndc_x = ctx.OpFSub(type, ctx.OpFDiv(type, wnd_x, vport_w), ctx.Constant(type, 1.f)); + const Id vport_h = + ctx.Constant(type, float(std::min(ctx.profile.max_viewport_height / 2, 8_KB))); + const Id wnd_y = ctx.OpFAdd(type, ctx.OpFMul(type, y, yscale), yoffset); + const Id ndc_y = ctx.OpFSub(type, ctx.OpFDiv(type, wnd_y, vport_h), ctx.Constant(type, 1.f)); + const Id vector{ctx.OpCompositeConstruct(ctx.F32[4], std::array({ndc_x, ndc_y, z, w}))}; + ctx.OpStore(ctx.output_position, vector); +} + void EmitEpilogue(EmitContext& ctx) { if (ctx.stage == Stage::Vertex && ctx.runtime_info.vs_info.emulate_depth_negative_one_to_one) { ConvertDepthMode(ctx); } + if (ctx.stage == Stage::Vertex && ctx.runtime_info.vs_info.clip_disable) { + ConvertPositionToClipSpace(ctx); + } } void EmitDiscard(EmitContext& ctx) { diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 295bef75d..b0bf5aa0a 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -568,25 +568,34 @@ void EmitContext::DefineOutputs() { void EmitContext::DefinePushDataBlock() { // Create push constants block for instance steps rates - const Id struct_type{Name( - TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4], U32[4], U32[4], U32[4]), "AuxData")}; + const Id struct_type{Name(TypeStruct(U32[1], U32[1], U32[4], U32[4], U32[4], U32[4], U32[4], + U32[4], F32[1], F32[1], F32[1], F32[1]), + "AuxData")}; Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, 0, "sr0"); MemberName(struct_type, 1, "sr1"); - MemberName(struct_type, 2, "buf_offsets0"); - MemberName(struct_type, 3, "buf_offsets1"); - MemberName(struct_type, 4, "ud_regs0"); - MemberName(struct_type, 5, "ud_regs1"); - MemberName(struct_type, 6, "ud_regs2"); - MemberName(struct_type, 7, "ud_regs3"); + MemberName(struct_type, Shader::PushData::BufOffsetIndex + 0, "buf_offsets0"); + MemberName(struct_type, Shader::PushData::BufOffsetIndex + 1, "buf_offsets1"); + MemberName(struct_type, Shader::PushData::UdRegsIndex + 0, "ud_regs0"); + MemberName(struct_type, Shader::PushData::UdRegsIndex + 1, "ud_regs1"); + MemberName(struct_type, Shader::PushData::UdRegsIndex + 2, "ud_regs2"); + MemberName(struct_type, Shader::PushData::UdRegsIndex + 3, "ud_regs3"); + MemberName(struct_type, Shader::PushData::XOffsetIndex, "xoffset"); + MemberName(struct_type, Shader::PushData::YOffsetIndex, "yoffset"); + MemberName(struct_type, Shader::PushData::XScaleIndex, "xscale"); + MemberName(struct_type, Shader::PushData::YScaleIndex, "yscale"); MemberDecorate(struct_type, 0, spv::Decoration::Offset, 0U); MemberDecorate(struct_type, 1, spv::Decoration::Offset, 4U); - MemberDecorate(struct_type, 2, spv::Decoration::Offset, 8U); - MemberDecorate(struct_type, 3, spv::Decoration::Offset, 24U); - MemberDecorate(struct_type, 4, spv::Decoration::Offset, 40U); - MemberDecorate(struct_type, 5, spv::Decoration::Offset, 56U); - MemberDecorate(struct_type, 6, spv::Decoration::Offset, 72U); - MemberDecorate(struct_type, 7, spv::Decoration::Offset, 88U); + MemberDecorate(struct_type, Shader::PushData::BufOffsetIndex + 0, spv::Decoration::Offset, 8U); + MemberDecorate(struct_type, Shader::PushData::BufOffsetIndex + 1, spv::Decoration::Offset, 24U); + MemberDecorate(struct_type, Shader::PushData::UdRegsIndex + 0, spv::Decoration::Offset, 40U); + MemberDecorate(struct_type, Shader::PushData::UdRegsIndex + 1, spv::Decoration::Offset, 56U); + MemberDecorate(struct_type, Shader::PushData::UdRegsIndex + 2, spv::Decoration::Offset, 72U); + MemberDecorate(struct_type, Shader::PushData::UdRegsIndex + 3, spv::Decoration::Offset, 88U); + MemberDecorate(struct_type, Shader::PushData::XOffsetIndex, spv::Decoration::Offset, 104U); + MemberDecorate(struct_type, Shader::PushData::YOffsetIndex, spv::Decoration::Offset, 108U); + MemberDecorate(struct_type, Shader::PushData::XScaleIndex, spv::Decoration::Offset, 112U); + MemberDecorate(struct_type, Shader::PushData::YScaleIndex, spv::Decoration::Offset, 116U); push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); Name(push_data_block, "push_data"); interfaces.push_back(push_data_block); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index aeff346fa..2cde30629 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -96,11 +96,19 @@ using FMaskResourceList = boost::container::small_vector; struct PushData { static constexpr u32 BufOffsetIndex = 2; static constexpr u32 UdRegsIndex = 4; + static constexpr u32 XOffsetIndex = 8; + static constexpr u32 YOffsetIndex = 9; + static constexpr u32 XScaleIndex = 10; + static constexpr u32 YScaleIndex = 11; u32 step0; u32 step1; std::array buf_offsets; std::array ud_regs; + float xoffset; + float yoffset; + float xscale; + float yscale; void AddOffset(u32 binding, u32 offset) { ASSERT(offset < 256 && binding < buf_offsets.size()); diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index f8878d442..f8b91a283 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -30,6 +30,8 @@ struct Profile { bool needs_manual_interpolation{}; bool needs_lds_barriers{}; u64 min_ssbo_alignment{}; + u32 max_viewport_width{}; + u32 max_viewport_height{}; }; } // namespace Shader diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 8a5e0a1a6..2bf5e3f0a 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -84,6 +84,7 @@ struct VertexRuntimeInfo { u32 num_outputs; std::array outputs; bool emulate_depth_negative_one_to_one{}; + bool clip_disable{}; // Domain AmdGpu::TessellationType tess_type; AmdGpu::TessellationTopology tess_topology; @@ -92,7 +93,8 @@ struct VertexRuntimeInfo { bool operator==(const VertexRuntimeInfo& other) const noexcept { return emulate_depth_negative_one_to_one == other.emulate_depth_negative_one_to_one && - tess_type == other.tess_type && tess_topology == other.tess_topology && + clip_disable == other.clip_disable && tess_type == other.tess_type && + tess_topology == other.tess_topology && tess_partitioning == other.tess_partitioning && hs_output_cp_stride == other.hs_output_cp_stride; } diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h index f696c1f72..9a20f94c1 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -42,13 +42,14 @@ struct GraphicsPipelineKey { vk::Format stencil_format; struct { + bool clip_disable : 1; bool depth_test_enable : 1; bool depth_write_enable : 1; bool depth_bounds_test_enable : 1; bool depth_bias_enable : 1; bool stencil_test_enable : 1; // Must be named to be zero-initialized. - u8 _unused : 3; + u8 _unused : 2; }; vk::CompareOp depth_compare_op; @@ -94,6 +95,10 @@ public: return key.mrt_mask; } + auto IsClipDisabled() const { + return key.clip_disable; + } + [[nodiscard]] bool IsPrimitiveListTopology() const { return key.prim_type == AmdGpu::PrimitiveType::PointList || key.prim_type == AmdGpu::PrimitiveType::LineList || diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 1600600b9..d9577a612 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -208,6 +208,7 @@ std::string Instance::GetDriverVersionName() { bool Instance::CreateDevice() { const vk::StructureChain feature_chain = physical_device.getFeatures2< vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, + vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT, vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT, vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, vk::PhysicalDeviceCustomBorderColorFeaturesEXT, @@ -317,6 +318,9 @@ bool Instance::CreateDevice() { .pQueuePriorities = queue_priorities.data(), }; + const auto topology_list_restart_features = + feature_chain.get(); + const auto vk12_features = feature_chain.get(); vk::StructureChain device_chain = { vk::DeviceCreateInfo{ @@ -406,6 +410,8 @@ bool Instance::CreateDevice() { }, vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT{ .primitiveTopologyListRestart = true, + .primitiveTopologyPatchListRestart = + topology_list_restart_features.primitiveTopologyPatchListRestart, }, vk::PhysicalDeviceFragmentShaderBarycentricFeaturesKHR{ .fragmentShaderBarycentric = true, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 1b0c276dc..8c4752c3f 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -279,6 +279,14 @@ public: return min_imported_host_pointer_alignment; } + u32 GetMaxViewportWidth() const { + return properties.limits.maxViewportDimensions[0]; + } + + u32 GetMaxViewportHeight() const { + return properties.limits.maxViewportDimensions[1]; + } + /// Returns the sample count flags supported by framebuffers. vk::SampleCountFlags GetFramebufferSampleCounts() const { return properties.limits.framebufferColorSampleCounts & diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 86f6dcc7a..c6a56745d 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -125,6 +125,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS info.vs_info.emulate_depth_negative_one_to_one = !instance.IsDepthClipControlSupported() && regs.clipper_control.clip_space == Liverpool::ClipSpace::MinusWToW; + info.vs_info.clip_disable = graphics_key.clip_disable; if (l_stage == LogicalStage::TessellationEval) { info.vs_info.tess_type = regs.tess_config.type; info.vs_info.tess_topology = regs.tess_config.topology; @@ -210,6 +211,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary || instance.GetDriverID() == vk::DriverId::eMoltenvk, + .max_viewport_width = instance.GetMaxViewportWidth(), + .max_viewport_height = instance.GetMaxViewportHeight(), }; auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({}); ASSERT_MSG(cache_result == vk::Result::eSuccess, "Failed to create pipeline cache: {}", @@ -262,6 +265,8 @@ bool PipelineCache::RefreshGraphicsKey() { auto& regs = liverpool->regs; auto& key = graphics_key; + key.clip_disable = + regs.clipper_control.clip_disable || regs.primitive_type == AmdGpu::PrimitiveType::RectList; key.depth_test_enable = regs.depth_control.depth_enable; key.depth_write_enable = regs.depth_control.depth_write_enable && !regs.depth_render_control.depth_clear_enable; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index bac647125..07369c620 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -504,6 +504,17 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { } push_data.step0 = regs.vgt_instance_step_rate_0; push_data.step1 = regs.vgt_instance_step_rate_1; + + // TODO(roamic): add support for multiple viewports and geometry shaders when ViewportIndex + // is encountered and implemented in the recompiler. + if (stage->stage == Shader::Stage::Vertex) { + push_data.xoffset = + regs.viewport_control.xoffset_enable ? regs.viewports[0].xoffset : 0.f; + push_data.xscale = regs.viewport_control.xscale_enable ? regs.viewports[0].xscale : 1.f; + push_data.yoffset = + regs.viewport_control.yoffset_enable ? regs.viewports[0].yoffset : 0.f; + push_data.yscale = regs.viewport_control.yscale_enable ? regs.viewports[0].yscale : 1.f; + } stage->PushUd(binding, push_data); BindBuffers(*stage, binding, push_data, set_writes, buffer_barriers); @@ -1032,7 +1043,7 @@ void Rasterizer::UnmapMemory(VAddr addr, u64 size) { } void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { - UpdateViewportScissorState(); + UpdateViewportScissorState(pipeline); auto& regs = liverpool->regs; const auto cmdbuf = scheduler.CommandBuffer(); @@ -1112,7 +1123,7 @@ void Rasterizer::UpdateDynamicState(const GraphicsPipeline& pipeline) { } } -void Rasterizer::UpdateViewportScissorState() { +void Rasterizer::UpdateViewportScissorState(const GraphicsPipeline& pipeline) { const auto& regs = liverpool->regs; const auto combined_scissor_value_tl = [](s16 scr, s16 win, s16 gen, s16 win_offset) { @@ -1151,26 +1162,46 @@ void Rasterizer::UpdateViewportScissorState() { ? 1.0f : 0.0f; + if (regs.polygon_control.enable_window_offset) { + LOG_ERROR(Render_Vulkan, + "PA_SU_SC_MODE_CNTL.VTX_WINDOW_OFFSET_ENABLE support is not yet implemented."); + } + for (u32 i = 0; i < Liverpool::NumViewports; i++) { const auto& vp = regs.viewports[i]; const auto& vp_d = regs.viewport_depths[i]; if (vp.xscale == 0) { continue; } - const auto xoffset = vp_ctl.xoffset_enable ? vp.xoffset : 0.f; - const auto xscale = vp_ctl.xscale_enable ? vp.xscale : 1.f; - const auto yoffset = vp_ctl.yoffset_enable ? vp.yoffset : 0.f; - const auto yscale = vp_ctl.yscale_enable ? vp.yscale : 1.f; - const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; - const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; - viewports.push_back({ - .x = xoffset - xscale, - .y = yoffset - yscale, - .width = xscale * 2.0f, - .height = yscale * 2.0f, - .minDepth = zoffset - zscale * reduce_z, - .maxDepth = zscale + zoffset, - }); + + if (pipeline.IsClipDisabled()) { + // In case if clipping is disabled we patch the shader to convert vertex position + // from screen space coordinates to NDC by defining a render space as full hardware + // window range [0..16383, 0..16383] and setting the viewport to its size. + viewports.push_back({ + .x = 0.f, + .y = 0.f, + .width = float(std::min(instance.GetMaxViewportWidth(), 16_KB)), + .height = float(std::min(instance.GetMaxViewportHeight(), 16_KB)), + .minDepth = 0.0, + .maxDepth = 1.0, + }); + } else { + const auto xoffset = vp_ctl.xoffset_enable ? vp.xoffset : 0.f; + const auto xscale = vp_ctl.xscale_enable ? vp.xscale : 1.f; + const auto yoffset = vp_ctl.yoffset_enable ? vp.yoffset : 0.f; + const auto yscale = vp_ctl.yscale_enable ? vp.yscale : 1.f; + const auto zoffset = vp_ctl.zoffset_enable ? vp.zoffset : 0.f; + const auto zscale = vp_ctl.zscale_enable ? vp.zscale : 1.f; + viewports.push_back({ + .x = xoffset - xscale, + .y = yoffset - yscale, + .width = xscale * 2.0f, + .height = yscale * 2.0f, + .minDepth = zoffset - zscale * reduce_z, + .maxDepth = zscale + zoffset, + }); + } auto vp_scsr = scsr; if (regs.mode_control.vport_scissor_enable) { @@ -1192,8 +1223,8 @@ void Rasterizer::UpdateViewportScissorState() { if (viewports.empty()) { // Vulkan requires providing at least one viewport. constexpr vk::Viewport empty_viewport = { - .x = 0.0f, - .y = 0.0f, + .x = -1.0f, + .y = -1.0f, .width = 1.0f, .height = 1.0f, .minDepth = 0.0f, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index abf58e522..ed6cc7e71 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -76,7 +76,7 @@ private: void EliminateFastClear(); void UpdateDynamicState(const GraphicsPipeline& pipeline); - void UpdateViewportScissorState(); + void UpdateViewportScissorState(const GraphicsPipeline& pipeline); bool FilterDraw(); From 90b04e8cc0227a35aa5b01d6c361b4eaaefaee44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Sat, 18 Jan 2025 13:59:38 +0700 Subject: [PATCH 20/65] input: Don't use old input state in GameController::ReadState() (#2170) --- src/core/libraries/pad/pad.cpp | 33 +++-- src/input/controller.cpp | 146 ++++++++++---------- src/input/controller.h | 36 +++-- src/sdl_window.cpp | 234 +++++++++++++++++++++++++++------ src/sdl_window.h | 22 +++- 5 files changed, 340 insertions(+), 131 deletions(-) diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 7eb628a90..9a44f91f0 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -11,6 +11,8 @@ namespace Libraries::Pad { +using Input::GameController; + int PS4_SYSV_ABI scePadClose(s32 handle) { LOG_ERROR(Lib_Pad, "(STUBBED) called"); return ORBIS_OK; @@ -290,7 +292,8 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { int connected_count = 0; bool connected = false; Input::State states[64]; - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); int ret_num = controller->ReadStates(states, num, &connected, &connected_count); if (!connected) { @@ -311,9 +314,14 @@ int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) { pData[i].angularVelocity.x = states[i].angularVelocity.x; pData[i].angularVelocity.y = states[i].angularVelocity.y; pData[i].angularVelocity.z = states[i].angularVelocity.z; - Input::GameController::CalculateOrientation(pData[i].acceleration, pData[i].angularVelocity, - 1.0f / controller->accel_poll_rate, - pData[i].orientation); + if (engine) { + const auto accel_poll_rate = engine->GetAccelPollRate(); + if (accel_poll_rate != 0.0f) { + GameController::CalculateOrientation(pData[i].acceleration, + pData[i].angularVelocity, + 1.0f / accel_poll_rate, pData[i].orientation); + } + } pData[i].touchData.touchNum = (states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0); pData[i].touchData.touch[0].x = states[i].touchpad[0].x; @@ -356,7 +364,8 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { if (handle == ORBIS_PAD_ERROR_DEVICE_NO_HANDLE) { return ORBIS_PAD_ERROR_INVALID_HANDLE; } - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); + const auto* engine = controller->GetEngine(); int connectedCount = 0; bool isConnected = false; Input::State state; @@ -374,9 +383,13 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { pData->angularVelocity.x = state.angularVelocity.x; pData->angularVelocity.y = state.angularVelocity.y; pData->angularVelocity.z = state.angularVelocity.z; - Input::GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, - 1.0f / controller->accel_poll_rate, - pData->orientation); + if (engine) { + const auto accel_poll_rate = engine->GetAccelPollRate(); + if (accel_poll_rate != 0.0f) { + GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, + 1.0f / accel_poll_rate, pData->orientation); + } + } pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); pData->touchData.touch[0].x = state.touchpad[0].x; @@ -468,7 +481,7 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING; } - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b); return ORBIS_OK; } @@ -536,7 +549,7 @@ int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pP if (pParam != nullptr) { LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle, pParam->smallMotor, pParam->largeMotor); - auto* controller = Common::Singleton::Instance(); + auto* controller = Common::Singleton::Instance(); controller->SetVibration(pParam->smallMotor, pParam->largeMotor); return ORBIS_OK; } diff --git a/src/input/controller.cpp b/src/input/controller.cpp index eb43e6adf..71f0b0c09 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -10,6 +10,55 @@ namespace Input { +using Libraries::Pad::OrbisPadButtonDataOffset; + +void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) { + if (isPressed) { + buttonsState |= button; + } else { + buttonsState &= ~button; + } +} + +void State::OnAxis(Axis axis, int value) { + const auto toggle = [&](const auto button) { + if (value > 0) { + buttonsState |= button; + } else { + buttonsState &= ~button; + } + }; + switch (axis) { + case Axis::TriggerLeft: + toggle(OrbisPadButtonDataOffset::L2); + break; + case Axis::TriggerRight: + toggle(OrbisPadButtonDataOffset::R2); + break; + default: + break; + } + axes[static_cast(axis)] = value; +} + +void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) { + touchpad[touchIndex].state = isDown; + touchpad[touchIndex].x = static_cast(x * 1920); + touchpad[touchIndex].y = static_cast(y * 941); +} + +void State::OnGyro(const float gyro[3]) { + angularVelocity.x = gyro[0]; + angularVelocity.y = gyro[1]; + angularVelocity.z = gyro[2]; +} + +void State::OnAccel(const float accel[3]) { + acceleration.x = accel[0]; + acceleration.y = accel[1]; + acceleration.z = accel[2]; +} + GameController::GameController() { m_states_num = 0; m_last_state = State(); @@ -20,7 +69,7 @@ void GameController::ReadState(State* state, bool* isConnected, int* connectedCo *isConnected = m_connected; *connectedCount = m_connected_count; - *state = GetLastState(); + *state = m_engine && m_connected ? m_engine->ReadState() : GetLastState(); } int GameController::ReadStates(State* states, int states_num, bool* isConnected, @@ -75,45 +124,22 @@ void GameController::AddState(const State& state) { m_states_num++; } -void GameController::CheckButton(int id, Libraries::Pad::OrbisPadButtonDataOffset button, - bool is_pressed) { +void GameController::CheckButton(int id, OrbisPadButtonDataOffset button, bool is_pressed) { std::scoped_lock lock{m_mutex}; auto state = GetLastState(); + state.time = Libraries::Kernel::sceKernelGetProcessTime(); - if (is_pressed) { - state.buttonsState |= button; - } else { - state.buttonsState &= ~button; - } + state.OnButton(button, is_pressed); AddState(state); } void GameController::Axis(int id, Input::Axis axis, int value) { - using Libraries::Pad::OrbisPadButtonDataOffset; - std::scoped_lock lock{m_mutex}; auto state = GetLastState(); state.time = Libraries::Kernel::sceKernelGetProcessTime(); - int axis_id = static_cast(axis); - state.axes[axis_id] = value; - - if (axis == Input::Axis::TriggerLeft) { - if (value > 0) { - state.buttonsState |= OrbisPadButtonDataOffset::L2; - } else { - state.buttonsState &= ~OrbisPadButtonDataOffset::L2; - } - } - - if (axis == Input::Axis::TriggerRight) { - if (value > 0) { - state.buttonsState |= OrbisPadButtonDataOffset::R2; - } else { - state.buttonsState &= ~OrbisPadButtonDataOffset::R2; - } - } + state.OnAxis(axis, value); AddState(state); } @@ -124,9 +150,7 @@ void GameController::Gyro(int id, const float gyro[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the angular velocity (gyro data) - state.angularVelocity.x = gyro[0]; // X-axis - state.angularVelocity.y = gyro[1]; // Y-axis - state.angularVelocity.z = gyro[2]; // Z-axis + state.OnGyro(gyro); AddState(state); } @@ -136,9 +160,7 @@ void GameController::Acceleration(int id, const float acceleration[3]) { state.time = Libraries::Kernel::sceKernelGetProcessTime(); // Update the acceleration values - state.acceleration.x = acceleration[0]; // X-axis - state.acceleration.y = acceleration[1]; // Y-axis - state.acceleration.z = acceleration[2]; // Z-axis + state.OnAccel(acceleration); AddState(state); } @@ -211,62 +233,48 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler } void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) { - if (m_sdl_gamepad != nullptr) { - SDL_SetGamepadLED(m_sdl_gamepad, r, g, b); + if (!m_engine) { + return; } + std::scoped_lock _{m_mutex}; + m_engine->SetLightBarRGB(r, g, b); } -bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) { - if (m_sdl_gamepad != nullptr) { - return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF, - (largeMotor / 255.0f) * 0xFFFF, -1); +void GameController::SetVibration(u8 smallMotor, u8 largeMotor) { + if (!m_engine) { + return; } - return true; + std::scoped_lock _{m_mutex}; + m_engine->SetVibration(smallMotor, largeMotor); } void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) { if (touchIndex < 2) { std::scoped_lock lock{m_mutex}; auto state = GetLastState(); - state.time = Libraries::Kernel::sceKernelGetProcessTime(); - state.touchpad[touchIndex].state = touchDown; - state.touchpad[touchIndex].x = static_cast(x * 1920); - state.touchpad[touchIndex].y = static_cast(y * 941); + state.time = Libraries::Kernel::sceKernelGetProcessTime(); + state.OnTouchpad(touchIndex, touchDown, x, y); AddState(state); } } -void GameController::TryOpenSDLController() { - if (m_sdl_gamepad == nullptr || !SDL_GamepadConnected(m_sdl_gamepad)) { - int gamepad_count; - SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); - m_sdl_gamepad = gamepad_count > 0 ? SDL_OpenGamepad(gamepads[0]) : nullptr; - if (Config::getIsMotionControlsEnabled()) { - if (SDL_SetGamepadSensorEnabled(m_sdl_gamepad, SDL_SENSOR_GYRO, true)) { - gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_sdl_gamepad, SDL_SENSOR_GYRO); - LOG_INFO(Input, "Gyro initialized, poll rate: {}", gyro_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad"); - } - if (SDL_SetGamepadSensorEnabled(m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) { - accel_poll_rate = SDL_GetGamepadSensorDataRate(m_sdl_gamepad, SDL_SENSOR_ACCEL); - LOG_INFO(Input, "Accel initialized, poll rate: {}", accel_poll_rate); - } else { - LOG_ERROR(Input, "Failed to initialize accel controls for gamepad"); - } - } - - SDL_free(gamepads); - - SetLightBarRGB(0, 0, 255); +void GameController::SetEngine(std::unique_ptr engine) { + std::scoped_lock _{m_mutex}; + m_engine = std::move(engine); + if (m_engine) { + m_engine->Init(); } } +Engine* GameController::GetEngine() { + return m_engine.get(); +} + u32 GameController::Poll() { - std::scoped_lock lock{m_mutex}; if (m_connected) { + std::scoped_lock lock{m_mutex}; auto time = Libraries::Kernel::sceKernelGetProcessTime(); if (m_states_num == 0) { auto diff = (time - m_last_state.time) / 1000; diff --git a/src/input/controller.h b/src/input/controller.h index c6fc02c24..a45e71d77 100644 --- a/src/input/controller.h +++ b/src/input/controller.h @@ -3,12 +3,12 @@ #pragma once +#include +#include #include #include "common/types.h" #include "core/libraries/pad/pad.h" -struct SDL_Gamepad; - namespace Input { enum class Axis { @@ -28,7 +28,14 @@ struct TouchpadEntry { u16 y{}; }; -struct State { +class State { +public: + void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool); + void OnAxis(Axis, int); + void OnTouchpad(int touchIndex, bool isDown, float x, float y); + void OnGyro(const float[3]); + void OnAccel(const float[3]); + Libraries::Pad::OrbisPadButtonDataOffset buttonsState{}; u64 time = 0; int axes[static_cast(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0}; @@ -38,9 +45,19 @@ struct State { Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f}; }; +class Engine { +public: + virtual ~Engine() = default; + virtual void Init() = 0; + virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0; + virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0; + virtual State ReadState() = 0; + virtual float GetAccelPollRate() const = 0; + virtual float GetGyroPollRate() const = 0; +}; + inline int GetAxis(int min, int max, int value) { - int v = (255 * (value - min)) / (max - min); - return (v < 0 ? 0 : (v > 255 ? 255 : v)); + return std::clamp((255 * (value - min)) / (max - min), 0, 255); } constexpr u32 MAX_STATES = 64; @@ -59,13 +76,12 @@ public: void Gyro(int id, const float gyro[3]); void Acceleration(int id, const float acceleration[3]); void SetLightBarRGB(u8 r, u8 g, u8 b); - bool SetVibration(u8 smallMotor, u8 largeMotor); + void SetVibration(u8 smallMotor, u8 largeMotor); void SetTouchpadState(int touchIndex, bool touchDown, float x, float y); - void TryOpenSDLController(); + void SetEngine(std::unique_ptr); + Engine* GetEngine(); u32 Poll(); - float gyro_poll_rate; - float accel_poll_rate; static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration, Libraries::Pad::OrbisFVector3& angularVelocity, float deltaTime, @@ -85,7 +101,7 @@ private: std::array m_states; std::array m_private; - SDL_Gamepad* m_sdl_gamepad = nullptr; + std::unique_ptr m_engine = nullptr; }; } // namespace Input diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index b0126def2..d1fe6bbab 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/config.h" +#include "core/libraries/kernel/time.h" #include "core/libraries/pad/pad.h" #include "imgui/renderer/imgui_core.h" #include "input/controller.h" @@ -20,47 +21,200 @@ #include #endif +namespace Input { + +using Libraries::Pad::OrbisPadButtonDataOffset; + +static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { + using OPBDO = OrbisPadButtonDataOffset; + + switch (button) { + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return OPBDO::Down; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return OPBDO::Up; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return OPBDO::Left; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return OPBDO::Right; + case SDL_GAMEPAD_BUTTON_SOUTH: + return OPBDO::Cross; + case SDL_GAMEPAD_BUTTON_NORTH: + return OPBDO::Triangle; + case SDL_GAMEPAD_BUTTON_WEST: + return OPBDO::Square; + case SDL_GAMEPAD_BUTTON_EAST: + return OPBDO::Circle; + case SDL_GAMEPAD_BUTTON_START: + return OPBDO::Options; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_BACK: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return OPBDO::L1; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return OPBDO::R1; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return OPBDO::L3; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return OPBDO::R3; + default: + return OPBDO::None; + } +} + +static SDL_GamepadAxis InputAxisToSDL(Axis axis) { + switch (axis) { + case Axis::LeftX: + return SDL_GAMEPAD_AXIS_LEFTX; + case Axis::LeftY: + return SDL_GAMEPAD_AXIS_LEFTY; + case Axis::RightX: + return SDL_GAMEPAD_AXIS_RIGHTX; + case Axis::RightY: + return SDL_GAMEPAD_AXIS_RIGHTY; + case Axis::TriggerLeft: + return SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + case Axis::TriggerRight: + return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + default: + UNREACHABLE(); + } +} + +SDLInputEngine::~SDLInputEngine() { + if (m_gamepad) { + SDL_CloseGamepad(m_gamepad); + } +} + +void SDLInputEngine::Init() { + if (m_gamepad) { + SDL_CloseGamepad(m_gamepad); + m_gamepad = nullptr; + } + int gamepad_count; + SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); + if (!gamepads) { + LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); + return; + } + if (gamepad_count == 0) { + LOG_INFO(Input, "No gamepad found!"); + SDL_free(gamepads); + return; + } + LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); + if (!(m_gamepad = SDL_OpenGamepad(gamepads[0]))) { + LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); + SDL_free(gamepads); + return; + } + if (Config::getIsMotionControlsEnabled()) { + if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) { + m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO); + LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad"); + } + if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) { + m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL); + LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate); + } else { + LOG_ERROR(Input, "Failed to initialize accel controls for gamepad"); + }; + } + SDL_free(gamepads); + SetLightBarRGB(0, 0, 255); +} + +void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) { + if (m_gamepad) { + SDL_SetGamepadLED(m_gamepad, r, g, b); + } +} + +void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) { + if (m_gamepad) { + const auto low_freq = (smallMotor / 255.0f) * 0xFFFF; + const auto high_freq = (largeMotor / 255.0f) * 0xFFFF; + SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1); + } +} + +State SDLInputEngine::ReadState() { + State state{}; + state.time = Libraries::Kernel::sceKernelGetProcessTime(); + + // Buttons + for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) { + auto orbisButton = SDLGamepadToOrbisButton(i); + if (orbisButton == OrbisPadButtonDataOffset::None) { + continue; + } + state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i)); + } + + // Axes + for (int i = 0; i < static_cast(Axis::AxisMax); ++i) { + const auto axis = static_cast(i); + const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis)); + switch (axis) { + case Axis::TriggerLeft: + case Axis::TriggerRight: + state.OnAxis(axis, GetAxis(0, 0x8000, value)); + break; + default: + state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value)); + break; + } + } + + // Touchpad + if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) { + for (int finger = 0; finger < 2; ++finger) { + bool down; + float x, y; + if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) { + state.OnTouchpad(finger, down, x, y); + } + } + } + + // Gyro + if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) { + float gyro[3]; + if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) { + state.OnGyro(gyro); + } + } + + // Accel + if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) { + float accel[3]; + if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) { + state.OnAccel(accel); + } + } + + return state; +} + +float SDLInputEngine::GetGyroPollRate() const { + return m_gyro_poll_rate; +} + +float SDLInputEngine::GetAccelPollRate() const { + return m_accel_poll_rate; +} + +} // namespace Input + namespace Frontend { using namespace Libraries::Pad; -static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { - switch (button) { - case SDL_GAMEPAD_BUTTON_DPAD_DOWN: - return OrbisPadButtonDataOffset::Down; - case SDL_GAMEPAD_BUTTON_DPAD_UP: - return OrbisPadButtonDataOffset::Up; - case SDL_GAMEPAD_BUTTON_DPAD_LEFT: - return OrbisPadButtonDataOffset::Left; - case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: - return OrbisPadButtonDataOffset::Right; - case SDL_GAMEPAD_BUTTON_SOUTH: - return OrbisPadButtonDataOffset::Cross; - case SDL_GAMEPAD_BUTTON_NORTH: - return OrbisPadButtonDataOffset::Triangle; - case SDL_GAMEPAD_BUTTON_WEST: - return OrbisPadButtonDataOffset::Square; - case SDL_GAMEPAD_BUTTON_EAST: - return OrbisPadButtonDataOffset::Circle; - case SDL_GAMEPAD_BUTTON_START: - return OrbisPadButtonDataOffset::Options; - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - return OrbisPadButtonDataOffset::TouchPad; - case SDL_GAMEPAD_BUTTON_BACK: - return OrbisPadButtonDataOffset::TouchPad; - case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: - return OrbisPadButtonDataOffset::L1; - case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: - return OrbisPadButtonDataOffset::R1; - case SDL_GAMEPAD_BUTTON_LEFT_STICK: - return OrbisPadButtonDataOffset::L3; - case SDL_GAMEPAD_BUTTON_RIGHT_STICK: - return OrbisPadButtonDataOffset::R3; - default: - return OrbisPadButtonDataOffset::None; - } -} - static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) { auto* controller = reinterpret_cast(userdata); return controller->Poll(); @@ -112,7 +266,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_ SDL_SetWindowFullscreen(window, Config::getIsFullscreen()); SDL_InitSubSystem(SDL_INIT_GAMEPAD); - controller->TryOpenSDLController(); + controller->SetEngine(std::make_unique()); #if defined(SDL_PLATFORM_WIN32) window_info.type = WindowSystemType::Windows; @@ -422,7 +576,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { switch (event->type) { case SDL_EVENT_GAMEPAD_ADDED: case SDL_EVENT_GAMEPAD_REMOVED: - controller->TryOpenSDLController(); + controller->SetEngine(std::make_unique()); break; case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: @@ -433,7 +587,7 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) { break; case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: { - button = SDLGamepadToOrbisButton(event->gbutton.button); + button = Input::SDLGamepadToOrbisButton(event->gbutton.button); if (button == OrbisPadButtonDataOffset::None) { break; } diff --git a/src/sdl_window.h b/src/sdl_window.h index 78d4bbc39..3ab3c3613 100644 --- a/src/sdl_window.h +++ b/src/sdl_window.h @@ -5,14 +5,32 @@ #include #include "common/types.h" +#include "input/controller.h" struct SDL_Window; struct SDL_Gamepad; union SDL_Event; namespace Input { -class GameController; -} + +class SDLInputEngine : public Engine { +public: + ~SDLInputEngine() override; + void Init() override; + void SetLightBarRGB(u8 r, u8 g, u8 b) override; + void SetVibration(u8 smallMotor, u8 largeMotor) override; + float GetGyroPollRate() const override; + float GetAccelPollRate() const override; + State ReadState() override; + +private: + SDL_Gamepad* m_gamepad = nullptr; + + float m_gyro_poll_rate{}; + float m_accel_poll_rate{}; +}; + +} // namespace Input namespace Frontend { From 40385e13e7ca96f19e004a0c21b718d9bf701570 Mon Sep 17 00:00:00 2001 From: tomboylover93 <95257311+tomboylover93@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:08:20 -0800 Subject: [PATCH 21/65] qt: Improve user experience on Steam Deck and window managers (#2103) --- src/qt_gui/cheats_patches.cpp | 6 + src/qt_gui/settings_dialog.cpp | 16 - src/qt_gui/settings_dialog.ui | 629 ++++++++++++++++----------------- 3 files changed, 310 insertions(+), 341 deletions(-) diff --git a/src/qt_gui/cheats_patches.cpp b/src/qt_gui/cheats_patches.cpp index 2fea0b6ea..13157aa3a 100644 --- a/src/qt_gui/cheats_patches.cpp +++ b/src/qt_gui/cheats_patches.cpp @@ -188,8 +188,12 @@ void CheatsPatches::setupUI() { } }); + QPushButton* closeButton = new QPushButton(tr("Close")); + connect(closeButton, &QPushButton::clicked, [this]() { QWidget::close(); }); + controlLayout->addWidget(downloadButton); controlLayout->addWidget(deleteCheatButton); + controlLayout->addWidget(closeButton); cheatsLayout->addLayout(controlLayout); cheatsTab->setLayout(cheatsLayout); @@ -464,6 +468,8 @@ void CheatsPatches::onSaveButtonClicked() { } else { QMessageBox::information(this, tr("Success"), tr("Options saved successfully.")); } + + QWidget::close(); } QCheckBox* CheatsPatches::findCheckBoxByName(const QString& name) { diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index a4b584294..175c8c51d 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -522,22 +522,6 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { } else { ui->descriptionText->setText(defaultTextEdit); } - - // if the text exceeds the size of the box, it will increase the size - QRect currentGeometry = this->geometry(); - int newWidth = currentGeometry.width(); - - int documentHeight = ui->descriptionText->document()->size().height(); - int visibleHeight = ui->descriptionText->viewport()->height(); - if (documentHeight > visibleHeight) { - ui->descriptionText->setMaximumSize(16777215, 110); - this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, - currentGeometry.height() + 40); - } else { - ui->descriptionText->setMaximumSize(16777215, 70); - this->setGeometry(currentGeometry.x(), currentGeometry.y(), newWidth, - initialHeight); - } return true; } } diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 8d68d1c90..c084d4849 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -12,7 +12,7 @@ 0 0 970 - 750 + 820 @@ -68,7 +68,7 @@ 0 0 946 - 586 + 611 @@ -77,43 +77,6 @@ 0 - - - - - - System - - - - - - Console Language - - - - - - - - - - - - Emulator Language - - - - - - - - - - - - - @@ -217,246 +180,6 @@ - - - - 6 - - - QLayout::SizeConstraint::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Update - - - - 10 - - - 1 - - - 11 - - - 11 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Update Channel - - - - 7 - - - 11 - - - 11 - - - 11 - - - 11 - - - - - - 0 - 0 - - - - - Release - - - - - Nightly - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Check for Updates - - - - - - - - 0 - 0 - - - - - 11 - false - - - - Check for Updates at Startup - - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Game Compatibility - - - - 10 - - - 1 - - - 11 - - - - - Display Compatibility Data - - - - - - - Update Compatibility Database On Startup - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Update Compatibility Database - - - - - - - - @@ -627,6 +350,283 @@ + + + + + + System + + + + + + Console Language + + + + + + + + + + + + Emulator Language + + + + + + + + + + + + + + + + + 6 + + + QLayout::SizeConstraint::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Update + + + + 10 + + + 1 + + + 11 + + + 190 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Update Channel + + + + 7 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + + 0 + 0 + + + + + Release + + + + + Nightly + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Check for Updates + + + + + + + + 0 + 0 + + + + + 11 + false + + + + Check for Updates at Startup + + + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Game Compatibility + + + + 10 + + + 1 + + + 11 + + + + + Display Compatibility Data + + + + + + + Update Compatibility Database On Startup + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Update Compatibility Database + + + + + + + + @@ -645,12 +645,12 @@ 0 0 946 - 586 + 605 - + @@ -664,17 +664,14 @@ Cursor - - - 0 - + 11 11 - + true @@ -701,7 +698,7 @@ - + true @@ -836,7 +833,7 @@ true - + 0 0 @@ -872,6 +869,12 @@ true + + + 0 + 0 + + 0 @@ -885,23 +888,6 @@ - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - @@ -943,7 +929,7 @@ 0 0 946 - 586 + 605 @@ -1124,11 +1110,14 @@ + + true + Advanced - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop @@ -1194,7 +1183,7 @@ 0 0 946 - 586 + 605 @@ -1233,22 +1222,6 @@ - - - - Qt::Orientation::Horizontal - - - QSizePolicy::Policy::Preferred - - - - 40 - 20 - - - - @@ -1445,10 +1418,16 @@ + + + 0 + 0 + + 16777215 - 70 + 120 From 9a956f5ed00584cbdf70240826c01445ee083f1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Sat, 18 Jan 2025 14:08:45 +0700 Subject: [PATCH 22/65] renderer_vulkan: Clear blank frame (#2095) * renderer_vulkan: Clear blank frame Fix display of garbage images on startup on some drivers. * Remove duplicated attachment declarations * Remove duplicated rendering_info declarations --- .../renderer_vulkan/vk_presenter.cpp | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 0f45574bc..fcdb84676 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -602,6 +602,23 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) .pImageMemoryBarriers = &pre_barrier, }); + const std::array attachments = {vk::RenderingAttachmentInfo{ + .imageView = frame->image_view, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eClear, + .storeOp = vk::AttachmentStoreOp::eStore, + }}; + const vk::RenderingInfo rendering_info{ + .renderArea = + vk::Rect2D{ + .offset = {0, 0}, + .extent = {frame->width, frame->height}, + }, + .layerCount = 1, + .colorAttachmentCount = attachments.size(), + .pColorAttachments = attachments.data(), + }; + if (image_id != VideoCore::NULL_IMAGE_ID) { auto& image = texture_cache.GetImage(image_id); image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}, @@ -662,26 +679,13 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop) cmdbuf.pushConstants(*pp_pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(PostProcessSettings), &pp_settings); - const std::array attachments = {vk::RenderingAttachmentInfo{ - .imageView = frame->image_view, - .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, - .loadOp = vk::AttachmentLoadOp::eClear, - .storeOp = vk::AttachmentStoreOp::eStore, - }}; - - vk::RenderingInfo rendering_info{ - .renderArea = - vk::Rect2D{ - .offset = {0, 0}, - .extent = {frame->width, frame->height}, - }, - .layerCount = 1, - .colorAttachmentCount = attachments.size(), - .pColorAttachments = attachments.data(), - }; cmdbuf.beginRendering(rendering_info); cmdbuf.draw(3, 1, 0, 0); cmdbuf.endRendering(); + } else { + // Fix display of garbage images on startup on some drivers + cmdbuf.beginRendering(rendering_info); + cmdbuf.endRendering(); } const auto post_barrier = From 81ad575b2294224194a365d087b2a6240a0b9161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Sat, 18 Jan 2025 14:47:38 +0700 Subject: [PATCH 23/65] video_core: Use adaptive mutex on Linux (#2105) Fix performance regression with #1973 on SteamDeck --- src/common/adaptive_mutex.h | 27 ++++++++++++++++++++++ src/video_core/buffer_cache/word_manager.h | 7 ++++++ src/video_core/page_manager.h | 7 ++++++ 3 files changed, 41 insertions(+) create mode 100644 src/common/adaptive_mutex.h diff --git a/src/common/adaptive_mutex.h b/src/common/adaptive_mutex.h new file mode 100644 index 000000000..f174f5996 --- /dev/null +++ b/src/common/adaptive_mutex.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef __linux__ +#include +#endif + +namespace Common { + +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP +class AdaptiveMutex { +public: + void lock() { + pthread_mutex_lock(&mutex); + } + void unlock() { + pthread_mutex_unlock(&mutex); + } + +private: + pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; +}; +#endif // PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + +} // namespace Common diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 7ad33d7a6..5ad724f96 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -8,6 +8,9 @@ #include #include +#ifdef __linux__ +#include "common/adaptive_mutex.h" +#endif #include "common/spin_lock.h" #include "common/types.h" #include "video_core/page_manager.h" @@ -272,7 +275,11 @@ private: } } +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + Common::AdaptiveMutex lock; +#else Common::SpinLock lock; +#endif PageManager* tracker; VAddr cpu_addr = 0; WordsArray cpu; diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index f44307f92..f6bae9641 100644 --- a/src/video_core/page_manager.h +++ b/src/video_core/page_manager.h @@ -5,6 +5,9 @@ #include #include +#ifdef __linux__ +#include "common/adaptive_mutex.h" +#endif #include "common/spin_lock.h" #include "common/types.h" @@ -36,7 +39,11 @@ private: std::unique_ptr impl; Vulkan::Rasterizer* rasterizer; boost::icl::interval_map cached_pages; +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + Common::AdaptiveMutex lock; +#else Common::SpinLock lock; +#endif }; } // namespace VideoCore From 12364b197a87ec79cd476eb0e73d1015ef02cac0 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:13:16 -0800 Subject: [PATCH 24/65] renderer_vulkan: Remove swapchain image reinterpretation. (#2176) --- .../renderer_vulkan/vk_instance.cpp | 1 - .../renderer_vulkan/vk_presenter.cpp | 2 +- .../renderer_vulkan/vk_swapchain.cpp | 20 ++++--------------- src/video_core/renderer_vulkan/vk_swapchain.h | 15 -------------- 4 files changed, 5 insertions(+), 33 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d9577a612..15bd573ef 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -271,7 +271,6 @@ bool Instance::CreateDevice() { legacy_vertex_attributes = add_extension(VK_EXT_LEGACY_VERTEX_ATTRIBUTES_EXTENSION_NAME); image_load_store_lod = add_extension(VK_AMD_SHADER_IMAGE_LOAD_STORE_LOD_EXTENSION_NAME); amd_gcn_shader = add_extension(VK_AMD_GCN_SHADER_EXTENSION_NAME); - add_extension(VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME); // These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2 // with extensions. diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index fcdb84676..ce2e02a43 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -380,7 +380,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) { const vk::ImageViewCreateInfo view_info = { .image = frame->image, .viewType = vk::ImageViewType::e2D, - .format = swapchain.GetViewFormat(), + .format = format, .subresourceRange{ .aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 438fe30ce..5467a5733 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -17,7 +17,7 @@ Swapchain::Swapchain(const Instance& instance_, const Frontend::WindowSDL& windo FindPresentFormat(); Create(window.GetWidth(), window.GetHeight()); - ImGui::Core::Initialize(instance, window, image_count, view_format); + ImGui::Core::Initialize(instance, window, image_count, surface_format.format); } Swapchain::~Swapchain() { @@ -57,17 +57,7 @@ void Swapchain::Create(u32 width_, u32 height_) { const u32 queue_family_indices_count = exclusive ? 1u : 2u; const vk::SharingMode sharing_mode = exclusive ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent; - const vk::Format view_formats[2] = { - surface_format.format, - view_format, - }; - const vk::ImageFormatListCreateInfo format_list = { - .viewFormatCount = 2, - .pViewFormats = view_formats, - }; const vk::SwapchainCreateInfoKHR swapchain_info = { - .pNext = &format_list, - .flags = vk::SwapchainCreateFlagBitsKHR::eMutableFormat, .surface = surface, .minImageCount = image_count, .imageFormat = surface_format.format, @@ -157,22 +147,20 @@ void Swapchain::FindPresentFormat() { // If there is a single undefined surface format, the device doesn't care, so we'll just use // RGBA sRGB. if (formats[0].format == vk::Format::eUndefined) { - surface_format.format = vk::Format::eR8G8B8A8Srgb; + surface_format.format = vk::Format::eR8G8B8A8Unorm; surface_format.colorSpace = vk::ColorSpaceKHR::eSrgbNonlinear; - view_format = FormatToUnorm(surface_format.format); return; } // Try to find a suitable format. for (const vk::SurfaceFormatKHR& sformat : formats) { vk::Format format = sformat.format; - if (format != vk::Format::eR8G8B8A8Srgb && format != vk::Format::eB8G8R8A8Srgb) { + if (format != vk::Format::eR8G8B8A8Unorm && format != vk::Format::eB8G8R8A8Unorm) { continue; } surface_format.format = format; surface_format.colorSpace = sformat.colorSpace; - view_format = FormatToUnorm(surface_format.format); return; } @@ -274,7 +262,7 @@ void Swapchain::SetupImages() { auto [im_view_result, im_view] = device.createImageView(vk::ImageViewCreateInfo{ .image = images[i], .viewType = vk::ImageViewType::e2D, - .format = FormatToUnorm(surface_format.format), + .format = surface_format.format, .subresourceRange = { .aspectMask = vk::ImageAspectFlagBits::eColor, diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h index 9da75c758..f5cf9f0d2 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.h +++ b/src/video_core/renderer_vulkan/vk_swapchain.h @@ -17,17 +17,6 @@ namespace Vulkan { class Instance; class Scheduler; -inline vk::Format FormatToUnorm(vk::Format fmt) { - switch (fmt) { - case vk::Format::eR8G8B8A8Srgb: - return vk::Format::eR8G8B8A8Unorm; - case vk::Format::eB8G8R8A8Srgb: - return vk::Format::eB8G8R8A8Unorm; - default: - UNREACHABLE(); - } -} - class Swapchain { public: explicit Swapchain(const Instance& instance, const Frontend::WindowSDL& window); @@ -61,10 +50,6 @@ public: return surface_format; } - vk::Format GetViewFormat() const { - return view_format; - } - vk::SwapchainKHR GetHandle() const { return swapchain; } From d3615796186b44902599ca2a41c61dd0b73b3eb5 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:35:44 -0800 Subject: [PATCH 25/65] texture_cache: Fix image mip overlap. (#2177) --- src/video_core/renderer_vulkan/vk_rasterizer.cpp | 2 -- src/video_core/texture_cache/image_info.cpp | 2 +- src/video_core/texture_cache/image_view.cpp | 9 ++++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 07369c620..88b510eca 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -802,8 +802,6 @@ void Rasterizer::BeginRendering(const GraphicsPipeline& pipeline, RenderState& s const auto mip = view.info.range.base.level; state.width = std::min(state.width, std::max(image.info.size.width >> mip, 1u)); state.height = std::min(state.height, std::max(image.info.size.height >> mip, 1u)); - ASSERT(old_img.info.size.width == state.width); - ASSERT(old_img.info.size.height == state.height); } auto& image = texture_cache.GetImage(image_id); if (image.binding.force_general) { diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 07a0488f3..a9ed76960 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -219,7 +219,7 @@ int ImageInfo::IsMipOf(const ImageInfo& info) const { return -1; } - if (IsTilingCompatible(info.tiling_idx, tiling_idx)) { + if (!IsTilingCompatible(info.tiling_idx, tiling_idx)) { return -1; } diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index 6b1349386..7befb5259 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -114,9 +114,12 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info const auto view_aspect = aspect & vk::ImageAspectFlagBits::eDepth ? "Depth" : aspect & vk::ImageAspectFlagBits::eStencil ? "Stencil" : "Color"; - Vulkan::SetObjectName(instance.GetDevice(), *image_view, "ImageView {}x{}x{} {:#x}:{:#x} ({})", - image.info.size.width, image.info.size.height, image.info.size.depth, - image.info.guest_address, image.info.guest_size, view_aspect); + Vulkan::SetObjectName( + instance.GetDevice(), *image_view, "ImageView {}x{}x{} {:#x}:{:#x} {}:{} {}:{} ({})", + image.info.size.width, image.info.size.height, image.info.size.depth, + image.info.guest_address, image.info.guest_size, info.range.base.level, + info.range.base.level + info.range.extent.levels - 1, info.range.base.layer, + info.range.base.layer + info.range.extent.layers - 1, view_aspect); } ImageView::~ImageView() = default; From c80151addedf25f497ad1064f5b3ab36bdcca28d Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sat, 18 Jan 2025 02:29:19 -0800 Subject: [PATCH 26/65] vk_presenter: Fix splash issues. (#2180) --- src/video_core/renderer_vulkan/vk_presenter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index ce2e02a43..36d64a5d5 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -476,7 +476,7 @@ bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { if (!frame) { if (!splash_img.has_value()) { VideoCore::ImageInfo info{}; - info.pixel_format = vk::Format::eR8G8B8A8Srgb; + info.pixel_format = vk::Format::eR8G8B8A8Unorm; info.type = vk::ImageType::e2D; info.size = VideoCore::Extent3D{splash->GetImageInfo().width, splash->GetImageInfo().height, 1}; @@ -487,6 +487,7 @@ bool Presenter::ShowSplash(Frame* frame /*= nullptr*/) { splash->GetImageInfo().width, splash->GetImageInfo().height, 0); splash_img.emplace(instance, present_scheduler, info); + splash_img->flags &= ~VideoCore::GpuDirty; texture_cache.RefreshImage(*splash_img); splash_img->Transit(vk::ImageLayout::eTransferSrcOptimal, From 1ea5f8f09243b958ff64d879116c89dd24a627ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Sat, 18 Jan 2025 17:48:39 +0700 Subject: [PATCH 27/65] input: Unbroke KBM-only input (#2179) --- src/input/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/controller.cpp b/src/input/controller.cpp index 71f0b0c09..ae54553f4 100644 --- a/src/input/controller.cpp +++ b/src/input/controller.cpp @@ -69,7 +69,7 @@ void GameController::ReadState(State* state, bool* isConnected, int* connectedCo *isConnected = m_connected; *connectedCount = m_connected_count; - *state = m_engine && m_connected ? m_engine->ReadState() : GetLastState(); + *state = GetLastState(); } int GameController::ReadStates(State* states, int states_num, bool* isConnected, From 3b92cd1c1a68f18b3f68afb58860d3c2e828aff2 Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Sat, 18 Jan 2025 13:21:08 +0100 Subject: [PATCH 28/65] CLI: Add argument to pass an argument to the game (#2135) --- src/core/linker.cpp | 10 ++++++++-- src/core/linker.h | 4 ++-- src/emulator.cpp | 13 +++++++++++-- src/emulator.h | 2 +- src/main.cpp | 21 ++++++++++++++++++++- src/qt_gui/main.cpp | 20 +++++++++++++++++++- 6 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/core/linker.cpp b/src/core/linker.cpp index 28d2eea7b..2461edcb2 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -52,7 +52,7 @@ Linker::Linker() : memory{Memory::Instance()} {} Linker::~Linker() = default; -void Linker::Execute() { +void Linker::Execute(const std::vector args) { if (Config::debugDump()) { DebugDump(); } @@ -101,7 +101,7 @@ void Linker::Execute() { memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2); - main_thread.Run([this, module](std::stop_token) { + main_thread.Run([this, module, args](std::stop_token) { Common::SetCurrentThreadName("GAME_MainThread"); LoadSharedLibraries(); @@ -109,6 +109,12 @@ void Linker::Execute() { EntryParams params{}; params.argc = 1; params.argv[0] = "eboot.bin"; + if (!args.empty()) { + params.argc = args.size() + 1; + for (int i = 0; i < args.size() && i < 32; i++) { + params.argv[i + 1] = args[i].c_str(); + } + } params.entry_addr = module->GetEntryAddress(); RunMainEntry(¶ms); }); diff --git a/src/core/linker.h b/src/core/linker.h index 7ef13ae56..00da3a08c 100644 --- a/src/core/linker.h +++ b/src/core/linker.h @@ -49,7 +49,7 @@ class Linker; struct EntryParams { int argc; u32 padding; - const char* argv[3]; + const char* argv[33]; VAddr entry_addr; }; @@ -143,7 +143,7 @@ public: void Relocate(Module* module); bool Resolve(const std::string& name, Loader::SymbolType type, Module* module, Loader::SymbolRecord* return_info); - void Execute(); + void Execute(const std::vector args = {}); void DebugDump(); private: diff --git a/src/emulator.cpp b/src/emulator.cpp index 61d6d3862..e77c2b87f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -99,7 +99,7 @@ Emulator::~Emulator() { Config::saveMainWindow(config_dir / "config.toml"); } -void Emulator::Run(const std::filesystem::path& file) { +void Emulator::Run(const std::filesystem::path& file, const std::vector args) { // Applications expect to be run from /app0 so mount the file's parent path as app0. auto* mnt = Common::Singleton::Instance(); const auto game_folder = file.parent_path(); @@ -152,6 +152,15 @@ void Emulator::Run(const std::filesystem::path& file) { if (const auto raw_attributes = param_sfo->GetInteger("ATTRIBUTE")) { psf_attributes.raw = *raw_attributes; } + if (!args.empty()) { + int argc = std::min(args.size(), 32); + for (int i = 0; i < argc; i++) { + LOG_INFO(Loader, "Game argument {}: {}", i, args[i]); + } + if (args.size() > 32) { + LOG_ERROR(Loader, "Too many game arguments, only passing the first 32"); + } + } } const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png"); @@ -239,7 +248,7 @@ void Emulator::Run(const std::filesystem::path& file) { } #endif - linker->Execute(); + linker->Execute(args); window->InitTimers(); while (window->IsOpen()) { diff --git a/src/emulator.h b/src/emulator.h index a08ab43c3..08c2807a1 100644 --- a/src/emulator.h +++ b/src/emulator.h @@ -25,7 +25,7 @@ public: Emulator(); ~Emulator(); - void Run(const std::filesystem::path& file); + void Run(const std::filesystem::path& file, const std::vector args = {}); void UpdatePlayTime(const std::string& serial); private: diff --git a/src/main.cpp b/src/main.cpp index 54772870c..fad3b1f53 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,6 +29,7 @@ int main(int argc, char* argv[]) { bool has_game_argument = false; std::string game_path; + std::vector game_args{}; // Map of argument strings to lambda functions std::unordered_map> arg_map = { @@ -37,6 +38,9 @@ int main(int argc, char* argv[]) { std::cout << "Usage: shadps4 [options] \n" "Options:\n" " -g, --game Specify game path to launch\n" + " -- ... Parameters passed to the game ELF. " + "Needs to be at the end of the line, and everything after \"--\" is a " + "game argument.\n" " -p, --patch Apply specified patch file\n" " -f, --fullscreen Specify window initial fullscreen " "state. Does not overwrite the config file.\n" @@ -126,6 +130,21 @@ int main(int argc, char* argv[]) { // Assume the last argument is the game file if not specified via -g/--game game_path = argv[i]; has_game_argument = true; + } else if (std::string(argv[i]) == "--") { + if (i + 1 == argc) { + std::cerr << "Warning: -- is set, but no game arguments are added!\n"; + break; + } + for (int j = i + 1; j < argc; j++) { + game_args.push_back(argv[j]); + } + break; + } else if (i + 1 < argc && std::string(argv[i + 1]) == "--") { + if (!has_game_argument) { + game_path = argv[i]; + has_game_argument = true; + } + break; } else { std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; return 1; @@ -166,7 +185,7 @@ int main(int argc, char* argv[]) { // Run the emulator with the resolved eboot path Core::Emulator emulator; - emulator.Run(eboot_path); + emulator.Run(eboot_path, game_args); return 0; } diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 2d524e199..8babadc35 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -33,6 +33,7 @@ int main(int argc, char* argv[]) { bool has_command_line_argument = argc > 1; bool show_gui = false, has_game_argument = false; std::string game_path; + std::vector game_args{}; // Map of argument strings to lambda functions std::unordered_map> arg_map = { @@ -43,6 +44,9 @@ int main(int argc, char* argv[]) { " No arguments: Opens the GUI.\n" " -g, --game Specify or " " to launch\n" + " -- ... Parameters passed to the game ELF. " + "Needs to be at the end of the line, and everything after \"--\" is a " + "game argument.\n" " -p, --patch Apply specified patch file\n" " -s, --show-gui Show the GUI\n" " -f, --fullscreen Specify window initial fullscreen " @@ -131,6 +135,20 @@ int main(int argc, char* argv[]) { // Assume the last argument is the game file if not specified via -g/--game game_path = argv[i]; has_game_argument = true; + } else if (std::string(argv[i]) == "--") { + if (i + 1 == argc) { + std::cerr << "Warning: -- is set, but no game arguments are added!\n"; + break; + } + for (int j = i + 1; j < argc; j++) { + game_args.push_back(argv[j]); + } + break; + } else if (i + 1 < argc && std::string(argv[i + 1]) == "--") { + if (!has_game_argument) { + game_path = argv[i]; + has_game_argument = true; + } } else { std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n"; return 1; @@ -181,7 +199,7 @@ int main(int argc, char* argv[]) { // Run the emulator with the resolved game path Core::Emulator emulator; - emulator.Run(game_file_path.string()); + emulator.Run(game_file_path.string(), game_args); if (!show_gui) { return 0; // Exit after running the emulator without showing the GUI } From 388548ba470aa68c5fff4e5737e03031e5783f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A5IGA?= <164882787+Xphalnos@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:02:02 +0100 Subject: [PATCH 29/65] pad: Configurable DeadZone (#2030) --- src/common/config.cpp | 14 ++++++++++++++ src/common/config.h | 2 ++ src/core/libraries/pad/pad.cpp | 8 ++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 9c842f8b7..ed9af5a72 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -45,6 +45,8 @@ static std::string logFilter; static std::string logType = "async"; static std::string userName = "shadPS4"; static std::string updateChannel; +static u16 deadZoneLeft = 2.0; +static u16 deadZoneRight = 2.0; static std::string backButtonBehavior = "left"; static bool useSpecialPad = false; static int specialPadClass = 1; @@ -140,6 +142,14 @@ bool getEnableDiscordRPC() { return enableDiscordRPC; } +u16 leftDeadZone() { + return deadZoneLeft; +} + +u16 rightDeadZone() { + return deadZoneRight; +} + s16 getCursorState() { return cursorState; } @@ -620,6 +630,8 @@ void load(const std::filesystem::path& path) { if (data.contains("Input")) { const toml::value& input = data.at("Input"); + deadZoneLeft = toml::find_or(input, "deadZoneLeft", 2.0); + deadZoneRight = toml::find_or(input, "deadZoneRight", 2.0); cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left"); @@ -739,6 +751,8 @@ void save(const std::filesystem::path& path) { data["General"]["separateUpdateEnabled"] = separateupdatefolder; data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; + data["Input"]["deadZoneLeft"] = deadZoneLeft; + data["Input"]["deadZoneRight"] = deadZoneRight; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["backButtonBehavior"] = backButtonBehavior; diff --git a/src/common/config.h b/src/common/config.h index f9e4c2815..cb56f99c7 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -35,6 +35,8 @@ std::string getLogType(); std::string getUserName(); std::string getUpdateChannel(); +u16 leftDeadZone(); +u16 rightDeadZone(); s16 getCursorState(); int getCursorHideTimeout(); std::string getBackButtonBehavior(); diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 9a44f91f0..f2b81fbe0 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -95,8 +95,8 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; - pInfo->stickInfo.deadZoneLeft = 2; - pInfo->stickInfo.deadZoneRight = 2; + pInfo->stickInfo.deadZoneLeft = Config::leftDeadZone(); + pInfo->stickInfo.deadZoneRight = Config::rightDeadZone(); pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; pInfo->connectedCount = 1; pInfo->connected = false; @@ -106,8 +106,8 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn pInfo->touchPadInfo.pixelDensity = 1; pInfo->touchPadInfo.resolution.x = 1920; pInfo->touchPadInfo.resolution.y = 950; - pInfo->stickInfo.deadZoneLeft = 2; - pInfo->stickInfo.deadZoneRight = 2; + pInfo->stickInfo.deadZoneLeft = Config::leftDeadZone(); + pInfo->stickInfo.deadZoneRight = Config::rightDeadZone(); pInfo->connectionType = ORBIS_PAD_PORT_TYPE_STANDARD; pInfo->connectedCount = 1; pInfo->connected = true; From 269ce126149700ba340d7014a04faa215ddf64f9 Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Sat, 18 Jan 2025 16:54:06 +0300 Subject: [PATCH 30/65] fix build on arch --- src/video_core/renderer_vulkan/vk_instance.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 15bd573ef..5efdf4127 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -566,7 +566,8 @@ void Instance::CollectToolingInfo() { return; } for (const vk::PhysicalDeviceToolProperties& tool : tools) { - LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", tool.name); + const std::string_view name = tool.name; + LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); } } From 746f2e091d3aa1c56cf56e0a16c858d01eaf31cd Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 19 Jan 2025 03:06:31 -0800 Subject: [PATCH 31/65] tile: Account for thickness in micro tiled size calculation. (#2185) --- src/video_core/texture_cache/image_info.cpp | 10 ++++++---- src/video_core/texture_cache/tile.h | 10 +++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index a9ed76960..8068aae2f 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -166,6 +166,7 @@ void ImageInfo::UpdateSize() { mip_w = std::max(mip_w, 1u); mip_h = std::max(mip_h, 1u); auto mip_d = std::max(size.depth >> mip, 1u); + auto thickness = 1; if (props.is_pow2) { mip_w = std::bit_ceil(mip_w); @@ -181,12 +182,13 @@ void ImageInfo::UpdateSize() { break; } case AmdGpu::TilingMode::Texture_Volume: - mip_d += (-mip_d) & 3u; + thickness = 4; + mip_d += (-mip_d) & (thickness - 1); [[fallthrough]]; case AmdGpu::TilingMode::Display_MicroTiled: case AmdGpu::TilingMode::Texture_MicroTiled: { std::tie(mip_info.pitch, mip_info.size) = - ImageSizeMicroTiled(mip_w, mip_h, bpp, num_samples); + ImageSizeMicroTiled(mip_w, mip_h, bpp, thickness, num_samples); mip_info.height = std::max(mip_h, 8u); if (props.is_block) { mip_info.pitch = std::max(mip_info.pitch * 4, 32u); @@ -198,8 +200,8 @@ void ImageInfo::UpdateSize() { case AmdGpu::TilingMode::Texture_MacroTiled: case AmdGpu::TilingMode::Depth_MacroTiled: { ASSERT(!props.is_block); - std::tie(mip_info.pitch, mip_info.size) = - ImageSizeMacroTiled(mip_w, mip_h, bpp, num_samples, tiling_idx, mip, alt_tile); + std::tie(mip_info.pitch, mip_info.size) = ImageSizeMacroTiled( + mip_w, mip_h, thickness, bpp, num_samples, tiling_idx, mip, alt_tile); break; } default: { diff --git a/src/video_core/texture_cache/tile.h b/src/video_core/texture_cache/tile.h index 532bf3d88..c111e6aca 100644 --- a/src/video_core/texture_cache/tile.h +++ b/src/video_core/texture_cache/tile.h @@ -308,20 +308,20 @@ constexpr std::pair ImageSizeLinearAligned(u32 pitch, u32 height, u return {pitch_aligned, (log_sz * bpp + 7) / 8}; } -constexpr std::pair ImageSizeMicroTiled(u32 pitch, u32 height, u32 bpp, +constexpr std::pair ImageSizeMicroTiled(u32 pitch, u32 height, u32 thickness, u32 bpp, u32 num_samples) { const auto& [pitch_align, height_align] = micro_tile_extent; auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); const auto height_aligned = (height + height_align - 1) & ~(height_align - 1); - size_t log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; + size_t log_sz = (pitch_aligned * height_aligned * bpp * num_samples * thickness + 7) / 8; while (log_sz % 256) { - pitch_aligned += 8; + pitch_aligned += pitch_align; log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; } return {pitch_aligned, log_sz}; } -constexpr std::pair ImageSizeMacroTiled(u32 pitch, u32 height, u32 bpp, +constexpr std::pair ImageSizeMacroTiled(u32 pitch, u32 height, u32 thickness, u32 bpp, u32 num_samples, u32 tiling_idx, u32 mip_n, bool alt) { const auto& [pitch_align, height_align] = @@ -335,7 +335,7 @@ constexpr std::pair ImageSizeMacroTiled(u32 pitch, u32 height, u32 } if (downgrade_to_micro) { - return ImageSizeMicroTiled(pitch, height, bpp, num_samples); + return ImageSizeMicroTiled(pitch, height, thickness, bpp, num_samples); } const auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); From ec0dfb32b532fe2ddc28b3a1e65343e71cfcd92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Sun, 19 Jan 2025 19:03:15 +0700 Subject: [PATCH 32/65] Some ImGui tweaks for the game window (#2183) * Remove window border * Remove window rounding * Set background color to black --- src/video_core/renderer_vulkan/vk_presenter.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_presenter.cpp b/src/video_core/renderer_vulkan/vk_presenter.cpp index 36d64a5d5..35ab4318a 100644 --- a/src/video_core/renderer_vulkan/vk_presenter.cpp +++ b/src/video_core/renderer_vulkan/vk_presenter.cpp @@ -825,6 +825,9 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { { // Draw the game ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f}); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); ImGui::SetNextWindowDockID(dockId, ImGuiCond_Once); ImGui::Begin("Display##game_display", nullptr, ImGuiWindowFlags_NoNav); @@ -840,7 +843,8 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) { static_cast(imgRect.extent.height), }); ImGui::End(); - ImGui::PopStyleVar(); + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(); } ImGui::Core::Render(cmdbuf, swapchain_image_view, swapchain.GetExtent()); From a7d45231b77633361862ef82cfafeb4d2a4669ac Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sun, 19 Jan 2025 15:44:57 +0200 Subject: [PATCH 33/65] Filesystem devices (#2184) * added dummy devices * More WIP * added urandom,srandom,random,console,deci_tty6 devices * small fix * macOS fix --- CMakeLists.txt | 10 ++++ src/core/devices/deci_tty6.cpp | 66 +++++++++++++++++++++ src/core/devices/deci_tty6.h | 33 +++++++++++ src/core/devices/dev_console.cpp | 67 +++++++++++++++++++++ src/core/devices/dev_console.h | 33 +++++++++++ src/core/devices/random.cpp | 68 ++++++++++++++++++++++ src/core/devices/random.h | 33 +++++++++++ src/core/devices/srandom.cpp | 69 ++++++++++++++++++++++ src/core/devices/srandom.h | 33 +++++++++++ src/core/devices/urandom.cpp | 71 +++++++++++++++++++++++ src/core/devices/urandom.h | 33 +++++++++++ src/core/libraries/kernel/file_system.cpp | 33 ++++------- 12 files changed, 527 insertions(+), 22 deletions(-) create mode 100644 src/core/devices/deci_tty6.cpp create mode 100644 src/core/devices/deci_tty6.h create mode 100644 src/core/devices/dev_console.cpp create mode 100644 src/core/devices/dev_console.h create mode 100644 src/core/devices/random.cpp create mode 100644 src/core/devices/random.h create mode 100644 src/core/devices/srandom.cpp create mode 100644 src/core/devices/srandom.h create mode 100644 src/core/devices/urandom.cpp create mode 100644 src/core/devices/urandom.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 30cb033ed..e5c16bd1b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -557,6 +557,16 @@ set(CORE src/core/aerolib/stubs.cpp src/core/devices/logger.cpp src/core/devices/logger.h src/core/devices/nop_device.h + src/core/devices/dev_console.cpp + src/core/devices/dev_console.h + src/core/devices/deci_tty6.cpp + src/core/devices/deci_tty6.h + src/core/devices/random.cpp + src/core/devices/random.h + src/core/devices/urandom.cpp + src/core/devices/urandom.h + src/core/devices/srandom.cpp + src/core/devices/srandom.h src/core/file_format/pfs.h src/core/file_format/pkg.cpp src/core/file_format/pkg.h diff --git a/src/core/devices/deci_tty6.cpp b/src/core/devices/deci_tty6.cpp new file mode 100644 index 000000000..20423de61 --- /dev/null +++ b/src/core/devices/deci_tty6.cpp @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "deci_tty6.h" + +namespace Core::Devices { +std::shared_ptr DeciTty6Device::Create(u32 handle, const char*, int, u16) { + return std::shared_ptr( + reinterpret_cast(new DeciTty6Device(handle))); +} +int DeciTty6Device::ioctl(u64 cmd, Common::VaCtx* args) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 DeciTty6Device::write(const void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t DeciTty6Device::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t DeciTty6Device::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 DeciTty6Device::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 DeciTty6Device::lseek(s64 offset, int whence) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 DeciTty6Device::read(void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int DeciTty6Device::fstat(Libraries::Kernel::OrbisKernelStat* sb) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s32 DeciTty6Device::fsync() { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int DeciTty6Device::ftruncate(s64 length) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int DeciTty6Device::getdents(void* buf, u32 nbytes, s64* basep) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 DeciTty6Device::pwrite(const void* buf, size_t nbytes, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/deci_tty6.h b/src/core/devices/deci_tty6.h new file mode 100644 index 000000000..71cbfba6b --- /dev/null +++ b/src/core/devices/deci_tty6.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "base_device.h" + +namespace Core::Devices { + +class DeciTty6Device final : BaseDevice { + u32 handle; + +public: + static std::shared_ptr Create(u32 handle, const char*, int, u16); + explicit DeciTty6Device(u32 handle) : handle(handle) {} + + ~DeciTty6Device() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override; + s64 write(const void* buf, size_t nbytes) override; + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override; + s64 lseek(s64 offset, int whence) override; + s64 read(void* buf, size_t nbytes) override; + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override; + s32 fsync() override; + int ftruncate(s64 length) override; + int getdents(void* buf, u32 nbytes, s64* basep) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; +}; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/dev_console.cpp b/src/core/devices/dev_console.cpp new file mode 100644 index 000000000..0ddcfd040 --- /dev/null +++ b/src/core/devices/dev_console.cpp @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "dev_console.h" + +namespace Core::Devices { +std::shared_ptr ConsoleDevice::Create(u32 handle, const char*, int, u16) { + return std::shared_ptr( + reinterpret_cast(new ConsoleDevice(handle))); +} +int ConsoleDevice::ioctl(u64 cmd, Common::VaCtx* args) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 ConsoleDevice::write(const void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t ConsoleDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t ConsoleDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 ConsoleDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 ConsoleDevice::lseek(s64 offset, int whence) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 ConsoleDevice::read(void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int ConsoleDevice::fstat(Libraries::Kernel::OrbisKernelStat* sb) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s32 ConsoleDevice::fsync() { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int ConsoleDevice::ftruncate(s64 length) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int ConsoleDevice::getdents(void* buf, u32 nbytes, s64* basep) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 ConsoleDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/dev_console.h b/src/core/devices/dev_console.h new file mode 100644 index 000000000..f280200e2 --- /dev/null +++ b/src/core/devices/dev_console.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "base_device.h" + +namespace Core::Devices { + +class ConsoleDevice final : BaseDevice { + u32 handle; + +public: + static std::shared_ptr Create(u32 handle, const char*, int, u16); + explicit ConsoleDevice(u32 handle) : handle(handle) {} + + ~ConsoleDevice() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override; + s64 write(const void* buf, size_t nbytes) override; + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override; + s64 lseek(s64 offset, int whence) override; + s64 read(void* buf, size_t nbytes) override; + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override; + s32 fsync() override; + int ftruncate(s64 length) override; + int getdents(void* buf, u32 nbytes, s64* basep) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; +}; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/random.cpp b/src/core/devices/random.cpp new file mode 100644 index 000000000..705449423 --- /dev/null +++ b/src/core/devices/random.cpp @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/logging/log.h" +#include "random.h" + +namespace Core::Devices { +std::shared_ptr RandomDevice::Create(u32 handle, const char*, int, u16) { + std::srand(std::time(nullptr)); + return std::shared_ptr( + reinterpret_cast(new RandomDevice(handle))); +} +int RandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 RandomDevice::write(const void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t RandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t RandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 RandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 RandomDevice::lseek(s64 offset, int whence) { + return 0; +} + +s64 RandomDevice::read(void* buf, size_t nbytes) { + auto rbuf = static_cast(buf); + for (size_t i = 0; i < nbytes; i++) + rbuf[i] = std::rand() & 0xFF; + return nbytes; +} + +int RandomDevice::fstat(Libraries::Kernel::OrbisKernelStat* sb) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s32 RandomDevice::fsync() { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int RandomDevice::ftruncate(s64 length) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int RandomDevice::getdents(void* buf, u32 nbytes, s64* basep) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 RandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/random.h b/src/core/devices/random.h new file mode 100644 index 000000000..3bbed1ca2 --- /dev/null +++ b/src/core/devices/random.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "base_device.h" + +namespace Core::Devices { + +class RandomDevice final : BaseDevice { + u32 handle; + +public: + static std::shared_ptr Create(u32 handle, const char*, int, u16); + explicit RandomDevice(u32 handle) : handle(handle) {} + + ~RandomDevice() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override; + s64 write(const void* buf, size_t nbytes) override; + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override; + s64 lseek(s64 offset, int whence) override; + s64 read(void* buf, size_t nbytes) override; + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override; + s32 fsync() override; + int ftruncate(s64 length) override; + int getdents(void* buf, u32 nbytes, s64* basep) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; +}; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/srandom.cpp b/src/core/devices/srandom.cpp new file mode 100644 index 000000000..ff80adeaf --- /dev/null +++ b/src/core/devices/srandom.cpp @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/logging/log.h" +#include "srandom.h" + +namespace Core::Devices { +std::shared_ptr SRandomDevice::Create(u32 handle, const char*, int, u16) { + std::srand(std::time(nullptr)); + return std::shared_ptr( + reinterpret_cast(new SRandomDevice(handle))); +} +int SRandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 SRandomDevice::write(const void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t SRandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t SRandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 SRandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 SRandomDevice::lseek(s64 offset, int whence) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 SRandomDevice::read(void* buf, size_t nbytes) { + auto rbuf = static_cast(buf); + for (size_t i = 0; i < nbytes; i++) + rbuf[i] = std::rand(); + return nbytes; +} + +int SRandomDevice::fstat(Libraries::Kernel::OrbisKernelStat* sb) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s32 SRandomDevice::fsync() { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return s32(); +} + +int SRandomDevice::ftruncate(s64 length) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int SRandomDevice::getdents(void* buf, u32 nbytes, s64* basep) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 SRandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/srandom.h b/src/core/devices/srandom.h new file mode 100644 index 000000000..3a3b02571 --- /dev/null +++ b/src/core/devices/srandom.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "base_device.h" + +namespace Core::Devices { + +class SRandomDevice final : BaseDevice { + u32 handle; + +public: + static std::shared_ptr Create(u32 handle, const char*, int, u16); + explicit SRandomDevice(u32 handle) : handle(handle) {} + + ~SRandomDevice() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override; + s64 write(const void* buf, size_t nbytes) override; + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override; + s64 lseek(s64 offset, int whence) override; + s64 read(void* buf, size_t nbytes) override; + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override; + s32 fsync() override; + int ftruncate(s64 length) override; + int getdents(void* buf, u32 nbytes, s64* basep) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; +}; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/urandom.cpp b/src/core/devices/urandom.cpp new file mode 100644 index 000000000..8917caea5 --- /dev/null +++ b/src/core/devices/urandom.cpp @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "common/logging/log.h" +#include "urandom.h" + +namespace Core::Devices { + +std::shared_ptr URandomDevice::Create(u32 handle, const char*, int, u16) { + std::srand(std::time(nullptr)); + return std::shared_ptr( + reinterpret_cast(new URandomDevice(handle))); +} + +int URandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 URandomDevice::write(const void* buf, size_t nbytes) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t URandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +size_t URandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 URandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +s64 URandomDevice::lseek(s64 offset, int whence) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 URandomDevice::read(void* buf, size_t nbytes) { + auto rbuf = static_cast(buf); + for (size_t i = 0; i < nbytes; i++) + rbuf[i] = std::rand() & 0xFF; + return nbytes; +} + +int URandomDevice::fstat(Libraries::Kernel::OrbisKernelStat* sb) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s32 URandomDevice::fsync() { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int URandomDevice::ftruncate(s64 length) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +int URandomDevice::getdents(void* buf, u32 nbytes, s64* basep) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} + +s64 URandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { + LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); + return 0; +} +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/urandom.h b/src/core/devices/urandom.h new file mode 100644 index 000000000..9370017d5 --- /dev/null +++ b/src/core/devices/urandom.h @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include "base_device.h" + +namespace Core::Devices { + +class URandomDevice final : BaseDevice { + u32 handle; + +public: + static std::shared_ptr Create(u32 handle, const char*, int, u16); + explicit URandomDevice(u32 handle) : handle(handle) {} + + ~URandomDevice() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override; + s64 write(const void* buf, size_t nbytes) override; + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override; + s64 lseek(s64 offset, int whence) override; + s64 read(void* buf, size_t nbytes) override; + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override; + s32 fsync() override; + int ftruncate(s64 length) override; + int getdents(void* buf, u32 nbytes, s64* basep) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; +}; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 2eb5d1621..ce91fe192 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -8,8 +8,13 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/singleton.h" +#include "core/devices/deci_tty6.h" +#include "core/devices/dev_console.h" #include "core/devices/logger.h" #include "core/devices/nop_device.h" +#include "core/devices/random.h" +#include "core/devices/srandom.h" +#include "core/devices/urandom.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/file_system.h" #include "core/libraries/kernel/orbis_error.h" @@ -41,6 +46,12 @@ static std::map available_device = { {"/dev/deci_stderr", GET_DEVICE_FD(2)}, {"/dev/null", GET_DEVICE_FD(0)}, // fd0 (stdin) is a nop device + + {"/dev/urandom", &D::URandomDevice::Create }, + {"/dev/random", &D::RandomDevice::Create }, + {"/dev/srandom", &D::SRandomDevice::Create }, + {"/dev/console", &D::ConsoleDevice::Create }, + {"/dev/deci_tty6",&D::DeciTty6Device::Create } // clang-format on }; @@ -67,17 +78,6 @@ int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { bool directory = (flags & ORBIS_KERNEL_O_DIRECTORY) != 0; std::string_view path{raw_path}; - - if (path == "/dev/console") { - return 2000; - } - if (path == "/dev/deci_tty6") { - return 2001; - } - if (path == "/dev/urandom") { - return 2003; - } - u32 handle = h->CreateHandle(); auto* file = h->GetFile(handle); @@ -167,9 +167,6 @@ int PS4_SYSV_ABI sceKernelClose(int d) { if (d < 3) { // d probably hold an error code return ORBIS_KERNEL_ERROR_EPERM; } - if (d == 2003) { // dev/urandom case - return ORBIS_OK; - } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { @@ -337,13 +334,6 @@ s64 PS4_SYSV_ABI posix_lseek(int d, s64 offset, int whence) { } s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) { - if (d == 2003) // dev urandom case - { - auto rbuf = static_cast(buf); - for (size_t i = 0; i < nbytes; i++) - rbuf[i] = std::rand() & 0xFF; - return nbytes; - } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { @@ -757,7 +747,6 @@ s32 PS4_SYSV_ABI sceKernelRename(const char* from, const char* to) { } void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { - std::srand(std::time(nullptr)); LIB_FUNCTION("1G3lF1Gg1k8", "libkernel", 1, "libkernel", 1, 1, sceKernelOpen); LIB_FUNCTION("wuCroIGjt2g", "libScePosix", 1, "libkernel", 1, 1, posix_open); LIB_FUNCTION("wuCroIGjt2g", "libkernel", 1, "libkernel", 1, 1, open); From c8bbecda2654e4cb0825cfce396942142ab3d19f Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 19 Jan 2025 12:45:24 -0300 Subject: [PATCH 34/65] Devtools: Close Button ( X ) (#2187) --- src/core/devtools/layer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index c652849e7..a6d99b49b 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -93,6 +93,12 @@ void L::DrawMenuBar() { } ImGui::EndMenu(); } + + SameLine(ImGui::GetWindowWidth() - 30.0f); + if (Button("X", ImVec2(25, 25))) { + DebugState.IsShowingDebugMenuBar() = false; + } + EndMainMenuBar(); } From 17ac63d23a4abd046854a852642bd093a28108d7 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 19 Jan 2025 12:47:40 -0300 Subject: [PATCH 35/65] Fix SurfaceFormat (#2188) --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 9695e127f..4ac8d5cc8 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -612,7 +612,7 @@ std::span SurfaceFormats() { vk::Format::eB5G6R5UnormPack16), // 1_5_5_5 CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format1_5_5_5, AmdGpu::NumberFormat::Unorm, - vk::Format::eR5G5B5A1UnormPack16), + vk::Format::eA1B5G5R5UnormPack16), // 5_5_5_1 - Remapped to 1_5_5_5. // 4_4_4_4 CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format4_4_4_4, AmdGpu::NumberFormat::Unorm, From 201f2817ca929d54460bfd5e18d3a23b0cad65c5 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 19 Jan 2025 18:55:27 -0300 Subject: [PATCH 36/65] Fix SurfaceFormat Format1_5_5_5 - Format5_5_5_1 (#2191) * Fix SurfaceFormat Format1_5_5_5 - again * Fix Format5_5_5_1 --- src/video_core/amdgpu/types.h | 11 +++++++++-- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/video_core/amdgpu/types.h b/src/video_core/amdgpu/types.h index 57f97418a..63e184cc5 100644 --- a/src/video_core/amdgpu/types.h +++ b/src/video_core/amdgpu/types.h @@ -283,8 +283,7 @@ inline CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizz result.a = swizzle.a; return result; } - case DataFormat::Format10_10_10_2: - case DataFormat::Format5_5_5_1: { + case DataFormat::Format10_10_10_2: { CompMapping result; result.r = swizzle.a; result.g = swizzle.b; @@ -292,6 +291,14 @@ inline CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizz result.a = swizzle.r; return result; } + case DataFormat::Format1_5_5_5: { + CompMapping result; + result.r = swizzle.b; + result.g = swizzle.g; + result.b = swizzle.r; + result.a = swizzle.a; + return result; + } default: return swizzle; } diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 4ac8d5cc8..35585edb7 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -612,8 +612,10 @@ std::span SurfaceFormats() { vk::Format::eB5G6R5UnormPack16), // 1_5_5_5 CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format1_5_5_5, AmdGpu::NumberFormat::Unorm, - vk::Format::eA1B5G5R5UnormPack16), - // 5_5_5_1 - Remapped to 1_5_5_5. + vk::Format::eA1R5G5B5UnormPack16), + // 5_5_5_1 + CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format5_5_5_1, AmdGpu::NumberFormat::Unorm, + vk::Format::eR5G5B5A1UnormPack16), // 4_4_4_4 CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format4_4_4_4, AmdGpu::NumberFormat::Unorm, vk::Format::eR4G4B4A4UnormPack16), From 80092b6367e1aa230fb6e72fb304a0828061d409 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Sun, 19 Jan 2025 20:09:10 -0300 Subject: [PATCH 37/65] Fix SurfaceFormat Format4_4_4_4 (#2193) * Fix SurfaceFormat Format4_4_4_4 Pac-Man 256 * add_extension --- src/video_core/renderer_vulkan/liverpool_to_vk.cpp | 2 +- src/video_core/renderer_vulkan/vk_instance.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp index 35585edb7..f2fbc6530 100644 --- a/src/video_core/renderer_vulkan/liverpool_to_vk.cpp +++ b/src/video_core/renderer_vulkan/liverpool_to_vk.cpp @@ -618,7 +618,7 @@ std::span SurfaceFormats() { vk::Format::eR5G5B5A1UnormPack16), // 4_4_4_4 CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format4_4_4_4, AmdGpu::NumberFormat::Unorm, - vk::Format::eR4G4B4A4UnormPack16), + vk::Format::eA4B4G4R4UnormPack16), // 8_24 // 24_8 // X24_8_32 diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 5efdf4127..d183d6b09 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -283,6 +283,7 @@ bool Instance::CreateDevice() { add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME); add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); + add_extension(VK_EXT_4444_FORMATS_EXTENSION_NAME); #ifdef __APPLE__ // Required by Vulkan spec if supported. From d14e57f6a8bf327db8a032382fcaefa0b441fd1c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:45:34 -0800 Subject: [PATCH 38/65] hotfix: Move some command buffer references down. Prevents references becoming stale due to stream buffer flushes. --- src/video_core/buffer_cache/buffer_cache.cpp | 2 +- src/video_core/texture_cache/texture_cache.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 487544a21..11ad0e96f 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -210,7 +210,6 @@ void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bo return; } scheduler.EndRendering(); - const auto cmdbuf = scheduler.CommandBuffer(); const Buffer* buffer = [&] { if (is_gds) { return &gds_buffer; @@ -218,6 +217,7 @@ void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bo const BufferId buffer_id = FindBuffer(address, num_bytes); return &slot_buffers[buffer_id]; }(); + const auto cmdbuf = scheduler.CommandBuffer(); const vk::BufferMemoryBarrier2 pre_barrier = { .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, .srcAccessMask = vk::AccessFlagBits2::eMemoryRead, diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a281b89c9..5947db864 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -545,12 +545,12 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule auto* sched_ptr = custom_scheduler ? custom_scheduler : &scheduler; sched_ptr->EndRendering(); - const auto cmdbuf = sched_ptr->CommandBuffer(); const VAddr image_addr = image.info.guest_address; const size_t image_size = image.info.guest_size; const auto [vk_buffer, buf_offset] = buffer_cache.ObtainViewBuffer(image_addr, image_size, is_gpu_dirty); + const auto cmdbuf = sched_ptr->CommandBuffer(); // The obtained buffer may be written by a shader so we need to emit a barrier to prevent RAW // hazard if (auto barrier = vk_buffer->GetBarrier(vk::AccessFlagBits2::eTransferRead, From 4fa501c8d525a6afa3ae36134b26e2dc8007a24e Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 19 Jan 2025 21:12:42 -0600 Subject: [PATCH 39/65] Additional libSceNpManager functions and cleanup (#2195) * Error return on sceNpGetAccountIdA Confirmed through hardware testing, this returns ORBIS_NP_ERROR_SIGNED_OUT on a signed out console. Parameters are based on fpPS4 code. * Fix compile * Move errors to separate file * Cleanup function headers Swaps user_ids to use our OrbisUserServiceUserId type, and fixes parameter names to align with our coding standards. * Add proper parameter checks * Implement sceNpGetAccountId This function takes an online_id, uses an NpManager function to get the user_id, then uses that user_id to perform the same internal functions as sceNpGetAccountIdA. * Implement sceNpHasSignedUp * Fix sceNpGetAccountId Further hardware testing shows that these always write 0 to account_id when failing. * Update np_manager.cpp --- src/core/libraries/np_manager/np_manager.cpp | 42 ++++++++++++++----- src/core/libraries/np_manager/np_manager.h | 14 +++---- .../libraries/np_manager/np_manager_error.h | 9 ++++ 3 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 src/core/libraries/np_manager/np_manager_error.h diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index e26c5a830..a60dcd86f 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -5,6 +5,7 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/np_manager/np_manager.h" +#include "core/libraries/np_manager/np_manager_error.h" #include "core/tls.h" namespace Libraries::NpManager { @@ -935,14 +936,22 @@ int PS4_SYSV_ABI sceNpGetAccountDateOfBirthA() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetAccountId() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id) { + LOG_DEBUG(Lib_NpManager, "called"); + if (online_id == nullptr || account_id == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + *account_id = 0; + return ORBIS_NP_ERROR_SIGNED_OUT; } -int PS4_SYSV_ABI sceNpGetAccountIdA() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account_id) { + LOG_DEBUG(Lib_NpManager, "user_id {}", user_id); + if (account_id == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + *account_id = 0; + return ORBIS_NP_ERROR_SIGNED_OUT; } int PS4_SYSV_ABI sceNpGetAccountLanguage() { @@ -972,6 +981,9 @@ int PS4_SYSV_ABI sceNpGetGamePresenceStatusA() { int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId user_id, OrbisNpId* np_id) { LOG_DEBUG(Lib_NpManager, "user_id {}", user_id); + if (np_id == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } return ORBIS_NP_ERROR_SIGNED_OUT; } @@ -980,8 +992,11 @@ int PS4_SYSV_ABI sceNpGetNpReachabilityState() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetOnlineId(s32 user_id, OrbisNpOnlineId* online_id) { +int PS4_SYSV_ABI sceNpGetOnlineId(OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id) { LOG_DEBUG(Lib_NpManager, "user_id {}", user_id); + if (online_id == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } return ORBIS_NP_ERROR_SIGNED_OUT; } @@ -995,7 +1010,10 @@ int PS4_SYSV_ABI sceNpGetParentalControlInfoA() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state) { +int PS4_SYSV_ABI sceNpGetState(OrbisUserServiceUserId user_id, OrbisNpState* state) { + if (state == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } *state = OrbisNpState::SignedOut; LOG_DEBUG(Lib_NpManager, "Signed out"); return ORBIS_OK; @@ -1011,8 +1029,12 @@ int PS4_SYSV_ABI sceNpGetUserIdByOnlineId() { return ORBIS_OK; } -int PS4_SYSV_ABI sceNpHasSignedUp() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpHasSignedUp(OrbisUserServiceUserId user_id, bool* has_signed_up) { + LOG_DEBUG(Lib_NpManager, "called"); + if (has_signed_up == nullptr) { + return ORBIS_NP_ERROR_INVALID_ARGUMENT; + } + *has_signed_up = false; return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 6ba588e5e..02a1a32f6 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -11,8 +11,6 @@ class SymbolsResolver; namespace Libraries::NpManager { -constexpr int ORBIS_NP_ERROR_SIGNED_OUT = 0x80550006; - enum class OrbisNpState : u32 { Unknown = 0, SignedOut, SignedIn }; using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(s32 userId, OrbisNpState state, @@ -220,22 +218,22 @@ int PS4_SYSV_ABI sceNpGetAccountCountry(); int PS4_SYSV_ABI sceNpGetAccountCountryA(); int PS4_SYSV_ABI sceNpGetAccountDateOfBirth(); int PS4_SYSV_ABI sceNpGetAccountDateOfBirthA(); -int PS4_SYSV_ABI sceNpGetAccountId(); -int PS4_SYSV_ABI sceNpGetAccountIdA(); +int PS4_SYSV_ABI sceNpGetAccountId(OrbisNpOnlineId* online_id, u64* account_id); +int PS4_SYSV_ABI sceNpGetAccountIdA(OrbisUserServiceUserId user_id, u64* account_id); int PS4_SYSV_ABI sceNpGetAccountLanguage(); int PS4_SYSV_ABI sceNpGetAccountLanguage2(); int PS4_SYSV_ABI sceNpGetAccountLanguageA(); int PS4_SYSV_ABI sceNpGetGamePresenceStatus(); int PS4_SYSV_ABI sceNpGetGamePresenceStatusA(); -int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId userId, OrbisNpId* npId); +int PS4_SYSV_ABI sceNpGetNpId(OrbisUserServiceUserId user_id, OrbisNpId* np_id); int PS4_SYSV_ABI sceNpGetNpReachabilityState(); -int PS4_SYSV_ABI sceNpGetOnlineId(s32 userId, OrbisNpOnlineId* onlineId); +int PS4_SYSV_ABI sceNpGetOnlineId(OrbisUserServiceUserId user_id, OrbisNpOnlineId* online_id); int PS4_SYSV_ABI sceNpGetParentalControlInfo(); int PS4_SYSV_ABI sceNpGetParentalControlInfoA(); -int PS4_SYSV_ABI sceNpGetState(s32 userId, OrbisNpState* state); +int PS4_SYSV_ABI sceNpGetState(OrbisUserServiceUserId user_id, OrbisNpState* state); int PS4_SYSV_ABI sceNpGetUserIdByAccountId(); int PS4_SYSV_ABI sceNpGetUserIdByOnlineId(); -int PS4_SYSV_ABI sceNpHasSignedUp(); +int PS4_SYSV_ABI sceNpHasSignedUp(OrbisUserServiceUserId user_id, bool* has_signed_up); int PS4_SYSV_ABI sceNpIdMapperAbortRequest(); int PS4_SYSV_ABI sceNpIdMapperAccountIdToNpId(); int PS4_SYSV_ABI sceNpIdMapperAccountIdToOnlineId(); diff --git a/src/core/libraries/np_manager/np_manager_error.h b/src/core/libraries/np_manager/np_manager_error.h new file mode 100644 index 000000000..4af0d08ef --- /dev/null +++ b/src/core/libraries/np_manager/np_manager_error.h @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_NP_ERROR_INVALID_ARGUMENT = 0x80550003; +constexpr int ORBIS_NP_ERROR_SIGNED_OUT = 0x80550006; \ No newline at end of file From 0f93edb377f50dcb462427cdda0eb9939f37de31 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Sun, 19 Jan 2025 21:20:51 -0600 Subject: [PATCH 40/65] Implement IMAGE_ATOMIC_SWAP (#2194) We already handle everything for this opcode in our IMAGE_ATOMIC function, so implementing this is fairly simple. Should improve Wipeout 3. --- src/shader_recompiler/frontend/format.cpp | 4 ++-- src/shader_recompiler/frontend/translate/vector_memory.cpp | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 9677be3e5..2fcac7c10 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -3420,8 +3420,8 @@ constexpr std::array InstructionFormatMIMG = {{ {InstClass::VectorMemImgUt, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, ScalarType::Uint32}, // 15 = IMAGE_ATOMIC_SWAP - {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, - ScalarType::Undefined}, + {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32, + ScalarType::Uint32}, // 16 = IMAGE_ATOMIC_CMPSWAP {InstClass::VectorMemImgNoSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, ScalarType::Undefined}, diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 8fa0f3f87..685785af1 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -107,6 +107,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return IMAGE_GET_RESINFO(inst); // Image atomic operations + case Opcode::IMAGE_ATOMIC_SWAP: + return IMAGE_ATOMIC(AtomicOp::Swap, inst); case Opcode::IMAGE_ATOMIC_ADD: return IMAGE_ATOMIC(AtomicOp::Add, inst); case Opcode::IMAGE_ATOMIC_SMIN: From e1132db197def956132f08870258a1f8c21e9c99 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:33:37 -0800 Subject: [PATCH 41/65] texture_cache: Prevent unregistered images from being tracked. (#2196) --- src/video_core/texture_cache/texture_cache.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 5947db864..798ecf030 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -643,6 +643,9 @@ void TextureCache::UnregisterImage(ImageId image_id) { void TextureCache::TrackImage(ImageId image_id) { auto& image = slot_images[image_id]; + if (!(image.flags & ImageFlagBits::Registered)) { + return; + } const auto image_begin = image.info.guest_address; const auto image_end = image.info.guest_address + image.info.guest_size; if (image_begin == image.track_addr && image_end == image.track_addr_end) { @@ -666,6 +669,9 @@ void TextureCache::TrackImage(ImageId image_id) { void TextureCache::TrackImageHead(ImageId image_id) { auto& image = slot_images[image_id]; + if (!(image.flags & ImageFlagBits::Registered)) { + return; + } const auto image_begin = image.info.guest_address; if (image_begin == image.track_addr) { return; @@ -678,6 +684,9 @@ void TextureCache::TrackImageHead(ImageId image_id) { void TextureCache::TrackImageTail(ImageId image_id) { auto& image = slot_images[image_id]; + if (!(image.flags & ImageFlagBits::Registered)) { + return; + } const auto image_end = image.info.guest_address + image.info.guest_size; if (image_end == image.track_addr_end) { return; From a3967ccdb43ebe5c8005dd35e72825497abbbb3d Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 20 Jan 2025 04:48:32 -0800 Subject: [PATCH 42/65] externals: Update vulkan-headers (#2197) --- CMakeLists.txt | 2 +- externals/vulkan-headers | 2 +- src/video_core/renderer_vulkan/vk_platform.cpp | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5c16bd1b..918a1acb7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,7 +120,7 @@ find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) find_package(toml11 4.2.0 CONFIG) find_package(tsl-robin-map 1.3.0 CONFIG) -find_package(VulkanHeaders 1.4.303 CONFIG) +find_package(VulkanHeaders 1.4.305 CONFIG) find_package(VulkanMemoryAllocator 3.1.0 CONFIG) find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) diff --git a/externals/vulkan-headers b/externals/vulkan-headers index 6a74a7d65..a03d2f6d5 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit 6a74a7d65cafa19e38ec116651436cce6efd5b2e +Subproject commit a03d2f6d5753b365d704d58161825890baad0755 diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index fdd590e9d..07ebfbda6 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -28,19 +28,19 @@ static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation"; static const char* const CRASH_DIAGNOSTIC_LAYER_NAME = "VK_LAYER_LUNARG_crash_diagnostic"; static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( - VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, - const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { + vk::DebugUtilsMessageSeverityFlagBitsEXT severity, vk::DebugUtilsMessageTypeFlagsEXT type, + const vk::DebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { Common::Log::Level level{}; switch (severity) { - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eError: level = Common::Log::Level::Error; break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning: level = Common::Log::Level::Info; break; - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: - case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo: + case vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose: level = Common::Log::Level::Debug; break; default: From 95a30b2b3e1aa4e20c3db632955cc67bbded0fb1 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:38:09 -0800 Subject: [PATCH 43/65] texture_cache: Lock when updating image. (#2198) --- src/video_core/texture_cache/texture_cache.cpp | 6 ++---- src/video_core/texture_cache/texture_cache.h | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 798ecf030..04711539c 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -60,15 +60,13 @@ void TextureCache::MarkAsMaybeDirty(ImageId image_id, Image& image) { void TextureCache::InvalidateMemory(VAddr addr, size_t size) { std::scoped_lock lock{mutex}; - const auto end = addr + size; const auto pages_start = PageManager::GetPageAddr(addr); const auto pages_end = PageManager::GetNextPageAddr(addr + size - 1); ForEachImageInRegion(pages_start, pages_end - pages_start, [&](ImageId image_id, Image& image) { const auto image_begin = image.info.guest_address; const auto image_end = image.info.guest_address + image.info.guest_size; - if (image_begin < end && addr < image_end) { - // Start or end of the modified region is in the image, or the image is entirely within - // the modified region, so the image was definitely accessed by this page fault. + if (image.Overlaps(addr, size)) { + // Modified region overlaps image, so the image was definitely accessed by this fault. // Untrack the image, so that the range is unprotected and the guest can write freely. image.flags |= ImageFlagBits::CpuDirty; UntrackImage(image_id); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 343a510e6..f262768ea 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -118,6 +118,7 @@ public: /// Updates image contents if it was modified by CPU. void UpdateImage(ImageId image_id, Vulkan::Scheduler* custom_scheduler = nullptr) { + std::scoped_lock lock{mutex}; Image& image = slot_images[image_id]; TrackImage(image_id); RefreshImage(image, custom_scheduler); From 3563b88d8c9b72474460233b13e4b28ac93e8bc1 Mon Sep 17 00:00:00 2001 From: polyproxy <47796739+polybiusproxy@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:28:39 +0100 Subject: [PATCH 44/65] hotfix: use logger device on stdin --- CMakeLists.txt | 20 +++++++++---------- src/core/devices/base_device.cpp | 4 ++-- src/core/devices/base_device.h | 4 ++-- .../{dev_console.cpp => console_device.cpp} | 13 +++++++++--- .../{dev_console.h => console_device.h} | 4 ++-- .../{deci_tty6.cpp => deci_tty6_device.cpp} | 14 ++++++++++--- .../{deci_tty6.h => deci_tty6_device.h} | 4 ++-- src/core/devices/ioccom.h | 4 ++-- src/core/devices/logger.cpp | 8 +++++--- src/core/devices/logger.h | 4 ++-- src/core/devices/nop_device.h | 17 +++++++++++++--- .../devices/{random.cpp => random_device.cpp} | 18 +++++++++++++---- .../devices/{random.h => random_device.h} | 4 ++-- .../{srandom.cpp => srandom_device.cpp} | 18 +++++++++++++---- .../devices/{srandom.h => srandom_device.h} | 4 ++-- .../{urandom.cpp => urandom_device.cpp} | 16 +++++++++++---- .../devices/{urandom.h => urandom_device.h} | 4 ++-- src/core/file_sys/fs.cpp | 2 +- src/core/libraries/kernel/file_system.cpp | 17 +++++----------- 19 files changed, 114 insertions(+), 65 deletions(-) rename src/core/devices/{dev_console.cpp => console_device.cpp} (92%) rename src/core/devices/{dev_console.h => console_device.h} (90%) rename src/core/devices/{deci_tty6.cpp => deci_tty6_device.cpp} (92%) rename src/core/devices/{deci_tty6.h => deci_tty6_device.h} (90%) rename src/core/devices/{random.cpp => random_device.cpp} (90%) rename src/core/devices/{random.h => random_device.h} (90%) rename src/core/devices/{srandom.cpp => srandom_device.cpp} (90%) rename src/core/devices/{srandom.h => srandom_device.h} (90%) rename src/core/devices/{urandom.cpp => urandom_device.cpp} (90%) rename src/core/devices/{urandom.h => urandom_device.h} (90%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 918a1acb7..131809c8e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -557,16 +557,16 @@ set(CORE src/core/aerolib/stubs.cpp src/core/devices/logger.cpp src/core/devices/logger.h src/core/devices/nop_device.h - src/core/devices/dev_console.cpp - src/core/devices/dev_console.h - src/core/devices/deci_tty6.cpp - src/core/devices/deci_tty6.h - src/core/devices/random.cpp - src/core/devices/random.h - src/core/devices/urandom.cpp - src/core/devices/urandom.h - src/core/devices/srandom.cpp - src/core/devices/srandom.h + src/core/devices/console_device.cpp + src/core/devices/console_device.h + src/core/devices/deci_tty6_device.cpp + src/core/devices/deci_tty6_device.h + src/core/devices/random_device.cpp + src/core/devices/random_device.h + src/core/devices/urandom_device.cpp + src/core/devices/urandom_device.h + src/core/devices/srandom_device.cpp + src/core/devices/srandom_device.h src/core/file_format/pfs.h src/core/file_format/pkg.cpp src/core/file_format/pkg.h diff --git a/src/core/devices/base_device.cpp b/src/core/devices/base_device.cpp index 4f91c81c7..fc2a98a29 100644 --- a/src/core/devices/base_device.cpp +++ b/src/core/devices/base_device.cpp @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "base_device.h" diff --git a/src/core/devices/base_device.h b/src/core/devices/base_device.h index 351af82b4..36614b8f4 100644 --- a/src/core/devices/base_device.h +++ b/src/core/devices/base_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/devices/dev_console.cpp b/src/core/devices/console_device.cpp similarity index 92% rename from src/core/devices/dev_console.cpp rename to src/core/devices/console_device.cpp index 0ddcfd040..f109cadb9 100644 --- a/src/core/devices/dev_console.cpp +++ b/src/core/devices/console_device.cpp @@ -1,34 +1,41 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" -#include "dev_console.h" +#include "console_device.h" namespace Core::Devices { + std::shared_ptr ConsoleDevice::Create(u32 handle, const char*, int, u16) { return std::shared_ptr( reinterpret_cast(new ConsoleDevice(handle))); } + int ConsoleDevice::ioctl(u64 cmd, Common::VaCtx* args) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 ConsoleDevice::write(const void* buf, size_t nbytes) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t ConsoleDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t ConsoleDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 ConsoleDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 ConsoleDevice::lseek(s64 offset, int whence) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; diff --git a/src/core/devices/dev_console.h b/src/core/devices/console_device.h similarity index 90% rename from src/core/devices/dev_console.h rename to src/core/devices/console_device.h index f280200e2..d4b590ba0 100644 --- a/src/core/devices/dev_console.h +++ b/src/core/devices/console_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include diff --git a/src/core/devices/deci_tty6.cpp b/src/core/devices/deci_tty6_device.cpp similarity index 92% rename from src/core/devices/deci_tty6.cpp rename to src/core/devices/deci_tty6_device.cpp index 20423de61..e7a5fd4fc 100644 --- a/src/core/devices/deci_tty6.cpp +++ b/src/core/devices/deci_tty6_device.cpp @@ -1,34 +1,41 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" -#include "deci_tty6.h" +#include "deci_tty6_device.h" namespace Core::Devices { + std::shared_ptr DeciTty6Device::Create(u32 handle, const char*, int, u16) { return std::shared_ptr( reinterpret_cast(new DeciTty6Device(handle))); } + int DeciTty6Device::ioctl(u64 cmd, Common::VaCtx* args) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 DeciTty6Device::write(const void* buf, size_t nbytes) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t DeciTty6Device::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t DeciTty6Device::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 DeciTty6Device::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 DeciTty6Device::lseek(s64 offset, int whence) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; @@ -63,4 +70,5 @@ s64 DeciTty6Device::pwrite(const void* buf, size_t nbytes, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + } // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/deci_tty6.h b/src/core/devices/deci_tty6_device.h similarity index 90% rename from src/core/devices/deci_tty6.h rename to src/core/devices/deci_tty6_device.h index 71cbfba6b..b8bd48556 100644 --- a/src/core/devices/deci_tty6.h +++ b/src/core/devices/deci_tty6_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include diff --git a/src/core/devices/ioccom.h b/src/core/devices/ioccom.h index 671ee33d4..2ded90bd8 100644 --- a/src/core/devices/ioccom.h +++ b/src/core/devices/ioccom.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/devices/logger.cpp b/src/core/devices/logger.cpp index 6f104509c..8dcb24a3b 100644 --- a/src/core/devices/logger.cpp +++ b/src/core/devices/logger.cpp @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" #include "core/libraries/kernel/file_system.h" @@ -17,10 +17,12 @@ s64 Logger::write(const void* buf, size_t nbytes) { } size_t Logger::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + size_t total_written = 0; for (int i = 0; i < iovcnt; i++) { log(static_cast(iov[i].iov_base), iov[i].iov_len); + total_written += iov[i].iov_len; } - return iovcnt; + return total_written; } s64 Logger::pwrite(const void* buf, size_t nbytes, u64 offset) { diff --git a/src/core/devices/logger.h b/src/core/devices/logger.h index bfb07f337..eef17bc4b 100644 --- a/src/core/devices/logger.h +++ b/src/core/devices/logger.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once diff --git a/src/core/devices/nop_device.h b/src/core/devices/nop_device.h index a75b92f1b..5518b1de1 100644 --- a/src/core/devices/nop_device.h +++ b/src/core/devices/nop_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "base_device.h" @@ -17,36 +17,47 @@ public: int ioctl(u64 cmd, Common::VaCtx* args) override { return 0; } + s64 write(const void* buf, size_t nbytes) override { return 0; } + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override { return 0; } + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override { - return 0; + return ORBIS_KERNEL_ERROR_EBADF; } + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override { return 0; } + s64 lseek(s64 offset, int whence) override { return 0; } + s64 read(void* buf, size_t nbytes) override { return 0; } + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override { return 0; } + s32 fsync() override { return 0; } + int ftruncate(s64 length) override { return 0; } + int getdents(void* buf, u32 nbytes, s64* basep) override { return 0; } + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override { return 0; } diff --git a/src/core/devices/random.cpp b/src/core/devices/random_device.cpp similarity index 90% rename from src/core/devices/random.cpp rename to src/core/devices/random_device.cpp index 705449423..50934e3b8 100644 --- a/src/core/devices/random.cpp +++ b/src/core/devices/random_device.cpp @@ -1,43 +1,52 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include #include "common/logging/log.h" -#include "random.h" +#include "random_device.h" namespace Core::Devices { + std::shared_ptr RandomDevice::Create(u32 handle, const char*, int, u16) { std::srand(std::time(nullptr)); return std::shared_ptr( reinterpret_cast(new RandomDevice(handle))); } + int RandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 RandomDevice::write(const void* buf, size_t nbytes) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t RandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t RandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 RandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 RandomDevice::lseek(s64 offset, int whence) { return 0; } s64 RandomDevice::read(void* buf, size_t nbytes) { auto rbuf = static_cast(buf); - for (size_t i = 0; i < nbytes; i++) + for (size_t i = 0; i < nbytes; i++) { rbuf[i] = std::rand() & 0xFF; + } return nbytes; } @@ -65,4 +74,5 @@ s64 RandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + } // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/random.h b/src/core/devices/random_device.h similarity index 90% rename from src/core/devices/random.h rename to src/core/devices/random_device.h index 3bbed1ca2..a5c8e9845 100644 --- a/src/core/devices/random.h +++ b/src/core/devices/random_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include diff --git a/src/core/devices/srandom.cpp b/src/core/devices/srandom_device.cpp similarity index 90% rename from src/core/devices/srandom.cpp rename to src/core/devices/srandom_device.cpp index ff80adeaf..ab78ddbe2 100644 --- a/src/core/devices/srandom.cpp +++ b/src/core/devices/srandom_device.cpp @@ -1,35 +1,43 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include #include "common/logging/log.h" -#include "srandom.h" +#include "srandom_device.h" namespace Core::Devices { + std::shared_ptr SRandomDevice::Create(u32 handle, const char*, int, u16) { std::srand(std::time(nullptr)); return std::shared_ptr( reinterpret_cast(new SRandomDevice(handle))); } + int SRandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 SRandomDevice::write(const void* buf, size_t nbytes) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t SRandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t SRandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 SRandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 SRandomDevice::lseek(s64 offset, int whence) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; @@ -37,8 +45,9 @@ s64 SRandomDevice::lseek(s64 offset, int whence) { s64 SRandomDevice::read(void* buf, size_t nbytes) { auto rbuf = static_cast(buf); - for (size_t i = 0; i < nbytes; i++) + for (size_t i = 0; i < nbytes; i++) { rbuf[i] = std::rand(); + } return nbytes; } @@ -66,4 +75,5 @@ s64 SRandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + } // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/srandom.h b/src/core/devices/srandom_device.h similarity index 90% rename from src/core/devices/srandom.h rename to src/core/devices/srandom_device.h index 3a3b02571..cd32f7289 100644 --- a/src/core/devices/srandom.h +++ b/src/core/devices/srandom_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include diff --git a/src/core/devices/urandom.cpp b/src/core/devices/urandom_device.cpp similarity index 90% rename from src/core/devices/urandom.cpp rename to src/core/devices/urandom_device.cpp index 8917caea5..c001aab83 100644 --- a/src/core/devices/urandom.cpp +++ b/src/core/devices/urandom_device.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + #include #include "common/logging/log.h" -#include "urandom.h" +#include "urandom_device.h" namespace Core::Devices { @@ -16,22 +17,27 @@ int URandomDevice::ioctl(u64 cmd, Common::VaCtx* args) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 URandomDevice::write(const void* buf, size_t nbytes) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t URandomDevice::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + size_t URandomDevice::readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 URandomDevice::preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + s64 URandomDevice::lseek(s64 offset, int whence) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; @@ -39,8 +45,9 @@ s64 URandomDevice::lseek(s64 offset, int whence) { s64 URandomDevice::read(void* buf, size_t nbytes) { auto rbuf = static_cast(buf); - for (size_t i = 0; i < nbytes; i++) + for (size_t i = 0; i < nbytes; i++) { rbuf[i] = std::rand() & 0xFF; + } return nbytes; } @@ -68,4 +75,5 @@ s64 URandomDevice::pwrite(const void* buf, size_t nbytes, u64 offset) { LOG_ERROR(Kernel_Pthread, "(STUBBED) called"); return 0; } + } // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/urandom.h b/src/core/devices/urandom_device.h similarity index 90% rename from src/core/devices/urandom.h rename to src/core/devices/urandom_device.h index 9370017d5..b8a854cc0 100644 --- a/src/core/devices/urandom.h +++ b/src/core/devices/urandom_device.h @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 7d456780b..8a191e666 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -233,7 +233,7 @@ void HandleTable::CreateStdHandles() { std::shared_ptr{reinterpret_cast(device)}; }; // order matters - setup("/dev/stdin", new Devices::NopDevice(0)); // stdin + setup("/dev/stdin", new Devices::Logger("stdin", false)); // stdin setup("/dev/stdout", new Devices::Logger("stdout", false)); // stdout setup("/dev/stderr", new Devices::Logger("stderr", true)); // stderr } diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index ce91fe192..b0f7fdafe 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -8,13 +8,13 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/singleton.h" -#include "core/devices/deci_tty6.h" -#include "core/devices/dev_console.h" +#include "core/devices/deci_tty6_device.h" +#include "core/devices/console_device.h" #include "core/devices/logger.h" #include "core/devices/nop_device.h" -#include "core/devices/random.h" -#include "core/devices/srandom.h" -#include "core/devices/urandom.h" +#include "core/devices/random_device.h" +#include "core/devices/srandom_device.h" +#include "core/devices/urandom_device.h" #include "core/file_sys/fs.h" #include "core/libraries/kernel/file_system.h" #include "core/libraries/kernel/orbis_error.h" @@ -270,13 +270,6 @@ size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) { } size_t PS4_SYSV_ABI _writev(int fd, const SceKernelIovec* iov, int iovcn) { - if (fd == 1) { - size_t total_written = 0; - for (int i = 0; i < iovcn; i++) { - total_written += ::fwrite(iov[i].iov_base, 1, iov[i].iov_len, stdout); - } - return total_written; - } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { From 84a341dce570c4a070d07bc023a757652247fb6f Mon Sep 17 00:00:00 2001 From: polyproxy <47796739+polybiusproxy@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:30:34 +0100 Subject: [PATCH 45/65] remove BADF return --- src/core/devices/nop_device.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/devices/nop_device.h b/src/core/devices/nop_device.h index 5518b1de1..da9a3fc82 100644 --- a/src/core/devices/nop_device.h +++ b/src/core/devices/nop_device.h @@ -27,7 +27,7 @@ public: } size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override { - return ORBIS_KERNEL_ERROR_EBADF; + return 0; } s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override { From 41b39428335025e65f9e707ed8d5a9a1b09ba942 Mon Sep 17 00:00:00 2001 From: polyproxy <47796739+polybiusproxy@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:34:05 +0100 Subject: [PATCH 46/65] clang-format --- src/core/file_sys/fs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 8a191e666..ec940503f 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -233,7 +233,7 @@ void HandleTable::CreateStdHandles() { std::shared_ptr{reinterpret_cast(device)}; }; // order matters - setup("/dev/stdin", new Devices::Logger("stdin", false)); // stdin + setup("/dev/stdin", new Devices::Logger("stdin", false)); // stdin setup("/dev/stdout", new Devices::Logger("stdout", false)); // stdout setup("/dev/stderr", new Devices::Logger("stderr", true)); // stderr } From 5c62a00134523c4d0b2f06e9b6c28c596631e748 Mon Sep 17 00:00:00 2001 From: polyproxy <47796739+polybiusproxy@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:14:05 +0100 Subject: [PATCH 47/65] `clang-format` (again) --- src/core/libraries/kernel/file_system.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index b0f7fdafe..0150c11f5 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -8,8 +8,8 @@ #include "common/logging/log.h" #include "common/scope_exit.h" #include "common/singleton.h" -#include "core/devices/deci_tty6_device.h" #include "core/devices/console_device.h" +#include "core/devices/deci_tty6_device.h" #include "core/devices/logger.h" #include "core/devices/nop_device.h" #include "core/devices/random_device.h" From 2a4798cfa60f430840855a88339c1f4b364df9a4 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:40:00 -0800 Subject: [PATCH 48/65] tile: Fix some tile thickness calculation errors. (#2203) * tile: Fix some tile thickness calculation errors. * tile: Do not pad mip height to tile height. --- src/video_core/texture_cache/image_info.cpp | 13 ++++++------- src/video_core/texture_cache/tile.h | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp index 8068aae2f..dd89be8aa 100644 --- a/src/video_core/texture_cache/image_info.cpp +++ b/src/video_core/texture_cache/image_info.cpp @@ -178,7 +178,6 @@ void ImageInfo::UpdateSize() { case AmdGpu::TilingMode::Display_Linear: { std::tie(mip_info.pitch, mip_info.size) = ImageSizeLinearAligned(mip_w, mip_h, bpp, num_samples); - mip_info.height = mip_h; break; } case AmdGpu::TilingMode::Texture_Volume: @@ -188,12 +187,7 @@ void ImageInfo::UpdateSize() { case AmdGpu::TilingMode::Display_MicroTiled: case AmdGpu::TilingMode::Texture_MicroTiled: { std::tie(mip_info.pitch, mip_info.size) = - ImageSizeMicroTiled(mip_w, mip_h, bpp, thickness, num_samples); - mip_info.height = std::max(mip_h, 8u); - if (props.is_block) { - mip_info.pitch = std::max(mip_info.pitch * 4, 32u); - mip_info.height = std::max(mip_info.height * 4, 32u); - } + ImageSizeMicroTiled(mip_w, mip_h, thickness, bpp, num_samples); break; } case AmdGpu::TilingMode::Display_MacroTiled: @@ -208,6 +202,11 @@ void ImageInfo::UpdateSize() { UNREACHABLE(); } } + mip_info.height = mip_h; + if (props.is_block) { + mip_info.pitch = std::max(mip_info.pitch * 4, 32u); + mip_info.height = std::max(mip_info.height * 4, 32u); + } mip_info.size *= mip_d; mip_info.offset = guest_size; mips_layout.emplace_back(mip_info); diff --git a/src/video_core/texture_cache/tile.h b/src/video_core/texture_cache/tile.h index c111e6aca..54938b801 100644 --- a/src/video_core/texture_cache/tile.h +++ b/src/video_core/texture_cache/tile.h @@ -313,8 +313,8 @@ constexpr std::pair ImageSizeMicroTiled(u32 pitch, u32 height, u32 const auto& [pitch_align, height_align] = micro_tile_extent; auto pitch_aligned = (pitch + pitch_align - 1) & ~(pitch_align - 1); const auto height_aligned = (height + height_align - 1) & ~(height_align - 1); - size_t log_sz = (pitch_aligned * height_aligned * bpp * num_samples * thickness + 7) / 8; - while (log_sz % 256) { + size_t log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; + while ((log_sz * thickness) % 256) { pitch_aligned += pitch_align; log_sz = (pitch_aligned * height_aligned * bpp * num_samples + 7) / 8; } From 78ae9613c5f0e40f30b5a5cea83fa368530f1c24 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 22 Jan 2025 04:07:43 -0600 Subject: [PATCH 49/65] Fix for address_space initialization on Windows (#2202) Should fix some `Region coalescing failed: Attempt to access invalid address.` crashes. Co-authored-by: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> --- src/core/address_space.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 2b7331cbd..e9fb8cfbc 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -67,28 +67,25 @@ struct AddressSpace::Impl { static constexpr size_t ReductionOnFail = 1_GB; static constexpr size_t MaxReductions = 10; - size_t reduction = 0; size_t virtual_size = SystemManagedSize + SystemReservedSize + UserSize; for (u32 i = 0; i < MaxReductions; i++) { - virtual_base = static_cast(VirtualAlloc2(process, NULL, virtual_size - reduction, + virtual_base = static_cast(VirtualAlloc2(process, NULL, virtual_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1)); if (virtual_base) { break; } - reduction += ReductionOnFail; + virtual_size -= ReductionOnFail; } ASSERT_MSG(virtual_base, "Unable to reserve virtual address space: {}", Common::GetLastErrorMsg()); - // Take the reduction off of the system managed area, and leave the others unchanged. - reduction = size_t(virtual_base - SYSTEM_MANAGED_MIN); - system_managed_base = virtual_base; - system_managed_size = SystemManagedSize - reduction; system_reserved_base = reinterpret_cast(SYSTEM_RESERVED_MIN); system_reserved_size = SystemReservedSize; + system_managed_base = virtual_base; + system_managed_size = system_reserved_base - virtual_base; user_base = reinterpret_cast(USER_MIN); - user_size = UserSize; + user_size = virtual_base + virtual_size - user_base; LOG_INFO(Kernel_Vmm, "System managed virtual memory region: {} - {}", fmt::ptr(system_managed_base), @@ -101,10 +98,8 @@ struct AddressSpace::Impl { // Initializer placeholder tracker const uintptr_t system_managed_addr = reinterpret_cast(system_managed_base); - const uintptr_t system_reserved_addr = reinterpret_cast(system_reserved_base); - const uintptr_t user_addr = reinterpret_cast(user_base); regions.emplace(system_managed_addr, - MemoryRegion{system_managed_addr, virtual_size - reduction, false}); + MemoryRegion{system_managed_addr, virtual_size, false}); // Allocate backing file that represents the total physical memory. backing_handle = From adbff4056fe5a98026c2239ecd9511cd1be90e0f Mon Sep 17 00:00:00 2001 From: f3d209 <45915273+rootexpm@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:10:35 +0000 Subject: [PATCH 50/65] Added ability to change save data path (#2199) * added ability to change save data path * get default save data path from fs * add copyright * formatting --- src/common/config.cpp | 15 +++ src/common/config.h | 2 + .../libraries/save_data/save_instance.cpp | 6 +- src/qt_gui/settings_dialog.cpp | 33 ++++- src/qt_gui/settings_dialog.ui | 121 +++++++++++------- src/qt_gui/translations/en.ts | 8 ++ 6 files changed, 132 insertions(+), 53 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index ed9af5a72..6e9db50ff 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -78,6 +78,7 @@ static std::string trophyKey; static bool load_game_size = true; std::vector settings_install_dirs = {}; std::filesystem::path settings_addon_install_dir = {}; +std::filesystem::path save_data_path = {}; u32 main_window_geometry_x = 400; u32 main_window_geometry_y = 400; u32 main_window_geometry_w = 1280; @@ -110,6 +111,13 @@ bool GetLoadGameSizeEnabled() { return load_game_size; } +std::filesystem::path GetSaveDataPath() { + if (save_data_path.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + } + return save_data_path; +} + void setLoadGameSizeEnabled(bool enable) { load_game_size = enable; } @@ -502,6 +510,10 @@ void setGameInstallDirs(const std::vector& settings_insta settings_install_dirs = settings_install_dirs_config; } +void setSaveDataPath(const std::filesystem::path& path) { + save_data_path = path; +} + u32 getMainWindowGeometryX() { return main_window_geometry_x; } @@ -690,6 +702,8 @@ void load(const std::filesystem::path& path) { addGameInstallDir(std::filesystem::path{dir}); } + save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); + settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); @@ -784,6 +798,7 @@ void save(const std::filesystem::path& path) { install_dirs.emplace_back(std::string{fmt::UTF(dirString.u8string()).data}); } data["GUI"]["installDirs"] = install_dirs; + data["GUI"]["saveDataPath"] = std::string{fmt::UTF(save_data_path.u8string()).data}; data["GUI"]["loadGameSizeEnabled"] = load_game_size; data["GUI"]["addonInstallDir"] = diff --git a/src/common/config.h b/src/common/config.h index cb56f99c7..564947f1e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -18,6 +18,7 @@ void saveMainWindow(const std::filesystem::path& path); std::string getTrophyKey(); void setTrophyKey(std::string key); bool GetLoadGameSizeEnabled(); +std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); bool getIsFullscreen(); std::string getFullscreenMode(); @@ -82,6 +83,7 @@ void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& settings_install_dirs_config); +void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index 99daf83cc..c2b7dca3c 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -47,15 +47,13 @@ namespace Libraries::SaveData { std::filesystem::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial) { - return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / - game_serial; + return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial; } std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial, std::string_view dir_name) { - return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir) / std::to_string(user_id) / - game_serial / dir_name; + return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name; } uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) { diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 175c8c51d..5695d6232 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -202,6 +202,21 @@ SettingsDialog::SettingsDialog(std::span physical_devices, delete selected_item; } }); + + connect(ui->browseButton, &QPushButton::clicked, this, [this]() { + const auto save_data_path = Config::GetSaveDataPath(); + QString initial_path; + Common::FS::PathToQString(initial_path, save_data_path); + + QString save_data_path_string = + QFileDialog::getExistingDirectory(this, tr("Directory to save data"), initial_path); + + auto file_path = Common::FS::PathFromQString(save_data_path_string); + if (!file_path.empty()) { + Config::setSaveDataPath(file_path); + ui->currentSaveDataPath->setText(save_data_path_string); + } + }); } // DEBUG TAB @@ -256,6 +271,10 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->addFolderButton->installEventFilter(this); ui->removeFolderButton->installEventFilter(this); + ui->saveDataGroupBox->installEventFilter(this); + ui->currentSaveDataPath->installEventFilter(this); + ui->browseButton->installEventFilter(this); + // Debug ui->debugDump->installEventFilter(this); ui->vkValidationCheckBox->installEventFilter(this); @@ -286,6 +305,11 @@ void SettingsDialog::LoadValuesFromConfig() { const QVector languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, 9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 30, 28}; + const auto save_data_path = Config::GetSaveDataPath(); + QString save_data_path_string; + Common::FS::PathToQString(save_data_path_string, save_data_path); + ui->currentSaveDataPath->setText(save_data_path_string); + ui->consoleLanguageComboBox->setCurrentIndex( std::distance(languageIndexes.begin(), std::find(languageIndexes.begin(), languageIndexes.end(), @@ -497,6 +521,13 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("removeFolderButton"); } + // Save Data + if (elementName == "saveDataGroupBox" || elementName == "currentSaveDataPath") { + text = tr("saveDataBox"); + } else if (elementName == "browseButton") { + text = tr("browseButton"); + } + // Debug if (elementName == "debugDump") { text = tr("debugDump"); @@ -603,4 +634,4 @@ void SettingsDialog::ResetInstallFolders() { } Config::setGameInstallDirs(settings_install_dirs_config); } -} +} \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index c084d4849..d72a56973 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -53,7 +53,7 @@ - 0 + 3 @@ -67,8 +67,8 @@ 0 0 - 946 - 611 + 771 + 606 @@ -644,8 +644,8 @@ 0 0 - 946 - 605 + 455 + 252 @@ -928,8 +928,8 @@ 0 0 - 946 - 605 + 579 + 194 @@ -1178,51 +1178,76 @@ Paths - - - 0 - 0 - 946 - 605 - - - + - - - - - Game Folders - - - - - - 0 - - + + + Game Folders + + + + + - - Add... - + + Add... + - - + + - - Remove - + + Remove + - - - - - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + - - - + + + + + + + + + + + Save Data Path + + + + + + + + true + + + + + + + Browse + + + + + + + @@ -1239,8 +1264,8 @@ 0 0 - 946 - 586 + 510 + 269 diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 9127df7e3..dc72d97c9 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -912,6 +912,14 @@ rdocCheckBox Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + + saveDataBox + Save Data Path:\nThe folder where game save data will be saved. + + + browseButton + Browse:\nBrowse for a folder to set as the save data path. + CheatsPatches From 2968cf5a99a1be2fe7f3f28dbc65fe16f488c8af Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:06:27 -0600 Subject: [PATCH 51/65] sceKernelVirtualQuery Fixes (#2204) --- src/core/memory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 619941000..f90d4e6aa 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -529,8 +529,8 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, info->is_flexible.Assign(vma.type == VMAType::Flexible); info->is_direct.Assign(vma.type == VMAType::Direct); info->is_stack.Assign(vma.type == VMAType::Stack); - info->is_pooled.Assign(vma.type == VMAType::PoolReserved); - info->is_committed.Assign(vma.type == VMAType::Pooled); + info->is_pooled.Assign(vma.type == VMAType::PoolReserved || vma.type == VMAType::Pooled); + info->is_committed.Assign(vma.IsMapped()); vma.name.copy(info->name.data(), std::min(info->name.size(), vma.name.size())); if (vma.type == VMAType::Direct) { const auto dmem_it = FindDmemArea(vma.phys_base); From b3bce086b30c148f25817f9df06abaedad244453 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Wed, 22 Jan 2025 12:08:49 -0300 Subject: [PATCH 52/65] devtools: fix ReleaseKeyboard assert being triggered if many shader editor windows exist (#2205) --- src/core/devtools/widget/shader_list.cpp | 65 +++++++++++++++--------- src/core/devtools/widget/shader_list.h | 9 ++-- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/core/devtools/widget/shader_list.cpp b/src/core/devtools/widget/shader_list.cpp index 97d01896d..0285db5a5 100644 --- a/src/core/devtools/widget/shader_list.cpp +++ b/src/core/devtools/widget/shader_list.cpp @@ -24,16 +24,33 @@ using namespace ImGui; namespace Core::Devtools::Widget { -ShaderList::Selection::Selection(int index) : index(index) { - isa_editor.SetPalette(TextEditor::GetDarkPalette()); - isa_editor.SetReadOnly(true); - glsl_editor.SetPalette(TextEditor::GetDarkPalette()); - glsl_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::GLSL()); +ShaderList::Selection::Selection(int index) + : index(index), isa_editor(std::make_unique()), + glsl_editor(std::make_unique()) { + isa_editor->SetPalette(TextEditor::GetDarkPalette()); + isa_editor->SetReadOnly(true); + glsl_editor->SetPalette(TextEditor::GetDarkPalette()); + glsl_editor->SetLanguageDefinition(TextEditor::LanguageDefinition::GLSL()); presenter->GetWindow().RequestKeyboard(); } ShaderList::Selection::~Selection() { - presenter->GetWindow().ReleaseKeyboard(); + if (index >= 0) { + presenter->GetWindow().ReleaseKeyboard(); + } +} + +ShaderList::Selection::Selection(Selection&& other) noexcept + : index{other.index}, isa_editor{std::move(other.isa_editor)}, + glsl_editor{std::move(other.glsl_editor)}, open{other.open}, showing_bin{other.showing_bin}, + patch_path{std::move(other.patch_path)}, patch_bin_path{std::move(other.patch_bin_path)} { + other.index = -1; +} + +ShaderList::Selection& ShaderList::Selection::operator=(Selection other) { + using std::swap; + swap(*this, other); + return *this; } void ShaderList::Selection::ReloadShader(DebugStateType::ShaderDump& value) { @@ -72,13 +89,13 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { value.is_patched = !value.patch_spv.empty(); if (!value.is_patched) { // No patch - isa_editor.SetText(value.cache_isa_disasm); - glsl_editor.SetText(value.cache_spv_disasm); + isa_editor->SetText(value.cache_isa_disasm); + glsl_editor->SetText(value.cache_spv_disasm); } else { - isa_editor.SetText(value.cache_patch_disasm); - isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); - glsl_editor.SetText(value.patch_source); - glsl_editor.SetReadOnly(false); + isa_editor->SetText(value.cache_patch_disasm); + isa_editor->SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); + glsl_editor->SetText(value.patch_source); + glsl_editor->SetReadOnly(false); } } @@ -97,18 +114,18 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { if (value.patch_source.empty()) { value.patch_source = value.cache_spv_disasm; } - isa_editor.SetText(value.cache_patch_disasm); - isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); - glsl_editor.SetText(value.patch_source); - glsl_editor.SetReadOnly(false); + isa_editor->SetText(value.cache_patch_disasm); + isa_editor->SetLanguageDefinition(TextEditor::LanguageDefinition::SPIRV()); + glsl_editor->SetText(value.patch_source); + glsl_editor->SetReadOnly(false); if (!value.patch_spv.empty()) { ReloadShader(value); } } else { - isa_editor.SetText(value.cache_isa_disasm); - isa_editor.SetLanguageDefinition(TextEditor::LanguageDefinition()); - glsl_editor.SetText(value.cache_spv_disasm); - glsl_editor.SetReadOnly(true); + isa_editor->SetText(value.cache_isa_disasm); + isa_editor->SetLanguageDefinition(TextEditor::LanguageDefinition()); + glsl_editor->SetText(value.cache_spv_disasm); + glsl_editor->SetReadOnly(true); ReloadShader(value); } } @@ -154,7 +171,7 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { compile = true; } if (save) { - value.patch_source = glsl_editor.GetText(); + value.patch_source = glsl_editor->GetText(); std::ofstream file{patch_path, std::ios::binary | std::ios::trunc}; file << value.patch_source; std::string msg = "Patch saved to "; @@ -192,7 +209,7 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { DebugState.ShowDebugMessage("Decompilation failed (Compile was ok):\n" + res); } else { - isa_editor.SetText(value.cache_patch_disasm); + isa_editor->SetText(value.cache_patch_disasm); ReloadShader(value); } } @@ -201,9 +218,9 @@ bool ShaderList::Selection::DrawShader(DebugStateType::ShaderDump& value) { } if (showing_bin) { - isa_editor.Render(value.is_patched ? "SPIRV" : "ISA", GetContentRegionAvail()); + isa_editor->Render(value.is_patched ? "SPIRV" : "ISA", GetContentRegionAvail()); } else { - glsl_editor.Render("GLSL", GetContentRegionAvail()); + glsl_editor->Render("GLSL", GetContentRegionAvail()); } End(); diff --git a/src/core/devtools/widget/shader_list.h b/src/core/devtools/widget/shader_list.h index fbb8d2070..c882b0964 100644 --- a/src/core/devtools/widget/shader_list.h +++ b/src/core/devtools/widget/shader_list.h @@ -14,14 +14,17 @@ class ShaderList { struct Selection { explicit Selection(int index); ~Selection(); + Selection(const Selection& other) = delete; + Selection(Selection&& other) noexcept; + Selection& operator=(Selection other); void ReloadShader(DebugStateType::ShaderDump& value); bool DrawShader(DebugStateType::ShaderDump& value); - int index; - TextEditor isa_editor{}; - TextEditor glsl_editor{}; + int index{-1}; + std::unique_ptr isa_editor{}; + std::unique_ptr glsl_editor{}; bool open = true; bool showing_bin = false; From e584444aa38a6f1191f622ef1b8db7617c0549c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E9=A4=85=E3=81=AECreeeper?= <56744841+creeper-0910@users.noreply.github.com> Date: Thu, 23 Jan 2025 06:52:27 +0900 Subject: [PATCH 53/65] Update Japanese translation (#2209) * Update Japanese translation * Update Japanese translation * Update Japanese translation --- src/qt_gui/translations/ja_JP.ts | 164 +++++++++++++++---------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index e07d4eb25..bb9488a6b 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -19,7 +19,7 @@ This software should not be used to play games you have not legally obtained. - このソフトウェアは、合法的に入手していないゲームをプレイするために使用するものではありません。 + 非正規、非合法のゲームをプレイするためにこのソフトウェアを使用しないでください。 @@ -33,7 +33,7 @@ GameInfoClass Loading game list, please wait :3 - ゲームリストを読み込み中です。お待ちください :3 + ゲームリストを読み込み中です。しばらくお待ちください :3 Cancel @@ -52,7 +52,7 @@ Select which directory you want to install to. - Select which directory you want to install to. + インストール先のディレクトリを選択してください。 @@ -75,7 +75,7 @@ The value for location to install games is not valid. - ゲームをインストールする場所が無効です。 + ゲームのインストール場所が無効です。 @@ -130,35 +130,35 @@ Delete... - Delete... + 削除... Delete Game - Delete Game + ゲームを削除 Delete Update - Delete Update + アップデートを削除 Delete DLC - Delete DLC + DLCを削除 Compatibility... - Compatibility... + 互換性... Update database - Update database + データベースを更新 View report - View report + レポートを表示 Submit a report - Submit a report + レポートを送信 Shortcut creation @@ -182,23 +182,23 @@ Game - Game + ゲーム requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + この機能を利用するには、 'アップデートフォルダの分離を有効化' を有効化する必要があります。 This game has no update to delete! - This game has no update to delete! + このゲームにはアップデートがないため削除することができません! Update - Update + アップデート This game has no DLC to delete! - This game has no DLC to delete! + このゲームにはDLCがないため削除することができません! DLC @@ -206,11 +206,11 @@ Delete %1 - Delete %1 + %1 を削除 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + %1 の %2 ディレクトリを本当に削除しますか? @@ -241,15 +241,15 @@ Install application from a .pkg file - .pkgファイルからアプリケーションをインストールする + .pkgファイルからアプリケーションをインストール Recent Games - 最近のゲーム + 最近プレイしたゲーム Open shadPS4 Folder - Open shadPS4 Folder + shadPS4フォルダを開く Exit @@ -273,7 +273,7 @@ Tiny - 極小 + 最小 Small @@ -297,7 +297,7 @@ Elf Viewer - Elfビュワー + Elfビューアー Game Install Directory @@ -397,11 +397,11 @@ You have downloaded cheats for all the games you have installed. - インストールしたすべてのゲームのチートをダウンロードしました。 + インストールされているすべてのゲームのチートをダウンロードしました。 Patches Downloaded Successfully! - パッチが正常にダウンロードされました! + パッチが正常にダウンロードされました! All Patches available for all games have been downloaded. @@ -425,11 +425,11 @@ Only one file can be selected! - 1つのファイルしか選択できません! + 1つのファイルしか選択できません! PKG Extraction - PKG抽出 + PKGの抽出 Patch detected! @@ -473,7 +473,7 @@ PKG is a patch, please install the game first! - PKGはパッチです。ゲームを先にインストールしてください! + PKGはパッチです。ゲームを先にインストールしてください! PKG ERROR @@ -526,11 +526,11 @@ Console Language - コンソール言語 + コンソールの言語 Emulator Language - エミュレーター言語 + エミュレーターの言語 Emulator @@ -542,7 +542,7 @@ Enable Separate Update Folder - Enable Separate Update Folder + アップデートフォルダの分離を有効化 Show Game Size In List @@ -550,7 +550,7 @@ Show Splash - スプラッシュを表示する + スプラッシュ画面を表示する Is PS4 Pro @@ -566,11 +566,11 @@ Trophy Key - Trophy Key + トロフィーキー Trophy - Trophy + トロフィー Logger @@ -602,7 +602,7 @@ Hide Cursor Idle Timeout - カーソル非アクティブタイムアウト + カーソルを隠すまでの非アクティブ期間 s @@ -706,7 +706,7 @@ Disable Trophy Pop-ups - Disable Trophy Pop-ups + トロフィーのポップアップを無効化 Play title music @@ -714,19 +714,19 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + 起動時に互換性データベースを更新する Game Compatibility - Game Compatibility + ゲームの互換性 Display Compatibility Data - Display Compatibility Data + 互換性に関するデータを表示 Update Compatibility Database - Update Compatibility Database + 互換性データベースを更新 Volume @@ -734,7 +734,7 @@ Audio Backend - Audio Backend + オーディオ バックエンド Save @@ -754,15 +754,15 @@ Point your mouse at an option to display its description. - オプションにマウスをポイントすると、その説明が表示されます。 + 設定項目にマウスをホバーすると、説明が表示されます。 consoleLanguageGroupBox - コンソール言語:\nPS4ゲームが使用する言語を設定します。\nこれはゲームがサポートする言語に設定することをお勧めしますが、地域によって異なる場合があります。 + コンソールの言語:\nPS4ゲームが使用する言語を設定します。\nゲームでサポートされている言語に設定することをお勧めしますが、地域によって異なる場合があります。 emulatorLanguageGroupBox - エミュレーター言語:\nエミュレーターのユーザーインターフェースの言語を設定します。 + エミュレーターの言語:\nエミュレーターのユーザーインターフェースの言語を設定します。 fullscreenCheckBox @@ -770,7 +770,7 @@ separateUpdatesCheckBox - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + Enable Separate Update Folder:\nゲームのアップデートを別のフォルダにインストールすることで、管理が容易になります。 showSplashCheckBox @@ -778,7 +778,7 @@ ps4proCheckBox - PS4 Proです:\nエミュレーターがPS4 PROとして動作するようにし、これをサポートするゲームで特別な機能を有効にする場合があります。 + PS4 Pro モード:\nエミュレーターがPS4 PROとして動作するようになり、PS4 PROをサポートする一部のゲームで特別な機能が有効化される場合があります。 discordRPCCheckbox @@ -790,7 +790,7 @@ TrophyKey - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + トロフィーキー:\nトロフィーの復号に使用されるキーです。脱獄済みのコンソールから取得することができます。\n16進数のみを受け入れます。 logTypeGroupBox @@ -798,27 +798,27 @@ logFilter - ログフィルター:\n特定の情報のみを印刷するようにログをフィルタリングします。\n例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" レベル: Trace, Debug, Info, Warning, Error, Critical - この順序で、特定のレベルはリスト内のすべての前のレベルをサイレンスし、その後のすべてのレベルをログに記録します。 + ログフィルター:\n特定の情報のみを印刷するようにログをフィルタリングします。\n例: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" \nレベル: Trace, Debug, Info, Warning, Error, Critical - レベルはこの並び通りに処理され、指定されたレベルより前のレベル ログを抑制し、それ以外のすべてのレベルをログに記録します。 updaterGroupBox - 更新:\nRelease: 非常に古いかもしれないが、より信頼性が高くテスト済みの公式バージョンを毎月リリースします。\nNightly: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。 + 更新:\nRelease: 最新の機能を利用できない可能性がありますが、より信頼性が高くテストされた公式バージョンが毎月リリースされます。\nNightly: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。 GUIgroupBox - タイトルミュージックを再生:\nゲームがそれをサポートしている場合、GUIでゲームを選択したときに特別な音楽を再生することを有効にします。 + タイトルミュージックを再生:\nゲームでサポートされている場合に、GUIでゲームを選択したときに特別な音楽を再生する機能を有効にします。 disableTrophycheckBox - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + トロフィーのポップアップを無効化:\nゲーム内でのトロフィー通知を無効化します。 トロフィーの進行状況は、トロフィービューアーを使用して確認できます。(メインウィンドウでゲームを右クリック) hideCursorGroupBox - カーソルを隠す:\nカーソルが消えるタイミングを選択してください:\n決して: いつでもマウスが見えます。\nアイドル: アイダルの後に消えるまでの時間を設定します。\n常に: マウスは決して見えません。 + カーソルを隠す:\nカーソルが消えるタイミングを選択してください:\n無効: 常にカーソルが表示されます。\n非アクティブ時: カーソルの非アクティブ期間が指定した時間を超えた場合にカーソルを隠します。\n常に: カーソルは常に隠れた状態になります。 idleTimeoutGroupBox - アイドル後にマウスが消えるまでの時間を設定します。 + カーソルが非アクティブになってから隠すまでの時間を設定します。 backButtonBehaviorGroupBox @@ -826,23 +826,23 @@ enableCompatibilityCheckBox - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + 互換性に関するデータを表示:\nゲームの互換性に関する情報を表として表示します。常に最新情報を取得したい場合、"起動時に互換性データベースを更新する" を有効化してください。 checkCompatibilityOnStartupCheckBox - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + 起動時に互換性データベースを更新する:\nshadPS4の起動時に自動で互換性データベースを更新します。 updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + 互換性データベースを更新する:\n今すぐ互換性データベースを更新します。 Never - 決して + 無効 Idle - アイドル + 非アクティブ時 Always @@ -850,11 +850,11 @@ Touchpad Left - タッチパッド左 + 左タッチパッド Touchpad Right - タッチパッド右 + 右タッチパッド Touchpad Center @@ -866,15 +866,15 @@ graphicsAdapterGroupBox - グラフィックデバイス:\n複数のGPUシステムで、ドロップダウンリストからエミュレーターで使用するGPUを選択するか、\n「自動選択」を選択して自動的に決定します。 + グラフィックデバイス:\nシステムに複数のGPUが搭載されている場合、ドロップダウンリストからエミュレーターで使用するGPUを選択するか、\n「自動選択」を選択して自動的に決定します。 resolutionLayout - 幅/高さ:\n起動時にエミュレーターウィンドウのサイズを設定します。ゲーム中にサイズ変更できます。\nこれはゲーム内の解像度とは異なります。 + 幅/高さ:\n起動時にエミュレーターウィンドウのサイズを設定します。ゲーム中でもサイズを変更することができます。\nこれはゲーム内の解像度とは異なります。 heightDivider - Vblankディバイダー:\nエミュレーターが更新されるフレームレートにこの数を掛けます。これを変更すると、ゲームの速度が上がったり、想定外の変更がある場合、ゲームの重要な機能が壊れる可能性があります! + Vblankディバイダー:\nエミュレーターが更新されるフレームレートにこの数を掛けます。これを変更すると、ゲームの速度が上がったり、想定外の変更がある場合、ゲームの重要な機能が壊れる可能性があります! dumpShadersCheckBox @@ -917,11 +917,11 @@ CheatsPatches Cheats / Patches for - Cheats / Patches for + のチート/パッチ defaultTextEdit_MSG - チート/パッチは実験的です。\n使用には注意してください。\n\nリポジトリを選択し、ダウンロードボタンをクリックしてチートを個別にダウンロードします。\n「Patches」タブでは、すべてのパッチを一度にダウンロードし、使用したいものを選択して選択を保存できます。\n\nチート/パッチを開発していないため、\n問題があればチートの作者に報告してください。\n\n新しいチートを作成しましたか?\nhttps://github.com/shadps4-emu/ps4_cheats を訪問してください。 + チート/パッチは実験的です。\n使用には注意してください。\n\nリポジトリを選択し、ダウンロードボタンをクリックしてチートを個別にダウンロードします。\n「Patches」タブでは、すべてのパッチを一度にダウンロードし、使用したいものを選択して選択を保存できます。\n\nチート/パッチは開発を行っていないため、\n問題があればチートの作者に報告してください。\n\n新しいチートを作成しましたか?\nhttps://github.com/shadps4-emu/ps4_cheats を訪問してください。 No Image Available @@ -997,7 +997,7 @@ Unable to open files.json for reading. - files.jsonを読み込み用に開けません。 + files.jsonを読み取りのために開く事が出来ませんでした。 No patch file found for the current serial. @@ -1005,11 +1005,11 @@ Unable to open the file for reading. - ファイルを読み込み用に開けません。 + ファイルを読み取りのために開く事が出来ませんでした。 Unable to open the file for writing. - ファイルを記録用に開けません。 + ファイルをを書き込みのために開く事が出来ませんでした。 Failed to parse XML: @@ -1029,7 +1029,7 @@ The selected source is invalid. - 選択したソースは無効です。 + 選択されたソースは無効です。 File Exists @@ -1113,7 +1113,7 @@ Failed to open files.json for writing - files.jsonを記録用に開けません + files.jsonを読み取りのために開く事が出来ませんでした。 Author: @@ -1125,7 +1125,7 @@ Failed to open files.json for reading. - files.jsonを読み込み用に開けません。 + files.jsonを読み取りのために開く事が出来ませんでした。 Name: @@ -1180,43 +1180,43 @@ Never Played - Never Played + 未プレイ h - h + 時間 m - m + s - s + Compatibility is untested - Compatibility is untested + 互換性は未検証です Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + ゲームが正常に初期化されない/エミュレーターがクラッシュする Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + ゲームは起動しますが、空のスクリーンが表示されます Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + 正常にゲーム画面が表示されますが、メニューから先に進むことができません Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + ゲームを壊すような不具合や、プレイが不可能なほどのパフォーマンスの問題があります Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + パフォーマンスに問題はなく、大きな不具合なしでゲームをプレイすることができます From c015ac134462b3c425d69953e27da0e6c8ca9498 Mon Sep 17 00:00:00 2001 From: tomboylover93 <95257311+tomboylover93@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:53:27 -0800 Subject: [PATCH 54/65] Update Linux building documentation (#2211) * wip: redo build instructions guide for Linux * Add Linux screenshots directory to REUSE.toml * change links for Visual Studio Code images * change instructions for building from terminal reference: https://github.com/shadps4-emu/shadPS4/pull/2211#issuecomment-2608134455 --- REUSE.toml | 1 + documents/Screenshots/Linux/1.png | Bin 0 -> 11447 bytes documents/Screenshots/Linux/2.png | Bin 0 -> 20159 bytes documents/Screenshots/Linux/3.png | Bin 0 -> 25540 bytes documents/Screenshots/Linux/4.png | Bin 0 -> 16845 bytes documents/Screenshots/Linux/5.png | Bin 0 -> 26893 bytes documents/building-linux.md | 103 +++++++++++++++++++++++++----- 7 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 documents/Screenshots/Linux/1.png create mode 100644 documents/Screenshots/Linux/2.png create mode 100644 documents/Screenshots/Linux/3.png create mode 100644 documents/Screenshots/Linux/4.png create mode 100644 documents/Screenshots/Linux/5.png diff --git a/REUSE.toml b/REUSE.toml index 55d76673d..a62974bcd 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -14,6 +14,7 @@ path = [ "documents/changelog.md", "documents/Quickstart/2.png", "documents/Screenshots/*", + "documents/Screenshots/Linux/*", "externals/MoltenVK/MoltenVK_icd.json", "scripts/ps4_names.txt", "src/images/about_icon.png", diff --git a/documents/Screenshots/Linux/1.png b/documents/Screenshots/Linux/1.png new file mode 100644 index 0000000000000000000000000000000000000000..3dd5ef92b39960dcf98cf22d47f3895d84884581 GIT binary patch literal 11447 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 OgJdKW#s7Xb^#4D*Qdz11 literal 0 HcmV?d00001 diff --git a/documents/Screenshots/Linux/2.png b/documents/Screenshots/Linux/2.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5ff3b534773394c892a9d026a4d5e553ed2cfa GIT binary patch literal 20159 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

IFt#Ds2NY9n;YzAXc!7BTm6;cMp= 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 HFG2qUd8Vx* literal 0 HcmV?d00001 diff --git a/documents/Screenshots/Linux/4.png b/documents/Screenshots/Linux/4.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8a3b6c1e9a39d06bfc9ec0364db3b1f0b7a5bc GIT binary patch literal 16845 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 r -## Build shadPS4 for Linux +## Build shadPS4 for Linux -### Install the necessary tools to build shadPS4: +First and foremost, Clang 18 is the **recommended compiler** as it is used for official builds and CI. If you build with GCC, you might encounter issues — please report any you find. Additionally, if you choose to use GCC, please build shadPS4 with Clang at least once before creating an `[APP BUG]` issue or submitting a pull request. + +## Preparatory steps + +### Installing dependencies #### Debian & Ubuntu + ``` sudo apt install build-essential clang git cmake libasound2-dev libpulse-dev libopenal-dev libssl-dev zlib1g-dev libedit-dev libudev-dev libevdev-dev libsdl2-dev libjack-dev libsndio-dev qt6-base-dev qt6-tools-dev qt6-multimedia-dev libvulkan-dev vulkan-validationlayers ``` #### Fedora + ``` sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-connection-kit-devel openal-devel openssl-devel libevdev-devel libudev-devel libXext-devel qt6-qtbase-devel qt6-qtbase-private-devel qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel vulkan-devel vulkan-validation-layers ``` #### Arch Linux + ``` sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers ``` +**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses GCC as the compiler as opposed to Clang. Use at your own discretion. #### OpenSUSE + ``` sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers ``` -### Cloning and compiling: -Clone the repository recursively: +#### Other Linux distributions + +You can try one of two methods: + +- Search the packages by name and install them with your package manager, or +- Install [distrobox](https://distrobox.it/), create a container using any of the distributions cited above as a base, for Arch Linux you'd do: + +``` +distrobox create --name archlinux --init --image archlinux:latest +``` + +and install the dependencies on that container as cited above. +This option is **highly recommended** for NixOS and distributions with immutable/atomic filesystems (example: Fedora Kinoite, SteamOS). +### Cloning + ``` git clone --recursive https://github.com/shadps4-emu/shadPS4.git cd shadPS4 ``` -Generate the build directory in the shadPS4 directory. To disable the QT GUI, remove the ```-DENABLE_QT_GUI=ON``` flag: +## Building + +There are 3 options you can choose from. Option 1 is **highly recommended**. + +#### Option 1: Terminal-only + +1. Generate the build directory in the shadPS4 directory. -**Note**: Clang is the compiler used for official builds and CI. If you build with GCC, you might encounter issues—please report any you find. If you choose to use GCC, we recommend building with Clang at least once before submitting a pull request. ``` cmake -S . -B build/ -DENABLE_QT_GUI=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ``` -Enter the directory: +To disable the Qt GUI, remove the `-DENABLE_QT_GUI=ON` flag. To change the build type (for debugging), add `-DCMAKE_BUILD_TYPE=Debug`. + +2. Use CMake to build the project: + ``` -cd build/ +cmake --build ./build --parallel$(nproc) ``` -Use make to build the project: +If your computer freezes during this step, this could be caused by excessive system resource usage. In that case, remove `--parallel$(nproc)`. + +Now run the emulator. If Qt was enabled at configure time: + ``` -cmake --build . --parallel$(nproc) +./build/shadps4 ``` -Now run the emulator. If QT is enabled: -``` -./shadps4 -``` Otherwise, specify the path to your PKG's boot file: + ``` -./shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin +./build/shadps4 /"PATH"/"TO"/"GAME"/"FOLDER"/eboot.bin ``` + +You can also specify the Game ID as an argument for which game to boot, as long as the folder containing the games is specified in config.toml (example: Bloodborne (US) is CUSA00900). +#### Option 2: Configuring with cmake-gui + +`cmake-gui` should be installed by default alongside `cmake`, if not search for the package in your package manager and install it. + +Open `cmake-gui` and specify the source code and build directories. If you cloned the source code to your Home directory, it would be `/home/user/shadPS4` and `/home/user/shadPS4/build`. + +Click on Configure, select "Unix Makefiles", select "Specify native compilers", click Next and choose `clang` and `clang++` as the C and CXX compilers. Usually they are located in `/bin/clang` and `/bin/clang++`. Click on Finish and let it configure the project. + +Now every option should be displayed in red. Change anything you want, then click on Generate to make the changes permanent, then open a terminal window and do steps 2 and 3 of Option 1. + +#### Option 3: Visual Studio Code + +This option is pretty convoluted and should only be used if you have VSCode as your default IDE, or just prefer building and debugging projects through it. This also assumes that you're using an Arch Linux environment, as the naming for some options might differ from other distros. + +[Download Visual Studio Code for your platform](https://code.visualstudio.com/download), or use [Code - OSS](https://github.com/microsoft/vscode) if you'd like. Code - OSS is available on most Linux distributions' package repositories (on Arch Linux it is simply named `code`). + +Once set up, go to Extensions and install "CMake Tools": + +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/3.png) + +You can also install other CMake and Clang related extensions if you'd like, but this one is what enables you to configure and build CMake projects directly within VSCode. + +Go to Settings, filter by `@ext:ms-vscode.cmake-tools configure` and disable this option: + +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/1.png) + +If you wish to build with the Qt GUI, add `-DENABLE_QT_GUI=ON` to the configure arguments: + +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/2.png) + +On the CMake tab, change the options as you wish, but make sure that it looks similar to or exactly like this: + +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/4.png) + +When hovering over Project Status > Configure, there should be an icon titled "Configure". Click on it and let it configure the project, then do the same for Project Status > Build. + +If you want to debug it, change the build type under Project Status > Configure to Debug (it should be the default) and compile it, then click on the icon in Project Status > Debug. If you simply want to launch the shadPS4 executable from within VSCode, click on the icon in Project Status > Launch. + +Don't forget to change the launch target for both options to the shadPS4 executable inside shadPS4/build: + +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/5.png) \ No newline at end of file From d7c2cb17f3e1b339d78c5eb1bcb44e0305a85e89 Mon Sep 17 00:00:00 2001 From: panzone91 <150828896+panzone91@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:53:54 +0000 Subject: [PATCH 55/65] update extension vector capacity (#2210) --- src/video_core/renderer_vulkan/vk_instance.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index d183d6b09..a722b5322 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -232,7 +232,7 @@ bool Instance::CreateDevice() { return false; } - boost::container::static_vector enabled_extensions; + boost::container::static_vector enabled_extensions; const auto add_extension = [&](std::string_view extension) -> bool { const auto result = std::find_if(available_extensions.begin(), available_extensions.end(), From 1fcfb07421909ab779ee1a884b6a5ce0fb0fd4e3 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Wed, 22 Jan 2025 19:21:52 -0300 Subject: [PATCH 56/65] GUI: Settings improvements (#2213) --- src/common/config.cpp | 13 +- src/common/config.h | 2 + src/qt_gui/settings_dialog.cpp | 23 +- src/qt_gui/settings_dialog.ui | 1336 ++++++++++++++++++------------ src/qt_gui/translations/ar.ts | 18 +- src/qt_gui/translations/da_DK.ts | 18 +- src/qt_gui/translations/de.ts | 18 +- src/qt_gui/translations/el.ts | 18 +- src/qt_gui/translations/en.ts | 18 +- src/qt_gui/translations/es_ES.ts | 18 +- src/qt_gui/translations/fa_IR.ts | 18 +- src/qt_gui/translations/fi.ts | 18 +- src/qt_gui/translations/fr.ts | 18 +- src/qt_gui/translations/hu_HU.ts | 18 +- src/qt_gui/translations/id.ts | 18 +- src/qt_gui/translations/it.ts | 18 +- src/qt_gui/translations/ja_JP.ts | 18 +- src/qt_gui/translations/ko_KR.ts | 18 +- src/qt_gui/translations/lt_LT.ts | 18 +- src/qt_gui/translations/nb.ts | 18 +- src/qt_gui/translations/nl.ts | 18 +- src/qt_gui/translations/pl_PL.ts | 18 +- src/qt_gui/translations/pt_BR.ts | 24 +- src/qt_gui/translations/ro_RO.ts | 18 +- src/qt_gui/translations/ru_RU.ts | 18 +- src/qt_gui/translations/sq.ts | 18 +- src/qt_gui/translations/sv.ts | 18 +- src/qt_gui/translations/tr_TR.ts | 18 +- src/qt_gui/translations/uk_UA.ts | 18 +- src/qt_gui/translations/vi_VN.ts | 18 +- src/qt_gui/translations/zh_CN.ts | 18 +- src/qt_gui/translations/zh_TW.ts | 18 +- 32 files changed, 1339 insertions(+), 545 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 6e9db50ff..a57b6d35c 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -45,6 +45,7 @@ static std::string logFilter; static std::string logType = "async"; static std::string userName = "shadPS4"; static std::string updateChannel; +static std::string chooseHomeTab; static u16 deadZoneLeft = 2.0; static u16 deadZoneRight = 2.0; static std::string backButtonBehavior = "left"; @@ -194,6 +195,10 @@ std::string getUpdateChannel() { return updateChannel; } +std::string getChooseHomeTab() { + return chooseHomeTab; +} + std::string getBackButtonBehavior() { return backButtonBehavior; } @@ -399,6 +404,9 @@ void setUserName(const std::string& type) { void setUpdateChannel(const std::string& type) { updateChannel = type; } +void setChooseHomeTab(const std::string& type) { + chooseHomeTab = type; +} void setBackButtonBehavior(const std::string& type) { backButtonBehavior = type; @@ -637,6 +645,7 @@ void load(const std::filesystem::path& path) { compatibilityData = toml::find_or(general, "compatibilityEnabled", false); checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", false); + chooseHomeTab = toml::find_or(general, "chooseHomeTab", "Release"); } if (data.contains("Input")) { @@ -760,6 +769,7 @@ void save(const std::filesystem::path& path) { data["General"]["logType"] = logType; data["General"]["userName"] = userName; data["General"]["updateChannel"] = updateChannel; + data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["showSplash"] = isShowSplash; data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["separateUpdateEnabled"] = separateupdatefolder; @@ -871,6 +881,7 @@ void setDefaultValues() { } else { updateChannel = "Nightly"; } + chooseHomeTab = "General"; cursorState = HideCursorState::Idle; cursorHideTimeout = 5; backButtonBehavior = "left"; @@ -898,4 +909,4 @@ void setDefaultValues() { checkCompatibilityOnStartup = false; } -} // namespace Config +} // namespace Config \ No newline at end of file diff --git a/src/common/config.h b/src/common/config.h index 564947f1e..15937606e 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -35,6 +35,7 @@ std::string getLogFilter(); std::string getLogType(); std::string getUserName(); std::string getUpdateChannel(); +std::string getChooseHomeTab(); u16 leftDeadZone(); u16 rightDeadZone(); @@ -81,6 +82,7 @@ void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(const std::string& type); void setUpdateChannel(const std::string& type); +void setChooseHomeTab(const std::string& type); void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& settings_install_dirs_config); void setSaveDataPath(const std::filesystem::path& path); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 5695d6232..b41fde745 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -65,6 +65,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); ui->tabWidgetSettings->setUsesScrollButtons(false); + initialHeight = this->height(); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); @@ -150,7 +151,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, }); #else ui->updaterGroupBox->setVisible(false); - ui->GUIgroupBox->setMaximumSize(265, 16777215); #endif connect(ui->updateCompatibilityButton, &QPushButton::clicked, this, [this, parent, m_compat_info]() { @@ -169,6 +169,11 @@ SettingsDialog::SettingsDialog(std::span physical_devices, }); } + // Gui TAB + { + connect(ui->chooseHomeTabComboBox, &QComboBox::currentTextChanged, this, + [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); + } // Input TAB { connect(ui->hideCursorComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, @@ -246,7 +251,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, #ifdef ENABLE_UPDATER ui->updaterGroupBox->installEventFilter(this); #endif - ui->GUIgroupBox->installEventFilter(this); + ui->GUIMusicGroupBox->installEventFilter(this); ui->disableTrophycheckBox->installEventFilter(this); ui->enableCompatibilityCheckBox->installEventFilter(this); ui->checkCompatibilityOnStartupCheckBox->installEventFilter(this); @@ -373,6 +378,15 @@ void SettingsDialog::LoadValuesFromConfig() { ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel)); #endif + std::string chooseHomeTab = toml::find_or(data, "General", "chooseHomeTab", ""); + ui->chooseHomeTabComboBox->setCurrentText(QString::fromStdString(chooseHomeTab)); + QStringList tabNames = {tr("General"), tr("Gui"), tr("Graphics"), tr("User"), + tr("Input"), tr("Paths"), tr("Debug")}; + QString chooseHomeTabQString = QString::fromStdString(chooseHomeTab); + int indexTab = tabNames.indexOf(chooseHomeTabQString); + indexTab = (indexTab == -1) ? 0 : indexTab; + ui->tabWidgetSettings->setCurrentIndex(indexTab); + QString backButtonBehavior = QString::fromStdString( toml::find_or(data, "Input", "backButtonBehavior", "left")); int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); @@ -476,8 +490,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { } else if (elementName == "updaterGroupBox") { text = tr("updaterGroupBox"); #endif - } else if (elementName == "GUIgroupBox") { - text = tr("GUIgroupBox"); + } else if (elementName == "GUIMusicGroupBox") { + text = tr("GUIMusicGroupBox"); } else if (elementName == "disableTrophycheckBox") { text = tr("disableTrophycheckBox"); } else if (elementName == "enableCompatibilityCheckBox") { @@ -592,6 +606,7 @@ void SettingsDialog::UpdateSettings() { Config::setRdocEnabled(ui->rdocCheckBox->isChecked()); Config::setAutoUpdate(ui->updateCheckBox->isChecked()); Config::setUpdateChannel(ui->updateComboBox->currentText().toStdString()); + Config::setChooseHomeTab(ui->chooseHomeTabComboBox->currentText().toStdString()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index d72a56973..5da6a8198 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -12,11 +12,11 @@ 0 0 970 - 820 + 600 - + 0 0 @@ -52,8 +52,14 @@ 0 + + + 0 + 0 + + - 3 + 0 @@ -67,8 +73,8 @@ 0 0 - 771 - 606 + 946 + 386 @@ -77,14 +83,56 @@ 0 - + + 6 + + + + 0 + + + 0 + + + 0 + + + + 0 + 0 + + Emulator - + + Qt::AlignmentFlag::AlignBottom|Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft + + + false + + + false + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + @@ -106,7 +154,7 @@ - + 0 0 @@ -149,215 +197,40 @@ - - - - 6 - - - 0 - - - - - - - Username - - - - - - - - - - - - - - + + + + 6 + + + 0 + - + - + 0 0 - + 0 0 - - GUI Settings - - - - 1 - - - 11 - - - - - Show Game Size In List - - - - - - - - 0 - 0 - - - - Play title music - - - - - - - 1 - - - 0 - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Volume - - - - - - - Set the volume of the background music. - - - 100 - - - 10 - - - 20 - - - 50 - - - Qt::Orientation::Horizontal - - - false - - - false - - - QSlider::TickPosition::NoTicks - - - 10 - - - - - - - - - 6 - - - 0 - - - 50 - - - - - - - Trophy - - - - - - Disable Trophy Pop-ups - - - - - - - Trophy Key - - - - - - - - 0 - 0 - - - - - 10 - false - - - - - - - - - - - - - - - - - - - - System + + 70 + @@ -387,6 +260,45 @@ + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + @@ -432,16 +344,16 @@ - 10 + 6 - 1 + 9 - 11 + 9 - 190 + 80 @@ -508,7 +420,7 @@ - + 0 0 @@ -554,8 +466,255 @@ - + + + + + + + + true + + + Gui + + + + + 0 + 0 + 946 + 386 + + + + + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + GUI Settings + + + + 9 + + + 9 + + + + + Default tab when opening settings + + + + + + + 0 + 0 + + + + + General + + + + + Gui + + + + + Graphics + + + + + User + + + + + Input + + + + + Paths + + + + + Debug + + + + + + + + + + + Show Game Size In List + + + + + + + + 0 + 0 + + + + + 11 + false + true + + + + false + + + + + + false + + + + + 10 + 80 + 416 + 29 + + + + + 0 + 0 + + + + Set the volume of the background music. + + + 100 + + + 10 + + + 20 + + + 50 + + + Qt::Orientation::Horizontal + + + false + + + false + + + QSlider::TickPosition::NoTicks + + + 10 + + + + + + 10 + 22 + 416 + 26 + + + + + 0 + 0 + + + + Play title music + + + + + + 10 + 54 + 416 + 20 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Volume + + + + + + + + + + + + 6 + + + 210 + @@ -570,6 +729,12 @@ 0 + + + 0 + 0 + + Game Compatibility @@ -578,10 +743,10 @@ 10 - 1 + 9 - 11 + 9 @@ -632,6 +797,394 @@ + + + true + + + Graphics + + + + + 0 + 0 + 946 + 386 + + + + + + + + + + + Graphics Device + + + + + + + + + Enable NULL GPU + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + 6 + + + 0 + + + + + + + Width + + + + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 0 + + + 9999 + + + 1280 + + + + + + + + + + Height + + + + + + true + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 0 + + + 9999 + + + 720 + + + + + + + + + + Vblank Divider + + + + + + true + + + true + + + QAbstractSpinBox::CorrectionMode::CorrectToNearestValue + + + false + + + 1 + + + 9999 + + + 1 + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + 12 + + + 12 + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + true + + + User + + + + + 0 + 0 + 946 + 386 + + + + + + + 0 + + + 0 + + + + + + + Username + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 40 + + + + + + + + + + 6 + + + 0 + + + 50 + + + + + + + Trophy + + + + + + Disable Trophy Pop-ups + + + + + + + Trophy Key + + + + + + + + 0 + 0 + + + + + 10 + false + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + true @@ -644,8 +1197,8 @@ 0 0 - 455 - 252 + 946 + 386 @@ -916,260 +1469,6 @@ - - - true - - - Graphics - - - - - 0 - 0 - 579 - 194 - - - - - - - - - - - Graphics Device - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - 6 - - - 0 - - - - - - - Width - - - - - - true - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - false - - - 0 - - - 9999 - - - 1280 - - - - - - - - - - Height - - - - - - true - - - true - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - false - - - 0 - - - 9999 - - - 720 - - - - - - - - - - - - - - 6 - - - 0 - - - - - - - Vblank Divider - - - - - - true - - - true - - - QAbstractSpinBox::CorrectionMode::CorrectToNearestValue - - - false - - - 1 - - - 9999 - - - 1 - - - - - - - - - - - - - - - - 12 - - - 12 - - - - - true - - - Advanced - - - Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop - - - - - - Enable Shaders Dumping - - - - - - - Enable NULL GPU - - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - true @@ -1178,6 +1477,14 @@ Paths + + + 0 + 0 + 946 + 386 + + @@ -1186,34 +1493,34 @@ - - - - - Add... - - - - - - - Remove - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - + + + + + Add... + + + + + + + Remove + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + @@ -1225,27 +1532,27 @@ - Save Data Path + Save Data Path - - - - - - true - - - - - - - Browse - - - - - + + + + + + true + + + + + + + Browse + + + + + @@ -1264,13 +1571,13 @@ 0 0 - 510 - 269 + 946 + 386 - + 0 @@ -1291,6 +1598,13 @@ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + + Enable Shaders Dumping + + + diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index 47bd673b2..f7b93028a 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -540,10 +540,18 @@ Enable Fullscreen تمكين ملء الشاشة + + Fullscreen Mode + وضع ملء الشاشة + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + علامة التبويب الافتراضية عند فتح الإعدادات + Show Game Size In List عرض حجم اللعبة في القائمة @@ -620,6 +628,14 @@ Graphics الرسومات + + Gui + واجهة + + + User + مستخدم + Graphics Device جهاز الرسومات @@ -805,7 +821,7 @@ تحديث: Release: إصدارات رسمية تصدر شهريًا، قد تكون قديمة بعض الشيء، لكنها أكثر استقرارًا واختبارًا. Nightly: إصدارات تطوير تحتوي على أحدث الميزات والإصلاحات، لكنها قد تحتوي على أخطاء وأقل استقرارًا. - GUIgroupBox + GUIMusicGroupBox تشغيل موسيقى العنوان:\nإذا كانت اللعبة تدعم ذلك، قم بتمكين تشغيل موسيقى خاصة عند اختيار اللعبة في واجهة المستخدم. diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 91a98abd4..61d2f68bf 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Fuldskærmstilstand + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Standardfaneblad ved åbning af indstillinger + Show Game Size In List Vis vis spilstørrelse i listen @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Interface + + + User + Bruger + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Opdatering:\nRelease: Officielle builds, der frigives månedligt, som kan være meget ældre, men mere stabile og testet.\nNightly: Udviklerbuilds med de nyeste funktioner og rettelser, men som kan indeholde fejl og være mindre stabile. - GUIgroupBox + GUIMusicGroupBox Titelsmusikafspilning:\nHvis spillet understøtter det, aktiver speciel musik, når spillet vælges i brugergrænsefladen. diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index b1e1d2664..9d99a7048 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -540,10 +540,18 @@ Enable Fullscreen Vollbild aktivieren + + Fullscreen Mode + Vollbildmodus + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Standardregisterkarte beim Öffnen der Einstellungen + Show Game Size In List Zeigen Sie die Spielgröße in der Liste @@ -620,6 +628,14 @@ Graphics Grafik + + Gui + Benutzeroberfläche + + + User + Benutzer + Graphics Device Grafikgerät @@ -805,7 +821,7 @@ Update:\nRelease: Offizielle Builds, die monatlich veröffentlicht werden, können viel älter sein, aber stabiler und getestet.\nNightly: Entwickler-Builds, die die neuesten Funktionen und Fehlerbehebungen enthalten, aber Fehler enthalten und weniger stabil sein können. - GUIgroupBox + GUIMusicGroupBox Wiedergabe der Titelmusik:\nWenn das Spiel dies unterstützt, wird beim Auswählen des Spiels in der Benutzeroberfläche spezielle Musik abgespielt. diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index ecda0ede0..66c3c07cd 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Λειτουργία Πλήρους Οθόνης + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Προεπιλεγμένη καρτέλα κατά την ανοίγμα των ρυθμίσεων + Show Game Size In List Εμφάνιση Μεγέθους Παιχνιδιού στη Λίστα @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Διεπαφή + + + User + Χρήστης + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Ενημερώσεις:\nRelease: Επίσημες εκδόσεις που κυκλοφορούν μηνιαίως, είναι παλαιότερες αλλά πιο σταθερές και δοκιμασμένες.\nNightly: Εκδόσεις προγραμματιστών με νέες δυνατότητες και διορθώσεις, αλλά μπορεί να περιέχουν σφάλματα και να είναι λιγότερο σταθερές. - GUIgroupBox + GUIMusicGroupBox Αναπαραγωγή Μουσικής Τίτλων:\nΕάν το παιχνίδι το υποστηρίζει, ενεργοποιεί ειδική μουσική κατά την επιλογή του παιχνιδιού από τη διεπαφή χρήστη. diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index dc72d97c9..8b6c3c69f 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Fullscreen Mode + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Default tab when opening settings + Show Game Size In List Show Game Size In List @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Gui + + + User + User + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - GUIgroupBox + GUIMusicGroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index a47f7c577..8f7a09b5d 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -540,10 +540,18 @@ Enable Fullscreen Habilitar pantalla completa + + Fullscreen Mode + Modo de Pantalla Completa + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Pestaña predeterminada al abrir la configuración + Show Game Size In List Mostrar Tamaño del Juego en la Lista @@ -620,6 +628,14 @@ Graphics Gráficos + + Gui + Interfaz + + + User + Usuario + Graphics Device Dispositivo gráfico @@ -805,7 +821,7 @@ Actualización:\nRelease: Versiones oficiales lanzadas cada mes que pueden estar muy desactualizadas, pero son más confiables y están probadas.\nNightly: Versiones de desarrollo que tienen todas las últimas funciones y correcciones, pero pueden contener errores y son menos estables. - GUIgroupBox + GUIMusicGroupBox Reproducir Música del Título:\nSi un juego lo admite, habilita la reproducción de música especial al seleccionar el juego en la interfaz gráfica. diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 976e7614e..043d782c2 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -540,10 +540,18 @@ Enable Fullscreen تمام صفحه + + Fullscreen Mode + حالت تمام صفحه + Enable Separate Update Folder فعال‌سازی پوشه جداگانه برای به‌روزرسانی + + Default tab when opening settings + زبان پیش‌فرض هنگام باز کردن تنظیمات + Show Game Size In List نمایش اندازه بازی در لیست @@ -620,6 +628,14 @@ Graphics گرافیک + + Gui + رابط کاربری + + + User + کاربر + Graphics Device کارت گرافیک مورداستفاده @@ -805,7 +821,7 @@ به‌روزرسانی:\nانتشار: نسخه‌های رسمی که هر ماه منتشر می‌شوند و ممکن است بسیار قدیمی باشند، اما پایدارتر و تست‌ شده‌تر هستند.\nشبانه: نسخه‌های توسعه‌ای که شامل جدیدترین ویژگی‌ها و اصلاحات هستند، اما ممکن است دارای اشکال باشند و کمتر پایدار باشند. - GUIgroupBox + GUIMusicGroupBox پخش موسیقی عنوان:\nIدر صورتی که بازی از آن پشتیبانی کند، پخش موسیقی ویژه هنگام انتخاب بازی در رابط کاربری را فعال می‌کند. diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index abc091b7e..86f6a6d3c 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -540,10 +540,18 @@ Enable Fullscreen Ota Käyttöön Koko Ruudun Tila + + Fullscreen Mode + Koko näytön tila + Enable Separate Update Folder Ota Käyttöön Erillinen Päivityshakemisto + + Default tab when opening settings + Oletusvälilehti avattaessa asetuksia + Show Game Size In List Näytä pelin koko luettelossa @@ -620,6 +628,14 @@ Graphics Grafiikka + + Gui + Rajapinta + + + User + Käyttäjä + Graphics Device Näytönohjain @@ -805,7 +821,7 @@ Päivitys:\nRelease: Viralliset versiot, jotka julkaistaan kuukausittain ja saattavat olla hyvin vanhoja, mutta ovat luotettavampia ja testatumpia.\nNightly: Kehitysversiot, joissa on kaikki uusimmat ominaisuudet ja korjaukset, mutta ne saattavat sisältää virheitä ja ovat vähemmän vakaita. - GUIgroupBox + GUIMusicGroupBox Soita Otsikkomusiikkia:\nJos peli tukee sitä, ota käyttöön erityisen musiikin soittaminen pelin valinnan yhteydessä käyttöliittymässä. diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index d2a1c5307..601ece8b4 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -540,10 +540,18 @@ Enable Fullscreen Plein écran + + Fullscreen Mode + Mode Plein Écran + Enable Separate Update Folder Dossier séparé pour les mises à jours + + Default tab when opening settings + Onglet par défaut lors de l'ouverture des paramètres + Show Game Size In List Afficher la taille du jeu dans la liste @@ -620,6 +628,14 @@ Graphics Graphismes + + Gui + Interface + + + User + Utilisateur + Graphics Device Carte graphique @@ -805,7 +821,7 @@ Mise à jour:\nRelease: versions officielles publiées chaque mois qui peuvent être très anciennes, mais plus fiables et testées.\nNightly: versions de développement avec toutes les dernières fonctionnalités et correctifs, mais pouvant avoir des bogues et être moins stables. - GUIgroupBox + GUIMusicGroupBox Jouer de la musique de titre:\nSi le jeu le prend en charge, cela active la musique spéciale lorsque vous sélectionnez le jeu dans l'interface utilisateur. diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index dff6a3a18..fe71e8120 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -540,10 +540,18 @@ Enable Fullscreen Teljes Képernyő Engedélyezése + + Fullscreen Mode + Teljes képernyős mód + Enable Separate Update Folder Külön Frissítési Mappa Engedélyezése + + Default tab when opening settings + Alapértelmezett fül a beállítások megnyitásakor + Show Game Size In List Játékméret megjelenítése a listában @@ -620,6 +628,14 @@ Graphics Grafika + + Gui + Felület + + + User + Felhasználó + Graphics Device Grafikai Eszköz @@ -805,7 +821,7 @@ Frissítés:\nRelease: Hivatalos verziók, amelyeket havonta adnak ki, és amelyek nagyon elavultak lehetnek, de megbízhatóbbak és teszteltek.\nNightly: Fejlesztési verziók, amelyek az összes legújabb funkciót és javítást tartalmazzák, de hibákat tartalmazhatnak és kevésbé stabilak. - GUIgroupBox + GUIMusicGroupBox Játék címzene lejátszása:\nHa a játék támogatja, engedélyezze egy speciális zene lejátszását, amikor a játékot kiválasztja a GUI-ban. diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index e6fb8b5aa..acfde43a7 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Mode Layar Penuh + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Tab default saat membuka pengaturan + Show Game Size In List Tampilkan Ukuran Game di Daftar @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Antarmuka + + + User + Pengguna + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Pembaruan:\nRelease: Versi resmi yang dirilis setiap bulan yang mungkin sangat ketinggalan zaman, tetapi lebih dapat diandalkan dan teruji.\nNightly: Versi pengembangan yang memiliki semua fitur dan perbaikan terbaru, tetapi mungkin mengandung bug dan kurang stabil. - GUIgroupBox + GUIMusicGroupBox Putar Musik Judul Permainan:\nJika permainan mendukungnya, aktifkan pemutaran musik khusus saat memilih permainan di GUI. diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 73dbdc603..93c6233a6 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -540,10 +540,18 @@ Enable Fullscreen Abilita Schermo Intero + + Fullscreen Mode + Modalità Schermo Intero + Enable Separate Update Folder Abilita Cartella Aggiornamenti Separata + + Default tab when opening settings + Scheda predefinita all'apertura delle impostazioni + Show Game Size In List Mostra la dimensione del gioco nell'elenco @@ -620,6 +628,14 @@ Graphics Grafica + + Gui + Interfaccia + + + User + Utente + Graphics Device Scheda Grafica @@ -805,7 +821,7 @@ Aggiornamento:\nRelease: Versioni ufficiali rilasciate ogni mese che potrebbero essere molto datate, ma sono più affidabili e testate.\nNightly: Versioni di sviluppo che hanno tutte le ultime funzionalità e correzioni, ma potrebbero contenere bug e sono meno stabili. - GUIgroupBox + GUIMusicGroupBox Riproduci Musica del Titolo:\nSe un gioco lo supporta, attiva la riproduzione di musica speciale quando selezioni il gioco nell'interfaccia grafica. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index bb9488a6b..921af52bb 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -540,10 +540,18 @@ Enable Fullscreen フルスクリーンを有効にする + + Fullscreen Mode + 全画面モード + Enable Separate Update Folder アップデートフォルダの分離を有効化 + + Default tab when opening settings + 設定を開くときのデフォルトタブ + Show Game Size In List ゲームサイズをリストに表示 @@ -620,6 +628,14 @@ Graphics グラフィックス + + Gui + インターフェース + + + User + ユーザー + Graphics Device グラフィックスデバイス @@ -805,7 +821,7 @@ 更新:\nRelease: 最新の機能を利用できない可能性がありますが、より信頼性が高くテストされた公式バージョンが毎月リリースされます。\nNightly: 最新の機能と修正がすべて含まれていますが、バグが含まれている可能性があり、安定性は低いです。 - GUIgroupBox + GUIMusicGroupBox タイトルミュージックを再生:\nゲームでサポートされている場合に、GUIでゲームを選択したときに特別な音楽を再生する機能を有効にします。 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 560b58340..2491959a6 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + 전체 화면 모드 + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + 설정 열기 시 기본 탭 + Show Game Size In List 게임 크기를 목록에 표시 @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + 인터페이스 + + + User + 사용자 + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - GUIgroupBox + GUIMusicGroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index e2ec1e5c3..23a52e948 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Viso ekranas + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Numatytoji kortelė atidarius nustatymus + Show Game Size In List Rodyti žaidimo dydį sąraše @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Interfeisa + + + User + Naudotojas + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Atnaujinti:\nRelease: Oficialios versijos, išleidžiamos kiekvieną mėnesį, kurios gali būti labai pasenusios, tačiau yra patikimos ir išbandytos.\nNightly: Vystymo versijos, kuriose yra visos naujausios funkcijos ir taisymai, tačiau gali turėti klaidų ir būti mažiau stabilios. - GUIgroupBox + GUIMusicGroupBox Groti antraščių muziką:\nJei žaidimas tai palaiko, įjungia specialios muzikos grojimą, kai pasirinkite žaidimą GUI. diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index b94d29b23..d6d4090df 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -540,10 +540,18 @@ Enable Fullscreen Aktiver fullskjerm + + Fullscreen Mode + Fullskjermmodus + Enable Separate Update Folder Aktiver seperat oppdateringsmappe + + Default tab when opening settings + Standardfanen når innstillingene åpnes + Show Game Size In List Vis spillstørrelse i listen @@ -620,6 +628,14 @@ Graphics Grafikk + + Gui + Grensesnitt + + + User + Bruker + Graphics Device Grafikkenhet @@ -805,7 +821,7 @@ Oppdatering:\nRelease: Offisielle versjoner utgitt hver måned som kan være veldig utdaterte, men er mer pålitelige og testet.\nNightly: Utviklingsversjoner som har alle de nyeste funksjonene og feilrettingene, men som kan inneholde feil og er mindre stabile. - GUIgroupBox + GUIMusicGroupBox Spille tittelmusikk:\nHvis et spill støtter det, så aktiveres det spesiell musikk når du velger spillet i menyen. diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index add27500f..1dabda8b4 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Volledig schermmodus + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Standaardtabblad bij het openen van instellingen + Show Game Size In List Toon grootte van het spel in de lijst @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Interface + + + User + Gebruiker + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Updateren:\nRelease: Officiële versies die elke maand worden uitgebracht, die zeer verouderd kunnen zijn, maar betrouwbaar en getest zijn.\nNightly: Ontwikkelingsversies die alle nieuwste functies en bugfixes bevatten, maar mogelijk bugs bevatten en minder stabiel zijn. - GUIgroupBox + GUIMusicGroupBox Speel titelsong:\nAls een game dit ondersteunt, wordt speciale muziek afgespeeld wanneer je het spel in de GUI selecteert. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 3280beea7..528e88337 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -540,10 +540,18 @@ Enable Fullscreen Włącz pełny ekran + + Fullscreen Mode + Tryb Pełnoekranowy + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Domyślna zakładka podczas otwierania ustawień + Show Game Size In List Pokaż rozmiar gry na liście @@ -620,6 +628,14 @@ Graphics Grafika + + Gui + Interfejs + + + User + Użytkownik + Graphics Device Karta graficzna @@ -805,7 +821,7 @@ Aktualizator:\nRelease: Oficjalne wersje wydawane co miesiąc, które mogą być bardzo przestarzałe, ale są niezawodne i przetestowane.\nNightly: Wersje rozwojowe, które zawierają wszystkie najnowsze funkcje i poprawki błędów, ale mogą mieć błędy i być mniej stabilne. - GUIgroupBox + GUIMusicGroupBox Odtwórz muzykę tytułową:\nJeśli gra to obsługuje, aktywuje odtwarzanie specjalnej muzyki podczas wybierania gry w GUI. diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index b9d889519..fde1d3b63 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -540,10 +540,18 @@ Enable Fullscreen Ativar Tela Cheia + + Fullscreen Mode + Modo de Tela Cheia + Enable Separate Update Folder Habilitar pasta de atualização separada + + Default tab when opening settings + Aba padrão ao abrir as configurações + Show Game Size In List Mostrar Tamanho do Jogo na Lista @@ -620,6 +628,14 @@ Graphics Gráficos + + Gui + Interface + + + User + Usuário + Graphics Device Placa de Vídeo @@ -766,7 +782,7 @@ fullscreenCheckBox - Ativar Tela Cheia:\nMove automaticamente a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. + Ativar Tela Cheia:\nAltera a janela do jogo para o modo tela cheia.\nIsso pode ser alterado pressionando a tecla F11. separateUpdatesCheckBox @@ -805,7 +821,11 @@ Atualizações:\nRelease: Versões oficiais que são lançadas todo mês e podem ser bastante antigas, mas são mais confiáveis e testadas.\nNightly: Versões de desenvolvimento que têm todos os novos recursos e correções, mas podem ter bugs e ser instáveis. - GUIgroupBox + chooseHomeTabGroupBox + do menu. + + + GUIMusicGroupBox Reproduzir música de abertura:\nSe o jogo suportar, ativa a reprodução de uma música especial ao selecionar o jogo na interface do menu. diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 00a9eb179..9791a7682 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Mod Ecran Complet + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Tab-ul implicit la deschiderea setărilor + Show Game Size In List Afișează dimensiunea jocului în listă @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Interfață + + + User + Utilizator + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Actualizare:\nRelease: Versiuni oficiale lansate în fiecare lună, care pot fi foarte învechite, dar sunt mai fiabile și testate.\nNightly: Versiuni de dezvoltare care conțin toate cele mai recente funcții și corecții, dar pot conține erori și sunt mai puțin stabile. - GUIgroupBox + GUIMusicGroupBox Redă muzica titlului:\nDacă un joc o suportă, activează redarea muzicii speciale când selectezi jocul în GUI. diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 4c90450dd..6423f5ba4 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -540,10 +540,18 @@ Enable Fullscreen Полноэкранный режим + + Fullscreen Mode + Режим Полного Экран + Enable Separate Update Folder Отдельная папка обновлений + + Default tab when opening settings + Вкладка по умолчанию при открытии настроек + Show Game Size In List Показать размер игры в списке @@ -620,6 +628,14 @@ Graphics Графика + + Gui + Интерфейс + + + User + Пользователь + Graphics Device Графическое устройство @@ -805,7 +821,7 @@ Обновление:\nRelease: Официальные версии, которые выпускаются каждый месяц и могут быть очень старыми, но они более надежные и проверенные.\nNightly: Версии разработки, которые содержат все последние функции и исправления, но могут содержать ошибки и менее стабильны. - GUIgroupBox + GUIMusicGroupBox Играть заглавную музыку:\nВключает воспроизведение специальной музыки при выборе игры в списке, если она это поддерживает. diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index 768db1e75..2f88b4390 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -540,10 +540,18 @@ Enable Fullscreen Aktivizo Ekranin e plotë + + Fullscreen Mode + Modaliteti i Plotë + Enable Separate Update Folder Aktivizo dosjen e ndarë të përditësimit + + Default tab when opening settings + Skeda e parazgjedhur kur hapni cilësimet + Show Game Size In List Shfaq madhësinë e lojës në listë @@ -620,6 +628,14 @@ Graphics Grafika + + Gui + Ndërfaqe + + + User + Përdorues + Graphics Device Pajisja e Grafikës @@ -805,7 +821,7 @@ Përditësimi:\nRelease: Versionet zyrtare të lëshuara çdo muaj që mund të jenë shumë të vjetra, por janë më të besueshme dhe të provuara.\nNightly: Versionet e zhvillimit që kanë të gjitha veçoritë dhe rregullimet më të fundit, por mund të përmbajnë gabime dhe janë më pak të qëndrueshme. - GUIgroupBox + GUIMusicGroupBox Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës të veçantë kur të zgjidhësh lojën në GUI. diff --git a/src/qt_gui/translations/sv.ts b/src/qt_gui/translations/sv.ts index 3781ba45c..e17944f12 100644 --- a/src/qt_gui/translations/sv.ts +++ b/src/qt_gui/translations/sv.ts @@ -1032,10 +1032,18 @@ Enable Fullscreen Aktivera helskärm + + Fullscreen Mode + Helskärmsläge + Enable Separate Update Folder Aktivera separat uppdateringsmapp + + Default tab when opening settings + Standardflik när inställningar öppnas + Show Game Size In List Visa spelstorlek i listan @@ -1108,6 +1116,14 @@ Graphics Grafik + + Gui + Gränssnitt + + + User + Användare + Graphics Device Grafikenhet @@ -1285,7 +1301,7 @@ updaterGroupBox - GUIgroupBox + GUIMusicGroupBox Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 5e8499073..6436278de 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -540,10 +540,18 @@ Enable Fullscreen Tam Ekranı Etkinleştir + + Fullscreen Mode + Tam Ekran Modu + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Ayarlar açıldığında varsayılan sekme + Show Game Size In List Göster oyun boyutunu listede @@ -620,6 +628,14 @@ Graphics Grafikler + + Gui + Arayüz + + + User + Kullanıcı + Graphics Device Grafik Cihazı @@ -805,7 +821,7 @@ Güncelleme:\nRelease: Her ay yayınlanan resmi sürümler; çok eski olabilirler, ancak daha güvenilirdir ve test edilmiştir.\nNightly: Tüm en son özellikler ve düzeltmeler ile birlikte geliştirme sürümleri; hatalar içerebilir ve daha az kararlıdırlar. - GUIgroupBox + GUIMusicGroupBox Başlık Müziklerini Çal:\nEğer bir oyun bunu destekliyorsa, GUI'de oyunu seçtiğinizde özel müziklerin çalmasını etkinleştirir. diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index a1c7e97e0..720ad5b99 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -540,10 +540,18 @@ Enable Fullscreen Увімкнути повноекранний режим + + Fullscreen Mode + Режим Повноекранний + Enable Separate Update Folder Увімкнути окрему папку оновлень + + Default tab when opening settings + Вкладка за замовчуванням при відкритті налаштувань + Show Game Size In List Показати розмір гри в списку @@ -620,6 +628,14 @@ Graphics Графіка + + Gui + Інтерфейс + + + User + Користувач + Graphics Device Графічний пристрій @@ -805,7 +821,7 @@ Оновлення:\nRelease: Офіційні версії, які випускаються щомісяця і можуть бути дуже старими, але вони більш надійні та перевірені.\nNightly: Версії для розробників, які мають усі найновіші функції та виправлення, але можуть містити помилки та є менш стабільними. - GUIgroupBox + GUIMusicGroupBox Грати заголовну музику:\nВмикає відтворення спеціальної музики під час вибору гри в списку, якщо вона це підтримує. diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index a579a1983..a81630fad 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + Chế độ Toàn màn hình + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + Tab mặc định khi mở cài đặt + Show Game Size In List Hiển thị Kích thước Game trong Danh sách @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + Giao diện + + + User + Người dùng + Graphics Device Graphics Device @@ -805,7 +821,7 @@ Cập nhật:\nRelease: Các phiên bản chính thức được phát hành hàng tháng; có thể khá cũ nhưng đáng tin cậy hơn và đã được thử nghiệm.\nNightly: Các phiên bản phát triển có tất cả các tính năng và sửa lỗi mới nhất; có thể có lỗi và ít ổn định hơn. - GUIgroupBox + GUIMusicGroupBox Phát nhạc tiêu đề trò chơi:\nNếu một trò chơi hỗ trợ điều này, hãy kích hoạt phát nhạc đặc biệt khi bạn chọn trò chơi trong GUI. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 5450f3dfd..b06b0a286 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -540,10 +540,18 @@ Enable Fullscreen 启用全屏 + + Fullscreen Mode + 全屏模式 + Enable Separate Update Folder 启用单独的更新目录 + + Default tab when opening settings + 打开设置时的默认选项卡 + Show Game Size In List 显示游戏大小在列表中 @@ -620,6 +628,14 @@ Graphics 图像 + + Gui + 界面 + + + User + 用户 + Graphics Device 图形设备 @@ -805,7 +821,7 @@ 更新:\nRelease:每月发布的官方版本可能非常过时,但更可靠且经过测试。\nNightly:包含所有最新功能和修复的开发版本,但可能包含错误且稳定性较低。 - GUIgroupBox + GUIMusicGroupBox 播放标题音乐:\n如果游戏支持,在图形界面选择游戏时播放特殊音乐。 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 0ce0b4d69..9f6758797 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -540,10 +540,18 @@ Enable Fullscreen Enable Fullscreen + + Fullscreen Mode + 全螢幕模式 + Enable Separate Update Folder Enable Separate Update Folder + + Default tab when opening settings + 打開設置時的默認選項卡 + Show Game Size In List 顯示遊戲大小在列表中 @@ -620,6 +628,14 @@ Graphics Graphics + + Gui + 介面 + + + User + 使用者 + Graphics Device Graphics Device @@ -805,7 +821,7 @@ 更新:\nRelease: 每月發布的官方版本,可能非常舊,但更可靠且經過測試。\nNightly: 開發版本,擁有所有最新的功能和修復,但可能包含錯誤,穩定性較差。 - GUIgroupBox + GUIMusicGroupBox 播放標題音樂:\n如果遊戲支持,啟用在GUI中選擇遊戲時播放特殊音樂。 From 81e7e4b21646ad40edaae06f9b1e58eaea139b3c Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 23 Jan 2025 00:52:22 +0200 Subject: [PATCH 57/65] Update building-linux.md --- documents/building-linux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index 2672c70f4..28b8c6056 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -99,7 +99,7 @@ Open `cmake-gui` and specify the source code and build directories. If you clone Click on Configure, select "Unix Makefiles", select "Specify native compilers", click Next and choose `clang` and `clang++` as the C and CXX compilers. Usually they are located in `/bin/clang` and `/bin/clang++`. Click on Finish and let it configure the project. -Now every option should be displayed in red. Change anything you want, then click on Generate to make the changes permanent, then open a terminal window and do steps 2 and 3 of Option 1. +Now every option should be displayed in red. Change anything you want, then click on Generate to make the changes permanent, then open a terminal window and do step 2 of Option 1. #### Option 3: Visual Studio Code @@ -131,4 +131,4 @@ If you want to debug it, change the build type under Project Status > Configure Don't forget to change the launch target for both options to the shadPS4 executable inside shadPS4/build: -![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/5.png) \ No newline at end of file +![image](https://raw.githubusercontent.com/shadps4-emu/shadPS4/refs/heads/main/documents/Screenshots/Linux/5.png) From cc2e13873f9be4e2f95e4da7a71e7333502fa183 Mon Sep 17 00:00:00 2001 From: DanielSvoboda Date: Wed, 22 Jan 2025 22:21:41 -0300 Subject: [PATCH 58/65] Fix showing debug menu bar / Devtools (#2214) * Fix showing debug menu bar / Devtools * Fix --- src/core/debug_state.cpp | 2 ++ src/core/debug_state.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/debug_state.cpp b/src/core/debug_state.cpp index 6508a9875..23ebcbb9b 100644 --- a/src/core/debug_state.cpp +++ b/src/core/debug_state.cpp @@ -17,6 +17,8 @@ using namespace DebugStateType; DebugStateImpl& DebugState = *Common::Singleton::Instance(); +bool DebugStateType::showing_debug_menu_bar = false; + static ThreadID ThisThreadID() { #ifdef _WIN32 return GetCurrentThreadId(); diff --git a/src/core/debug_state.h b/src/core/debug_state.h index a9e6f48b7..217efd1a9 100644 --- a/src/core/debug_state.h +++ b/src/core/debug_state.h @@ -35,6 +35,8 @@ class ShaderList; namespace DebugStateType { +extern bool showing_debug_menu_bar; + enum class QueueType { dcb = 0, ccb = 1, @@ -131,8 +133,6 @@ class DebugStateImpl { friend class Core::Devtools::Widget::FrameGraph; friend class Core::Devtools::Widget::ShaderList; - bool showing_debug_menu_bar = false; - std::queue debug_message_popup; std::mutex guest_threads_mutex{}; From cef92fbcaad34e0caa90bc5afbce5e5b30c43636 Mon Sep 17 00:00:00 2001 From: DemoJameson Date: Thu, 23 Jan 2025 19:28:44 +0800 Subject: [PATCH 59/65] Update Simplified Chinese translation (#2216) * Update Simplified Chinese translation * Fix incorrect translation * Fix new line --- src/qt_gui/translations/zh_CN.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index b06b0a286..16ae03f94 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -552,7 +552,7 @@ Default tab when opening settings 打开设置时的默认选项卡 - + Show Game Size In List 显示游戏大小在列表中 @@ -830,7 +830,7 @@ hideCursorGroupBox - 隐藏光标:\n选择光标何时消失:\n从不: 始终显示光标。\闲置: 光标在闲置若干秒后消失。\n始终: 始终显示光标。 + 隐藏光标:\n选择光标何时消失:\n从不: 从不隐藏光标。\n闲置:光标在闲置若干秒后消失。\n始终:始终隐藏光标。 idleTimeoutGroupBox @@ -928,6 +928,14 @@ rdocCheckBox 启用 RenderDoc 调试:\n启用后模拟器将提供与 Renderdoc 的兼容性,允许在渲染过程中捕获和分析当前渲染的帧。 + + saveDataBox + 存档数据路径:\n保存游戏存档数据的目录。 + + + browseButton + 浏览:\n选择一个目录保存游戏存档数据。 + CheatsPatches From fb738bc24739704abcc87d87f25dd6a241c9719d Mon Sep 17 00:00:00 2001 From: Angelo Rosa Date: Thu, 23 Jan 2025 05:54:42 -0800 Subject: [PATCH 60/65] Optimize workflows by caching `apt install` (#2212) * optimize apt caches * change on push syntax * removing comments and check time improvements on following pushes * add recursive submodule fetch * Revert "add recursive submodule fetch" This reverts commit b059bda3b88aa7d81e4bee3348c9104536b15fd2. * restore old push on everyhing rule * fix libfuse2 uncaching package * moving qt deps outside from caching directives --- .github/workflows/build.yml | 48 ++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3da7163dd..a49ffefe9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,20 +24,27 @@ jobs: runs-on: ubuntu-24.04 continue-on-error: true steps: + - uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Install run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' - sudo apt update - sudo apt install clang-format-18 + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: clang-format-18 + version: 1.0 + - name: Build env: COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} run: ./.ci/clang-format.sh - + + get-info: runs-on: ubuntu-24.04 outputs: @@ -282,7 +289,13 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: | + sudo apt install -y libfuse2 + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + version: 1.0 - name: Cache CMake Configuration uses: actions/cache@v4 @@ -338,7 +351,13 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: | + sudo apt install -y libfuse2 qt6-base-dev qt6-tools-dev qt6-multimedia-dev + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + version: 1.0 - name: Cache CMake Configuration uses: actions/cache@v4 @@ -385,7 +404,13 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: | + sudo apt install -y libfuse2 + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + version: 1.0 - name: Cache CMake Configuration uses: actions/cache@v4 @@ -421,7 +446,13 @@ jobs: submodules: recursive - name: Install dependencies - run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev + run: | + sudo apt install -y libfuse2 qt6-base-dev qt6-tools-dev qt6-multimedia-dev + + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev + version: 1.0 - name: Cache CMake Configuration uses: actions/cache@v4 @@ -443,7 +474,8 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: | + cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) From 2e6c9b8f9866f48bd39dc35b25310e77e3fa89f6 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 23 Jan 2025 22:58:45 +0200 Subject: [PATCH 61/65] Revert "Optimize workflows by caching `apt install` (#2212)" (#2220) This reverts commit fb738bc24739704abcc87d87f25dd6a241c9719d. --- .github/workflows/build.yml | 48 +++++++------------------------------ 1 file changed, 8 insertions(+), 40 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a49ffefe9..3da7163dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,27 +24,20 @@ jobs: runs-on: ubuntu-24.04 continue-on-error: true steps: - - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Install run: | wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' - - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: clang-format-18 - version: 1.0 - + sudo apt update + sudo apt install clang-format-18 - name: Build env: COMMIT_RANGE: ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} run: ./.ci/clang-format.sh - - + get-info: runs-on: ubuntu-24.04 outputs: @@ -289,13 +282,7 @@ jobs: submodules: recursive - name: Install dependencies - run: | - sudo apt install -y libfuse2 - - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - version: 1.0 + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -351,13 +338,7 @@ jobs: submodules: recursive - name: Install dependencies - run: | - sudo apt install -y libfuse2 qt6-base-dev qt6-tools-dev qt6-multimedia-dev - - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev clang build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - version: 1.0 + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -404,13 +385,7 @@ jobs: submodules: recursive - name: Install dependencies - run: | - sudo apt install -y libfuse2 - - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - version: 1.0 + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -446,13 +421,7 @@ jobs: submodules: recursive - name: Install dependencies - run: | - sudo apt install -y libfuse2 qt6-base-dev qt6-tools-dev qt6-multimedia-dev - - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev gcc-14 build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev - version: 1.0 + run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev libudev-dev - name: Cache CMake Configuration uses: actions/cache@v4 @@ -474,8 +443,7 @@ jobs: key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} - name: Configure CMake - run: | - cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake --fresh -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel $(nproc) From cdca420a2e8579832c7ebffb7ec2ed8f9e74030c Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 23 Jan 2025 23:27:37 +0200 Subject: [PATCH 62/65] Update building-linux.md --- documents/building-linux.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index 28b8c6056..eedcd6664 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -29,8 +29,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-co sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers ``` -**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses GCC as the compiler as opposed to Clang. Use at your own discretion. -#### OpenSUSE +**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.#### OpenSUSE ``` sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers From 0ebe817a28afc14511578d183f8ef2afce3905aa Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 23 Jan 2025 23:46:15 +0200 Subject: [PATCH 63/65] Update building-linux.md --- documents/building-linux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documents/building-linux.md b/documents/building-linux.md index eedcd6664..2342e9afc 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -29,7 +29,7 @@ sudo dnf install clang git cmake libatomic alsa-lib-devel pipewire-jack-audio-co sudo pacman -S base-devel clang git cmake sndio jack2 openal qt6-base qt6-declarative qt6-multimedia sdl2 vulkan-validation-layers ``` -**Note**: The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.#### OpenSUSE +**Note** : The `shadps4-git` AUR package is not maintained by any of the developers, and it uses the default compiler, which is often set to GCC. Use at your own discretion.#### OpenSUSE ``` sudo zypper install clang git cmake libasound2 libpulse-devel libsndio7 libjack-devel openal-soft-devel libopenssl-devel zlib-devel libedit-devel systemd-devel libevdev-devel qt6-base-devel qt6-multimedia-devel qt6-svg-devel qt6-linguist-devel qt6-gui-private-devel vulkan-devel vulkan-validationlayers From cc4ddd28c3f3cfe978a0be75131a99ca4e86851b Mon Sep 17 00:00:00 2001 From: Log3rinioo <80830849+Log3rinioo@users.noreply.github.com> Date: Thu, 23 Jan 2025 22:56:06 +0100 Subject: [PATCH 64/65] Add missing Polish translations and fix typos (#2222) --- src/qt_gui/translations/pl_PL.ts | 86 ++++++++++++++++---------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 528e88337..85eb63bfb 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -52,7 +52,7 @@ Select which directory you want to install to. - Select which directory you want to install to. + Wybierz katalog, do którego chcesz zainstalować. @@ -130,35 +130,35 @@ Delete... - Delete... + Usuń... Delete Game - Delete Game + Usuń Grę Delete Update - Delete Update + Usuń Aktualizację Delete DLC - Delete DLC + Usuń DLC Compatibility... - Compatibility... + kompatybilność... Update database - Update database + Zaktualizuj bazę danych View report - View report + Wyświetl zgłoszenie Submit a report - Submit a report + Wyślij zgłoszenie Shortcut creation @@ -182,23 +182,23 @@ Game - Game + Gra requiresEnableSeparateUpdateFolder_MSG - This feature requires the 'Enable Separate Update Folder' config option to work. If you want to use this feature, please enable it. + Ta funkcja wymaga do działania opcji „Włącz oddzielny folder aktualizacji”. Jeśli chcesz korzystać z tej funkcji, włącz ją. This game has no update to delete! - This game has no update to delete! + Ta gra nie ma aktualizacji do usunięcia! Update - Update + Aktualizacja This game has no DLC to delete! - This game has no DLC to delete! + Ta gra nie ma DLC do usunięcia! DLC @@ -206,11 +206,11 @@ Delete %1 - Delete %1 + Usuń %1 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Czy na pewno chcesz usunąć katalog %1 z %2? @@ -249,7 +249,7 @@ Open shadPS4 Folder - Open shadPS4 Folder + Otwórz folder shadPS4 Exit @@ -546,7 +546,7 @@ Enable Separate Update Folder - Enable Separate Update Folder + Włącz oddzielny folder aktualizacji Default tab when opening settings @@ -574,11 +574,11 @@ Trophy Key - Trophy Key + Klucz trofeów Trophy - Trophy + Trofeum Logger @@ -722,7 +722,7 @@ Disable Trophy Pop-ups - Disable Trophy Pop-ups + Wyłącz wyskakujące okienka trofeów Play title music @@ -730,19 +730,19 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + Aktualizuj bazę danych zgodności podczas uruchamiania Game Compatibility - Game Compatibility + Kompatybilność gier Display Compatibility Data - Display Compatibility Data + Wyświetl dane zgodności Update Compatibility Database - Update Compatibility Database + Aktualizuj bazę danych zgodności Volume @@ -750,7 +750,7 @@ Audio Backend - Audio Backend + Zaplecze audio Save @@ -786,7 +786,7 @@ separateUpdatesCheckBox - Enable Separate Update Folder:\nEnables installing game updates into a separate folder for easy management. + Włącz oddzielny folder aktualizacji:\nUmożliwia instalowanie aktualizacji gier w oddzielnym folderze w celu łatwego zarządzania. showSplashCheckBox @@ -798,7 +798,7 @@ discordRPCCheckbox - Włącz Discord Rich Presence:\nWyświetla ikonę emuladora i odpowiednie informacje na twoim profilu Discord. + Włącz Discord Rich Presence:\nWyświetla ikonę emulatora i odpowiednie informacje na twoim profilu Discord. userName @@ -806,7 +806,7 @@ TrophyKey - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Klucz trofeów:\nKlucz używany do odszyfrowywania trofeów. Musi być uzyskany z konsoli po jailbreaku. Musi zawierać tylko znaki w kodzie szesnastkowym. logTypeGroupBox @@ -826,7 +826,7 @@ disableTrophycheckBox - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Wyłącz wyskakujące okienka trofeów:\nWyłącz powiadomienia o trofeach w grze. Postępy w zdobywaniu trofeów można nadal śledzić za pomocą przeglądarki trofeów (kliknij prawym przyciskiem myszy grę w oknie głównym). hideCursorGroupBox @@ -842,15 +842,15 @@ enableCompatibilityCheckBox - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Wyświetl dane zgodności:\nWyświetla informacje o kompatybilności gry w widoku tabeli. Włącz opcję „Aktualizuj zgodność przy uruchomieniu”, aby uzyskać aktualne informacje. checkCompatibilityOnStartupCheckBox - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Aktualizuj zgodność przy uruchomieniu:\nAutomatycznie aktualizuj bazę danych kompatybilności podczas uruchamiania shadPS4. updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + Zaktualizuj bazę danych zgodności:\nNatychmiast zaktualizuj bazę danych zgodności. Never @@ -933,7 +933,7 @@ CheatsPatches Cheats / Patches for - Cheats / Patches for + Kody / Łatki dla defaultTextEdit_MSG @@ -1145,7 +1145,7 @@ Failed to parse JSON: - Nie udało się przeanlizować JSON: + Nie udało się przeanalizować JSON: Can't apply cheats before the game is started @@ -1168,7 +1168,7 @@ Compatibility - Compatibility + Zgodność Region @@ -1196,7 +1196,7 @@ Never Played - Never Played + Nigdy nie grane h @@ -1212,27 +1212,27 @@ Compatibility is untested - Compatibility is untested + Kompatybilność nie została przetestowana Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + Gra nie inicjuje się poprawnie / zawiesza się emulator Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + Gra uruchamia się, ale wyświetla tylko pusty ekran Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + Gra wyświetla obraz, ale nie przechodzi do menu Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + Gra ma usterki przerywające rozgrywkę lub niegrywalną wydajność Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + Grę można ukończyć z grywalną wydajnością i bez większych usterek From e652369f22fc7cbdd456ce338dd1dae0974c69a4 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 23 Jan 2025 23:58:43 +0200 Subject: [PATCH 65/65] sdl3 update (#2221) --- externals/MoltenVK/MoltenVK | 2 +- externals/sdl3 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 9f0b616d9..2473ce6f0 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 9f0b616d9e2c39464d2a859b79dbc655c4a30e7e +Subproject commit 2473ce6f0ab7d5d8a49aa91b2e37f3447a939f18 diff --git a/externals/sdl3 b/externals/sdl3 index 22422f774..a336b62d8 160000 --- a/externals/sdl3 +++ b/externals/sdl3 @@ -1 +1 @@ -Subproject commit 22422f7748d5128135995ed34c8f8012861c7332 +Subproject commit a336b62d8b0b97b09214e053203e442e2b6e2be5