diff options
29 files changed, 604 insertions, 172 deletions
| diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 25b22a281..eb05e46a8 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -78,6 +78,7 @@ add_library(common STATIC      logging/types.h      lz4_compression.cpp      lz4_compression.h +    make_unique_for_overwrite.h      math_util.h      memory_detect.cpp      memory_detect.h @@ -101,6 +102,7 @@ add_library(common STATIC      ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp      scm_rev.h      scope_exit.h +    scratch_buffer.h      settings.cpp      settings.h      settings_input.cpp diff --git a/src/common/make_unique_for_overwrite.h b/src/common/make_unique_for_overwrite.h new file mode 100644 index 000000000..c7413cf51 --- /dev/null +++ b/src/common/make_unique_for_overwrite.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> +#include <type_traits> + +namespace Common { + +template <class T> +requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() { +    return std::unique_ptr<T>(new T); +} + +template <class T> +requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) { +    return std::unique_ptr<T>(new std::remove_extent_t<T>[n]); +} + +template <class T, class... Args> +requires std::is_bounded_array_v<T> +void make_unique_for_overwrite(Args&&...) = delete; + +} // namespace Common diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h new file mode 100644 index 000000000..1245a5086 --- /dev/null +++ b/src/common/scratch_buffer.h @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/make_unique_for_overwrite.h" + +namespace Common { + +/** + * ScratchBuffer class + * This class creates a default initialized heap allocated buffer for cases such as intermediate + * buffers being copied into entirely, where value initializing members during allocation or resize + * is redundant. + */ +template <typename T> +class ScratchBuffer { +public: +    ScratchBuffer() = default; + +    explicit ScratchBuffer(size_t initial_capacity) +        : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity}, +          buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {} + +    ~ScratchBuffer() = default; + +    /// This will only grow the buffer's capacity if size is greater than the current capacity. +    /// The previously held data will remain intact. +    void resize(size_t size) { +        if (size > buffer_capacity) { +            auto new_buffer = Common::make_unique_for_overwrite<T[]>(size); +            std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get()); +            buffer = std::move(new_buffer); +            buffer_capacity = size; +        } +        last_requested_size = size; +    } + +    /// This will only grow the buffer's capacity if size is greater than the current capacity. +    /// The previously held data will be destroyed if a reallocation occurs. +    void resize_destructive(size_t size) { +        if (size > buffer_capacity) { +            buffer_capacity = size; +            buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity); +        } +        last_requested_size = size; +    } + +    [[nodiscard]] T* data() noexcept { +        return buffer.get(); +    } + +    [[nodiscard]] const T* data() const noexcept { +        return buffer.get(); +    } + +    [[nodiscard]] T* begin() noexcept { +        return data(); +    } + +    [[nodiscard]] const T* begin() const noexcept { +        return data(); +    } + +    [[nodiscard]] T* end() noexcept { +        return data() + last_requested_size; +    } + +    [[nodiscard]] const T* end() const noexcept { +        return data() + last_requested_size; +    } + +    [[nodiscard]] T& operator[](size_t i) { +        return buffer[i]; +    } + +    [[nodiscard]] const T& operator[](size_t i) const { +        return buffer[i]; +    } + +    [[nodiscard]] size_t size() const noexcept { +        return last_requested_size; +    } + +    [[nodiscard]] size_t capacity() const noexcept { +        return buffer_capacity; +    } + +private: +    size_t last_requested_size{}; +    size_t buffer_capacity{}; +    std::unique_ptr<T[]> buffer{}; +}; + +} // namespace Common diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index f238d6ccd..5587ee097 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -210,6 +210,13 @@ void EmulatedController::LoadTASParams() {      tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);      tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);      tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3); + +    // set to optimal stick to avoid sanitizing the stick and tweaking the coordinates +    // making sure they play back in the game as originally written down in the script file +    tas_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f); +    tas_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f); +    tas_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f); +    tas_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);  }  void EmulatedController::LoadVirtualGamepadParams() { diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 10cd4c43d..0aa68103c 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp @@ -6,6 +6,7 @@  #include "core/hle/kernel/k_page_table.h"  #include "core/hle/kernel/k_scoped_resource_reservation.h"  #include "core/hle/kernel/k_shared_memory.h" +#include "core/hle/kernel/k_system_resource.h"  #include "core/hle/kernel/kernel.h"  #include "core/hle/kernel/svc_results.h" @@ -18,19 +19,19 @@ KSharedMemory::~KSharedMemory() {  }  Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_, -                                 KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_, -                                 Svc::MemoryPermission user_permission_, PAddr physical_address_, -                                 std::size_t size_, std::string name_) { +                                 Svc::MemoryPermission owner_permission_, +                                 Svc::MemoryPermission user_permission_, std::size_t size_, +                                 std::string name_) {      // Set members.      owner_process = owner_process_;      device_memory = &device_memory_; -    page_list = std::move(page_list_);      owner_permission = owner_permission_;      user_permission = user_permission_; -    physical_address = physical_address_; -    size = size_; +    size = Common::AlignUp(size_, PageSize);      name = std::move(name_); +    const size_t num_pages = Common::DivideUp(size, PageSize); +      // Get the resource limit.      KResourceLimit* reslimit = kernel.GetSystemResourceLimit(); @@ -39,6 +40,17 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o                                                    size_);      R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached); +    // Allocate the memory. + +    //! HACK: Open continuous mapping from sysmodule pool. +    auto option = KMemoryManager::EncodeOption(KMemoryManager::Pool::Secure, +                                               KMemoryManager::Direction::FromBack); +    physical_address = kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option); +    R_UNLESS(physical_address != 0, ResultOutOfMemory); + +    //! Insert the result into our page group. +    page_group.emplace(physical_address, num_pages); +      // Commit our reservation.      memory_reservation.Commit(); @@ -50,12 +62,23 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o      is_initialized = true;      // Clear all pages in the memory. -    std::memset(device_memory_.GetPointer<void>(physical_address_), 0, size_); +    for (const auto& block : page_group->Nodes()) { +        std::memset(device_memory_.GetPointer<void>(block.GetAddress()), 0, block.GetSize()); +    }      return ResultSuccess;  }  void KSharedMemory::Finalize() { +    // Close and finalize the page group. +    // page_group->Close(); +    // page_group->Finalize(); + +    //! HACK: Manually close. +    for (const auto& block : page_group->Nodes()) { +        kernel.MemoryManager().Close(block.GetAddress(), block.GetNumPages()); +    } +      // Release the memory reservation.      resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);      resource_limit->Close(); @@ -65,32 +88,28 @@ void KSharedMemory::Finalize() {  }  Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t map_size, -                          Svc::MemoryPermission permissions) { -    const u64 page_count{(map_size + PageSize - 1) / PageSize}; - -    if (page_list.GetNumPages() != page_count) { -        UNIMPLEMENTED_MSG("Page count does not match"); -    } +                          Svc::MemoryPermission map_perm) { +    // Validate the size. +    R_UNLESS(size == map_size, ResultInvalidSize); -    const Svc::MemoryPermission expected = +    // Validate the permission. +    const Svc::MemoryPermission test_perm =          &target_process == owner_process ? owner_permission : user_permission; - -    if (permissions != expected) { -        UNIMPLEMENTED_MSG("Permission does not match"); +    if (test_perm == Svc::MemoryPermission::DontCare) { +        ASSERT(map_perm == Svc::MemoryPermission::Read || map_perm == Svc::MemoryPermission::Write); +    } else { +        R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission);      } -    return target_process.PageTable().MapPages(address, page_list, KMemoryState::Shared, -                                               ConvertToKMemoryPermission(permissions)); +    return target_process.PageTable().MapPages(address, *page_group, KMemoryState::Shared, +                                               ConvertToKMemoryPermission(map_perm));  }  Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) { -    const u64 page_count{(unmap_size + PageSize - 1) / PageSize}; - -    if (page_list.GetNumPages() != page_count) { -        UNIMPLEMENTED_MSG("Page count does not match"); -    } +    // Validate the size. +    R_UNLESS(size == unmap_size, ResultInvalidSize); -    return target_process.PageTable().UnmapPages(address, page_list, KMemoryState::Shared); +    return target_process.PageTable().UnmapPages(address, *page_group, KMemoryState::Shared);  }  } // namespace Kernel diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h index a96c55a3e..8b29f0b4a 100644 --- a/src/core/hle/kernel/k_shared_memory.h +++ b/src/core/hle/kernel/k_shared_memory.h @@ -3,6 +3,7 @@  #pragma once +#include <optional>  #include <string>  #include "common/common_types.h" @@ -26,9 +27,8 @@ public:      ~KSharedMemory() override;      Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_, -                      KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_, -                      Svc::MemoryPermission user_permission_, PAddr physical_address_, -                      std::size_t size_, std::string name_); +                      Svc::MemoryPermission owner_permission_, +                      Svc::MemoryPermission user_permission_, std::size_t size_, std::string name_);      /**       * Maps a shared memory block to an address in the target process' address space @@ -76,7 +76,7 @@ public:  private:      Core::DeviceMemory* device_memory{};      KProcess* owner_process{}; -    KPageGroup page_list; +    std::optional<KPageGroup> page_group{};      Svc::MemoryPermission owner_permission{};      Svc::MemoryPermission user_permission{};      PAddr physical_address{}; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index b75bac5df..1fb25f221 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -94,6 +94,7 @@ struct KernelCore::Impl {                                         pt_heap_region.GetSize());          } +        InitializeHackSharedMemory();          RegisterHostThread(nullptr);          default_service_thread = &CreateServiceThread(kernel, "DefaultServiceThread"); @@ -726,14 +727,14 @@ struct KernelCore::Impl {      }      void InitializeMemoryLayout() { -        const auto system_pool = memory_layout->GetKernelSystemPoolRegionPhysicalExtents(); -          // Initialize the memory manager.          memory_manager = std::make_unique<KMemoryManager>(system);          const auto& management_region = memory_layout->GetPoolManagementRegion();          ASSERT(management_region.GetEndAddress() != 0);          memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize()); +    } +    void InitializeHackSharedMemory() {          // Setup memory regions for emulated processes          // TODO(bunnei): These should not be hardcoded regions initialized within the kernel          constexpr std::size_t hid_size{0x40000}; @@ -742,39 +743,23 @@ struct KernelCore::Impl {          constexpr std::size_t time_size{0x1000};          constexpr std::size_t hidbus_size{0x1000}; -        const PAddr hid_phys_addr{system_pool.GetAddress()}; -        const PAddr font_phys_addr{system_pool.GetAddress() + hid_size}; -        const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size}; -        const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size}; -        const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size + -                                     time_size}; -          hid_shared_mem = KSharedMemory::Create(system.Kernel());          font_shared_mem = KSharedMemory::Create(system.Kernel());          irs_shared_mem = KSharedMemory::Create(system.Kernel());          time_shared_mem = KSharedMemory::Create(system.Kernel());          hidbus_shared_mem = KSharedMemory::Create(system.Kernel()); -        hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, -                                   {hid_phys_addr, hid_size / PageSize}, -                                   Svc::MemoryPermission::None, Svc::MemoryPermission::Read, -                                   hid_phys_addr, hid_size, "HID:SharedMemory"); -        font_shared_mem->Initialize(system.DeviceMemory(), nullptr, -                                    {font_phys_addr, font_size / PageSize}, -                                    Svc::MemoryPermission::None, Svc::MemoryPermission::Read, -                                    font_phys_addr, font_size, "Font:SharedMemory"); -        irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, -                                   {irs_phys_addr, irs_size / PageSize}, -                                   Svc::MemoryPermission::None, Svc::MemoryPermission::Read, -                                   irs_phys_addr, irs_size, "IRS:SharedMemory"); -        time_shared_mem->Initialize(system.DeviceMemory(), nullptr, -                                    {time_phys_addr, time_size / PageSize}, -                                    Svc::MemoryPermission::None, Svc::MemoryPermission::Read, -                                    time_phys_addr, time_size, "Time:SharedMemory"); -        hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, -                                      {hidbus_phys_addr, hidbus_size / PageSize}, -                                      Svc::MemoryPermission::None, Svc::MemoryPermission::Read, -                                      hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory"); +        hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, +                                   Svc::MemoryPermission::Read, hid_size, "HID:SharedMemory"); +        font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, +                                    Svc::MemoryPermission::Read, font_size, "Font:SharedMemory"); +        irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, +                                   Svc::MemoryPermission::Read, irs_size, "IRS:SharedMemory"); +        time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, +                                    Svc::MemoryPermission::Read, time_size, "Time:SharedMemory"); +        hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None, +                                      Svc::MemoryPermission::Read, hidbus_size, +                                      "HidBus:SharedMemory");      }      KClientPort* CreateNamedServicePort(std::string name) { diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h index ef070f32f..ed1eb5b2d 100644 --- a/src/core/hle/service/time/clock_types.h +++ b/src/core/hle/service/time/clock_types.h @@ -49,6 +49,7 @@ struct SteadyClockContext {  static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size");  static_assert(std::is_trivially_copyable_v<SteadyClockContext>,                "SteadyClockContext must be trivially copyable"); +using StandardSteadyClockTimePointType = SteadyClockContext;  struct SystemClockContext {      s64 offset; diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp index a3aa0e77f..ff53a7d6f 100644 --- a/src/core/hle/service/time/time_sharedmemory.cpp +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -26,23 +26,24 @@ void SharedMemory::SetupStandardSteadyClock(const Common::UUID& clock_source_id,      const Clock::SteadyClockContext context{          static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),          clock_source_id}; -    shared_memory_format.standard_steady_clock_timepoint.StoreData( -        system.Kernel().GetTimeSharedMem().GetPointer(), context); +    StoreToLockFreeAtomicType(&GetFormat()->standard_steady_clock_timepoint, context);  }  void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) { -    shared_memory_format.standard_local_system_clock_context.StoreData( -        system.Kernel().GetTimeSharedMem().GetPointer(), context); +    StoreToLockFreeAtomicType(&GetFormat()->standard_local_system_clock_context, context);  }  void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) { -    shared_memory_format.standard_network_system_clock_context.StoreData( -        system.Kernel().GetTimeSharedMem().GetPointer(), context); +    StoreToLockFreeAtomicType(&GetFormat()->standard_network_system_clock_context, context);  }  void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) { -    shared_memory_format.standard_user_system_clock_automatic_correction.StoreData( -        system.Kernel().GetTimeSharedMem().GetPointer(), is_enabled); +    StoreToLockFreeAtomicType( +        &GetFormat()->is_standard_user_system_clock_automatic_correction_enabled, is_enabled); +} + +SharedMemory::Format* SharedMemory::GetFormat() { +    return reinterpret_cast<SharedMemory::Format*>(system.Kernel().GetTimeSharedMem().GetPointer());  }  } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h index 561685acd..044a4d24e 100644 --- a/src/core/hle/service/time/time_sharedmemory.h +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -10,45 +10,68 @@  namespace Service::Time { +// Note: this type is not safe for concurrent writes. +template <typename T> +struct LockFreeAtomicType { +    u32 counter_; +    std::array<T, 2> value_; +}; + +template <typename T> +static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) { +    // Get the current counter. +    auto counter = p->counter_; + +    // Increment the counter. +    ++counter; + +    // Store the updated value. +    p->value_[counter % 2] = value; + +    // Fence memory. +    std::atomic_thread_fence(std::memory_order_release); + +    // Set the updated counter. +    p->counter_ = counter; +} + +template <typename T> +static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) { +    while (true) { +        // Get the counter. +        auto counter = p->counter_; + +        // Get the value. +        auto value = p->value_[counter % 2]; + +        // Fence memory. +        std::atomic_thread_fence(std::memory_order_acquire); + +        // Check that the counter matches. +        if (counter == p->counter_) { +            return value; +        } +    } +} +  class SharedMemory final {  public:      explicit SharedMemory(Core::System& system_);      ~SharedMemory(); -    // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this? -    template <typename T, std::size_t Offset> -    struct MemoryBarrier { -        static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); -        u32_le read_attempt{}; -        std::array<T, 2> data{}; - -        // These are not actually memory barriers at the moment as we don't have multicore and all -        // HLE is mutexed. This will need to properly be implemented when we start updating the time -        // points on threads. As of right now, we'll be updated both values synchronously and just -        // incrementing the read_attempt to indicate that we waited. -        void StoreData(u8* shared_memory, T data_to_store) { -            std::memcpy(this, shared_memory + Offset, sizeof(*this)); -            read_attempt++; -            data[read_attempt & 1] = data_to_store; -            std::memcpy(shared_memory + Offset, this, sizeof(*this)); -        } - -        // For reading we're just going to read the last stored value. If there was no value stored -        // it will just end up reading an empty value as intended. -        T ReadData(u8* shared_memory) { -            std::memcpy(this, shared_memory + Offset, sizeof(*this)); -            return data[(read_attempt - 1) & 1]; -        } -    }; -      // Shared memory format      struct Format { -        MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint; -        MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context; -        MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context; -        MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; -        u32_le format_version; +        LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint; +        LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context; +        LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context; +        LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled; +        u32 format_version;      }; +    static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0); +    static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38); +    static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80); +    static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) == +                  0xc8);      static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");      void SetupStandardSteadyClock(const Common::UUID& clock_source_id, @@ -56,10 +79,10 @@ public:      void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);      void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);      void SetAutomaticCorrectionEnabled(bool is_enabled); +    Format* GetFormat();  private:      Core::System& system; -    Format shared_memory_format{};  };  } // namespace Service::Time diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 348d1edf4..6a4022e45 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(tests      common/host_memory.cpp      common/param_package.cpp      common/ring_buffer.cpp +    common/scratch_buffer.cpp      common/unique_function.cpp      core/core_timing.cpp      core/internal_network/network.cpp diff --git a/src/tests/common/scratch_buffer.cpp b/src/tests/common/scratch_buffer.cpp new file mode 100644 index 000000000..f6e50da4a --- /dev/null +++ b/src/tests/common/scratch_buffer.cpp @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <algorithm> +#include <array> +#include <cstring> +#include <span> +#include <catch2/catch.hpp> +#include "common/common_types.h" +#include "common/scratch_buffer.h" + +namespace Common { + +TEST_CASE("ScratchBuffer: Basic Test", "[common]") { +    ScratchBuffer<u8> buf; + +    REQUIRE(buf.size() == 0U); +    REQUIRE(buf.capacity() == 0U); + +    std::array<u8, 10> payload; +    payload.fill(66); + +    buf.resize(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: resize_destructive Grow", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    // Increasing the size should reallocate the buffer +    buf.resize_destructive(payload.size() * 2); +    REQUIRE(buf.size() == payload.size() * 2); +    REQUIRE(buf.capacity() == payload.size() * 2); + +    // Since the buffer is not value initialized, reading its data will be garbage +} + +TEST_CASE("ScratchBuffer: resize_destructive Shrink", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    // Decreasing the size should not cause a buffer reallocation +    // This can be tested by ensuring the buffer capacity and data has not changed, +    buf.resize_destructive(1U); +    REQUIRE(buf.size() == 1U); +    REQUIRE(buf.capacity() == payload.size()); + +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: resize Grow u8", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    // Increasing the size should reallocate the buffer +    buf.resize(payload.size() * 2); +    REQUIRE(buf.size() == payload.size() * 2); +    REQUIRE(buf.capacity() == payload.size() * 2); + +    // resize() keeps the previous data intact +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: resize Grow u64", "[common]") { +    std::array<u64, 10> payload; +    payload.fill(6666); + +    ScratchBuffer<u64> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size() * sizeof(u64)); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    // Increasing the size should reallocate the buffer +    buf.resize(payload.size() * 2); +    REQUIRE(buf.size() == payload.size() * 2); +    REQUIRE(buf.capacity() == payload.size() * 2); + +    // resize() keeps the previous data intact +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: resize Shrink", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    // Decreasing the size should not cause a buffer reallocation +    // This can be tested by ensuring the buffer capacity and data has not changed, +    buf.resize(1U); +    REQUIRE(buf.size() == 1U); +    REQUIRE(buf.capacity() == payload.size()); + +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: Span Size", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    buf.resize(3U); +    REQUIRE(buf.size() == 3U); +    REQUIRE(buf.capacity() == payload.size()); + +    const auto buf_span = std::span<u8>(buf); +    // The span size is the last requested size of the buffer, not its capacity +    REQUIRE(buf_span.size() == buf.size()); + +    for (size_t i = 0; i < buf_span.size(); ++i) { +        REQUIRE(buf_span[i] == buf[i]); +        REQUIRE(buf_span[i] == payload[i]); +    } +} + +TEST_CASE("ScratchBuffer: Span Writes", "[common]") { +    std::array<u8, 10> payload; +    payload.fill(66); + +    ScratchBuffer<u8> buf(payload.size()); +    REQUIRE(buf.size() == payload.size()); +    REQUIRE(buf.capacity() == payload.size()); + +    std::memcpy(buf.data(), payload.data(), payload.size()); +    for (size_t i = 0; i < payload.size(); ++i) { +        REQUIRE(buf[i] == payload[i]); +    } + +    buf.resize(3U); +    REQUIRE(buf.size() == 3U); +    REQUIRE(buf.capacity() == payload.size()); + +    const auto buf_span = std::span<u8>(buf); +    REQUIRE(buf_span.size() == buf.size()); + +    for (size_t i = 0; i < buf_span.size(); ++i) { +        const auto new_value = static_cast<u8>(i + 1U); +        // Writes to a span of the scratch buffer will propogate to the buffer itself +        buf_span[i] = new_value; +        REQUIRE(buf[i] == new_value); +    } +} + +} // namespace Common diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 502b4d90a..6c8d98946 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -20,6 +20,7 @@  #include "common/lru_cache.h"  #include "common/microprofile.h"  #include "common/polyfill_ranges.h" +#include "common/scratch_buffer.h"  #include "common/settings.h"  #include "core/memory.h"  #include "video_core/buffer_cache/buffer_base.h" @@ -422,8 +423,7 @@ private:      IntervalSet common_ranges;      std::deque<IntervalSet> committed_ranges; -    size_t immediate_buffer_capacity = 0; -    std::unique_ptr<u8[]> immediate_buffer_alloc; +    Common::ScratchBuffer<u8> immediate_buffer_alloc;      struct LRUItemParams {          using ObjectType = BufferId; @@ -1926,11 +1926,8 @@ std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size  template <class P>  std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) { -    if (wanted_capacity > immediate_buffer_capacity) { -        immediate_buffer_capacity = wanted_capacity; -        immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity); -    } -    return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity); +    immediate_buffer_alloc.resize_destructive(wanted_capacity); +    return std::span<u8>(immediate_buffer_alloc.data(), wanted_capacity);  }  template <class P> diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp index 9835e3ac1..322de2606 100644 --- a/src/video_core/dma_pusher.cpp +++ b/src/video_core/dma_pusher.cpp @@ -56,7 +56,7 @@ bool DmaPusher::Step() {      if (command_list.prefetch_command_list.size()) {          // Prefetched command list from nvdrv, used for things like synchronization -        command_headers = std::move(command_list.prefetch_command_list); +        ProcessCommands(command_list.prefetch_command_list);          dma_pushbuffer.pop();      } else {          const CommandListHeader command_list_header{ @@ -74,7 +74,7 @@ bool DmaPusher::Step() {          }          // Push buffer non-empty, read a word -        command_headers.resize(command_list_header.size); +        command_headers.resize_destructive(command_list_header.size);          if (Settings::IsGPULevelHigh()) {              memory_manager.ReadBlock(dma_get, command_headers.data(),                                       command_list_header.size * sizeof(u32)); @@ -82,16 +82,21 @@ bool DmaPusher::Step() {              memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),                                             command_list_header.size * sizeof(u32));          } +        ProcessCommands(command_headers);      } -    for (std::size_t index = 0; index < command_headers.size();) { -        const CommandHeader& command_header = command_headers[index]; + +    return true; +} + +void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) { +    for (std::size_t index = 0; index < commands.size();) { +        const CommandHeader& command_header = commands[index];          if (dma_state.method_count) {              // Data word of methods command              if (dma_state.non_incrementing) {                  const u32 max_write = static_cast<u32>( -                    std::min<std::size_t>(index + dma_state.method_count, command_headers.size()) - -                    index); +                    std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);                  CallMultiMethod(&command_header.argument, max_write);                  dma_state.method_count -= max_write;                  dma_state.is_last_call = true; @@ -142,8 +147,6 @@ bool DmaPusher::Step() {          }          index++;      } - -    return true;  }  void DmaPusher::SetState(const CommandHeader& command_header) { diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h index 938f0f11c..6f00de937 100644 --- a/src/video_core/dma_pusher.h +++ b/src/video_core/dma_pusher.h @@ -4,11 +4,13 @@  #pragma once  #include <array> +#include <span>  #include <vector>  #include <queue>  #include "common/bit_field.h"  #include "common/common_types.h" +#include "common/scratch_buffer.h"  #include "video_core/engines/engine_interface.h"  #include "video_core/engines/puller.h" @@ -136,13 +138,15 @@ private:      static constexpr u32 non_puller_methods = 0x40;      static constexpr u32 max_subchannels = 8;      bool Step(); +    void ProcessCommands(std::span<const CommandHeader> commands);      void SetState(const CommandHeader& command_header);      void CallMethod(u32 argument) const;      void CallMultiMethod(const u32* base_start, u32 num_methods) const; -    std::vector<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once +    Common::ScratchBuffer<CommandHeader> +        command_headers; ///< Buffer for list of commands fetched at once      std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed      std::size_t dma_pushbuffer_subindex{};  ///< Index within a command list within the pushbuffer @@ -159,7 +163,7 @@ private:      DmaState dma_state{};      bool dma_increment_once{}; -    bool ib_enable{true}; ///< IB mode enabled +    const bool ib_enable{true}; ///< IB mode enabled      std::array<Engines::EngineInterface*, max_subchannels> subchannels{}; diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp index e4f8331ab..cea1dd8b0 100644 --- a/src/video_core/engines/engine_upload.cpp +++ b/src/video_core/engines/engine_upload.cpp @@ -24,7 +24,7 @@ void State::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {  void State::ProcessExec(const bool is_linear_) {      write_offset = 0;      copy_size = regs.line_length_in * regs.line_count; -    inner_buffer.resize(copy_size); +    inner_buffer.resize_destructive(copy_size);      is_linear = is_linear_;  } @@ -70,7 +70,7 @@ void State::ProcessData(std::span<const u8> read_buffer) {          const std::size_t dst_size = Tegra::Texture::CalculateSize(              true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,              regs.dest.BlockHeight(), regs.dest.BlockDepth()); -        tmp_buffer.resize(dst_size); +        tmp_buffer.resize_destructive(dst_size);          memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);          Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,                                         regs.dest.height, regs.dest.depth, x_offset, regs.dest.y, diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index 94fafd9dc..7242d2529 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h @@ -4,9 +4,10 @@  #pragma once  #include <span> -#include <vector> +  #include "common/bit_field.h"  #include "common/common_types.h" +#include "common/scratch_buffer.h"  namespace Tegra {  class MemoryManager; @@ -73,8 +74,8 @@ private:      u32 write_offset = 0;      u32 copy_size = 0; -    std::vector<u8> inner_buffer; -    std::vector<u8> tmp_buffer; +    Common::ScratchBuffer<u8> inner_buffer; +    Common::ScratchBuffer<u8> tmp_buffer;      bool is_linear = false;      Registers& regs;      MemoryManager& memory_manager; diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index a189e60ae..f73d7bf0f 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -184,12 +184,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() {      const size_t src_size =          CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth); -    if (read_buffer.size() < src_size) { -        read_buffer.resize(src_size); -    } -    if (write_buffer.size() < dst_size) { -        write_buffer.resize(dst_size); -    } +    read_buffer.resize_destructive(src_size); +    write_buffer.resize_destructive(dst_size);      memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);      memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); @@ -235,12 +231,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() {          CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);      const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count; -    if (read_buffer.size() < src_size) { -        read_buffer.resize(src_size); -    } -    if (write_buffer.size() < dst_size) { -        write_buffer.resize(dst_size); -    } +    read_buffer.resize_destructive(src_size); +    write_buffer.resize_destructive(dst_size);      memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);      if (Settings::IsGPULevelExtreme()) { @@ -269,12 +261,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {      pos_x = pos_x % x_in_gob;      pos_y = pos_y % 8; -    if (read_buffer.size() < src_size) { -        read_buffer.resize(src_size); -    } -    if (write_buffer.size() < dst_size) { -        write_buffer.resize(dst_size); -    } +    read_buffer.resize_destructive(src_size); +    write_buffer.resize_destructive(dst_size);      if (Settings::IsGPULevelExtreme()) {          memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(), src_size); @@ -333,14 +321,10 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {      const u32 pitch = x_elements * bytes_per_pixel;      const size_t mid_buffer_size = pitch * regs.line_count; -    if (read_buffer.size() < src_size) { -        read_buffer.resize(src_size); -    } -    if (write_buffer.size() < dst_size) { -        write_buffer.resize(dst_size); -    } +    read_buffer.resize_destructive(src_size); +    write_buffer.resize_destructive(dst_size); -    intermediate_buffer.resize(mid_buffer_size); +    intermediate_buffer.resize_destructive(mid_buffer_size);      memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);      memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size); diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index d40d3d302..c88191a61 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -6,8 +6,10 @@  #include <array>  #include <cstddef>  #include <vector> +  #include "common/bit_field.h"  #include "common/common_types.h" +#include "common/scratch_buffer.h"  #include "video_core/engines/engine_interface.h"  namespace Core { @@ -234,9 +236,9 @@ private:      MemoryManager& memory_manager;      VideoCore::RasterizerInterface* rasterizer = nullptr; -    std::vector<u8> read_buffer; -    std::vector<u8> write_buffer; -    std::vector<u8> intermediate_buffer; +    Common::ScratchBuffer<u8> read_buffer; +    Common::ScratchBuffer<u8> write_buffer; +    Common::ScratchBuffer<u8> intermediate_buffer;      static constexpr std::size_t NUM_REGS = 0x800;      struct Regs { diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp index ac0b7d20e..36a04e4e0 100644 --- a/src/video_core/host1x/vic.cpp +++ b/src/video_core/host1x/vic.cpp @@ -155,7 +155,7 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {          // swizzle pitch linear to block linear          const u32 block_height = static_cast<u32>(config.block_linear_height_log2);          const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0); -        luma_buffer.resize(size); +        luma_buffer.resize_destructive(size);          std::span<const u8> frame_buff(converted_frame_buf_addr, 4 * width * height);          Texture::SwizzleSubrect(luma_buffer, frame_buff, 4, width, height, 1, 0, 0, width, height,                                  block_height, 0, width * 4); @@ -181,8 +181,8 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {      const auto stride = static_cast<size_t>(frame->linesize[0]); -    luma_buffer.resize(aligned_width * surface_height); -    chroma_buffer.resize(aligned_width * surface_height / 2); +    luma_buffer.resize_destructive(aligned_width * surface_height); +    chroma_buffer.resize_destructive(aligned_width * surface_height / 2);      // Populate luma buffer      const u8* luma_src = frame->data[0]; diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h index 2b78786e8..3d9753047 100644 --- a/src/video_core/host1x/vic.h +++ b/src/video_core/host1x/vic.h @@ -4,8 +4,9 @@  #pragma once  #include <memory> -#include <vector> +  #include "common/common_types.h" +#include "common/scratch_buffer.h"  struct SwsContext; @@ -49,8 +50,8 @@ private:      /// size does not change during a stream      using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;      AVMallocPtr converted_frame_buffer; -    std::vector<u8> luma_buffer; -    std::vector<u8> chroma_buffer; +    Common::ScratchBuffer<u8> luma_buffer; +    Common::ScratchBuffer<u8> chroma_buffer;      GPUVAddr config_struct_address{};      GPUVAddr output_surface_luma_address{}; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 13782869d..3d560f303 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -78,7 +78,6 @@ void EmuThread::run() {      gpu.Start();      m_system.GetCpuManager().OnGpuReady(); -    m_system.RegisterExitCallback([this] { m_stop_source.request_stop(); });      if (m_system.DebuggerEnabled()) {          m_system.InitializeDebugger(); diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 1c2e76369..eca16b313 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -80,7 +80,7 @@ public:       * @return True if the emulation thread is running, otherwise false       */      bool IsRunning() const { -        return m_is_running.load(); +        return m_is_running.load() || m_should_run;      }      /** diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 820f60e61..524650144 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1550,8 +1550,9 @@ void GMainWindow::AllowOSSleep() {  bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {      // Shutdown previous session if the emu thread is still active... -    if (emu_thread != nullptr) +    if (emu_thread != nullptr) {          ShutdownGame(); +    }      if (!render_window->InitRenderTarget()) {          return false; @@ -1710,6 +1711,11 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t      system->RegisterExecuteProgramCallback(          [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); +    system->RegisterExitCallback([this] { +        emu_thread->ForceStop(); +        render_window->Exit(); +    }); +      connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);      connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);      // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views @@ -1779,9 +1785,9 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t      OnStartGame();  } -void GMainWindow::ShutdownGame() { +bool GMainWindow::OnShutdownBegin() {      if (!emulation_running) { -        return; +        return false;      }      if (ui->action_Fullscreen->isChecked()) { @@ -1793,6 +1799,10 @@ void GMainWindow::ShutdownGame() {      // Disable unlimited frame rate      Settings::values.use_speed_limit.SetValue(true); +    if (system->IsShuttingDown()) { +        return false; +    } +      system->SetShuttingDown(true);      discord_rpc->Pause(); @@ -1802,13 +1812,42 @@ void GMainWindow::ShutdownGame() {      emit EmulationStopping(); -    // Wait for emulation thread to complete and delete it -    if (system->DebuggerEnabled() || !emu_thread->wait(5000)) { +    shutdown_timer.setSingleShot(true); +    shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000); +    connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired); +    connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped); + +    // Disable everything to prevent anything from being triggered here +    ui->action_Pause->setEnabled(false); +    ui->action_Restart->setEnabled(false); +    ui->action_Stop->setEnabled(false); + +    return true; +} + +void GMainWindow::OnShutdownBeginDialog() { +    shutdown_dialog = new OverlayDialog(this, *system, QString{}, tr("Closing software..."), +                                        QString{}, QString{}, Qt::AlignHCenter | Qt::AlignVCenter); +    shutdown_dialog->open(); +} + +void GMainWindow::OnEmulationStopTimeExpired() { +    if (emu_thread) {          emu_thread->ForceStop(); -        emu_thread->wait();      } +} + +void GMainWindow::OnEmulationStopped() { +    shutdown_timer.stop(); +    emu_thread->disconnect(); +    emu_thread->wait();      emu_thread = nullptr; +    if (shutdown_dialog) { +        shutdown_dialog->deleteLater(); +        shutdown_dialog = nullptr; +    } +      emulation_running = false;      discord_rpc->Update(); @@ -1854,6 +1893,20 @@ void GMainWindow::ShutdownGame() {      // When closing the game, destroy the GLWindow to clear the context after the game is closed      render_window->ReleaseRenderTarget(); + +    Settings::RestoreGlobalState(system->IsPoweredOn()); +    system->HIDCore().ReloadInputDevices(); +    UpdateStatusButtons(); +} + +void GMainWindow::ShutdownGame() { +    if (!emulation_running) { +        return; +    } + +    OnShutdownBegin(); +    OnEmulationStopTimeExpired(); +    OnEmulationStopped();  }  void GMainWindow::StoreRecentFile(const QString& filename) { @@ -2956,11 +3009,9 @@ void GMainWindow::OnStopGame() {          return;      } -    ShutdownGame(); - -    Settings::RestoreGlobalState(system->IsPoweredOn()); -    system->HIDCore().ReloadInputDevices(); -    UpdateStatusButtons(); +    if (OnShutdownBegin()) { +        OnShutdownBeginDialog(); +    }  }  void GMainWindow::OnLoadComplete() { @@ -4047,10 +4098,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) {      // Shutdown session if the emu thread is active...      if (emu_thread != nullptr) {          ShutdownGame(); - -        Settings::RestoreGlobalState(system->IsPoweredOn()); -        system->HIDCore().ReloadInputDevices(); -        UpdateStatusButtons();      }      render_window->close(); @@ -4143,6 +4190,10 @@ bool GMainWindow::ConfirmForceLockedExit() {  }  void GMainWindow::RequestGameExit() { +    if (!system->IsPoweredOn()) { +        return; +    } +      auto& sm{system->ServiceManager()};      auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");      auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 5b84c7a00..db318485d 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -29,6 +29,7 @@ class GImageInfo;  class GRenderWindow;  class LoadingScreen;  class MicroProfileDialog; +class OverlayDialog;  class ProfilerWidget;  class ControllerDialog;  class QLabel; @@ -335,6 +336,10 @@ private slots:      void OnReinitializeKeys(ReinitializeKeyBehavior behavior);      void OnLanguageChanged(const QString& locale);      void OnMouseActivity(); +    bool OnShutdownBegin(); +    void OnShutdownBeginDialog(); +    void OnEmulationStopped(); +    void OnEmulationStopTimeExpired();  private:      QString GetGameListErrorRemoving(InstalledEntryType type) const; @@ -384,6 +389,8 @@ private:      GRenderWindow* render_window;      GameList* game_list;      LoadingScreen* loading_screen; +    QTimer shutdown_timer; +    OverlayDialog* shutdown_dialog{};      GameListPlaceholder* game_list_placeholder; diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp index 3fa3d0afb..796f5bf41 100644 --- a/src/yuzu/util/overlay_dialog.cpp +++ b/src/yuzu/util/overlay_dialog.cpp @@ -3,6 +3,7 @@  #include <QKeyEvent>  #include <QScreen> +#include <QWindow>  #include "core/core.h"  #include "core/hid/hid_types.h" @@ -162,7 +163,7 @@ void OverlayDialog::MoveAndResizeWindow() {      const auto height = static_cast<float>(parentWidget()->height());      // High DPI -    const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; +    const float dpi_scale = parentWidget()->windowHandle()->screen()->logicalDotsPerInch() / 96.0f;      const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;      const auto body_text_font_size = @@ -259,3 +260,9 @@ void OverlayDialog::InputThread() {          std::this_thread::sleep_for(std::chrono::milliseconds(50));      }  } + +void OverlayDialog::keyPressEvent(QKeyEvent* e) { +    if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) { +        QDialog::keyPressEvent(e); +    } +} diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h index 39c44393c..872283d61 100644 --- a/src/yuzu/util/overlay_dialog.h +++ b/src/yuzu/util/overlay_dialog.h @@ -94,6 +94,7 @@ private:      /// The thread where input is being polled and processed.      void InputThread(); +    void keyPressEvent(QKeyEvent* e) override;      std::unique_ptr<Ui::OverlayDialog> ui; diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index f6eeb9d8d..61b6cc4e0 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -49,6 +49,15 @@ if(UNIX AND NOT APPLE)      install(TARGETS yuzu-cmd)  endif() +if(WIN32) +    # compile as a win32 gui application instead of a console application +    if(MSVC) +        set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") +    elseif(MINGW) +        set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows") +    endif() +endif() +  if (MSVC)      include(CopyYuzuSDLDeps)      copy_yuzu_SDL_deps(yuzu-cmd) diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index a80649703..91133569d 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -174,6 +174,13 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {  /// Application entry point  int main(int argc, char** argv) { +#ifdef _WIN32 +    if (AttachConsole(ATTACH_PARENT_PROCESS)) { +        freopen("CONOUT$", "wb", stdout); +        freopen("CONOUT$", "wb", stderr); +    } +#endif +      Common::Log::Initialize();      Common::Log::SetColorConsoleBackendEnabled(true);      Common::Log::Start(); | 
