diff --git a/.gitmodules b/.gitmodules index 3d0d21c5b..1c05ba6f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -119,3 +119,7 @@ path = externals/MoltenVK/cereal url = https://github.com/USCiLab/cereal shallow = true +[submodule "externals/cubeb"] + path = externals/cubeb + url = https://github.com/mozilla/cubeb + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index dac3b19ab..43e8d7cab 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ git_describe(GIT_DESC --always --long --dirty) git_branch_name(GIT_BRANCH) string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S") -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) find_package(Boost 1.84.0 CONFIG) find_package(FFmpeg 5.1.2 MODULE) @@ -127,6 +127,7 @@ find_package(xxHash 0.8.2 MODULE) find_package(ZLIB 1.3 MODULE) find_package(Zydis 5.0.0 CONFIG) find_package(pugixml 1.14 CONFIG) +find_package(cubeb CONFIG) if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT MSVC) find_package(cryptopp 8.9.0 MODULE) @@ -198,9 +199,10 @@ set(AUDIO_LIB src/core/libraries/audio/audioin.cpp src/core/libraries/audio/audioin.h src/core/libraries/audio/audioout.cpp src/core/libraries/audio/audioout.h - src/core/libraries/audio/sdl_audio.cpp - src/core/libraries/audio/sdl_audio.h + src/core/libraries/audio/audioout_backend.h src/core/libraries/audio/audioout_error.h + src/core/libraries/audio/cubeb_audio.cpp + src/core/libraries/audio/sdl_audio.cpp src/core/libraries/ngs2/ngs2.cpp src/core/libraries/ngs2/ngs2.h ) @@ -493,6 +495,7 @@ set(COMMON src/common/logging/backend.cpp src/common/polyfill_thread.h src/common/rdtsc.cpp src/common/rdtsc.h + src/common/ringbuffer.h src/common/signal_context.h src/common/signal_context.cpp src/common/singleton.h @@ -517,7 +520,7 @@ set(COMMON src/common/logging/backend.cpp src/common/number_utils.cpp src/common/memory_patcher.h src/common/memory_patcher.cpp - src/common/scm_rev.cpp + ${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp src/common/scm_rev.h ) @@ -884,7 +887,7 @@ endif() create_target_directory_groups(shadps4) target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG) -target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers) +target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 pugixml::pugixml stb::headers cubeb::cubeb) target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h") target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h") @@ -901,11 +904,18 @@ endif() if (APPLE) if (ENABLE_QT_GUI) # Include MoltenVK in the app bundle, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers. - target_sources(shadps4 PRIVATE externals/MoltenVK/MoltenVK_icd.json) - set_source_files_properties(externals/MoltenVK/MoltenVK_icd.json - PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d) - add_custom_command(TARGET shadps4 POST_BUILD - COMMAND cmake -E copy $ $/Contents/Frameworks/libMoltenVK.dylib) + set(MVK_ICD ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK_icd.json) + target_sources(shadps4 PRIVATE ${MVK_ICD}) + set_source_files_properties(${MVK_ICD} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d) + + set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib) + set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib) + add_custom_command( + OUTPUT ${MVK_DYLIB_DST} + DEPENDS ${MVK_DYLIB_SRC} + COMMAND cmake -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST}) + add_custom_target(CopyMoltenVK DEPENDS ${MVK_DYLIB_DST}) + add_dependencies(shadps4 CopyMoltenVK) set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks") else() # For non-bundled SDL build, just do a normal library link. diff --git a/LICENSES/ISC.txt b/LICENSES/ISC.txt new file mode 100644 index 000000000..b9bcfa3a4 --- /dev/null +++ b/LICENSES/ISC.txt @@ -0,0 +1,7 @@ +ISC License + + + +Permission to use, copy, modify, and /or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4350948b7..8bdf089f8 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -228,6 +228,16 @@ if (NOT TARGET stb::headers) add_library(stb::headers ALIAS stb) endif() +# cubeb +if (NOT TARGET cubeb::cubeb) + option(BUILD_TESTS "" OFF) + option(BUILD_TOOLS "" OFF) + option(BUNDLE_SPEEX "" ON) + option(USE_SANITIZERS "" OFF) + add_subdirectory(cubeb) + add_library(cubeb::cubeb ALIAS cubeb) +endif() + # Apple-only dependencies if (APPLE) # date diff --git a/externals/cubeb b/externals/cubeb new file mode 160000 index 000000000..9a9d034c5 --- /dev/null +++ b/externals/cubeb @@ -0,0 +1 @@ +Subproject commit 9a9d034c51859a045a34f201334f612c51e6c19d diff --git a/src/common/config.cpp b/src/common/config.cpp index b16c9999b..088cfa853 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -68,6 +68,7 @@ static int cursorHideTimeout = 5; // 5 seconds (default) static bool separateupdatefolder = false; static bool compatibilityData = false; static bool checkCompatibilityOnStartup = false; +static std::string audioBackend = "cubeb"; // Gui std::vector settings_install_dirs = {}; @@ -244,6 +245,10 @@ bool getCheckCompatibilityOnStartup() { return checkCompatibilityOnStartup; } +std::string getAudioBackend() { + return audioBackend; +} + void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } @@ -380,6 +385,10 @@ void setCheckCompatibilityOnStartup(bool use) { checkCompatibilityOnStartup = use; } +void setAudioBackend(std::string backend) { + audioBackend = backend; +} + void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_x = x; main_window_geometry_y = y; @@ -620,6 +629,12 @@ void load(const std::filesystem::path& path) { vkCrashDiagnostic = toml::find_or(vk, "crashDiagnostic", false); } + if (data.contains("Audio")) { + const toml::value& audio = data.at("Audio"); + + audioBackend = toml::find_or(audio, "backend", "cubeb"); + } + if (data.contains("Debug")) { const toml::value& debug = data.at("Debug"); @@ -719,6 +734,7 @@ void save(const std::filesystem::path& path) { data["Vulkan"]["rdocEnable"] = rdocEnable; data["Vulkan"]["rdocMarkersEnable"] = vkMarkers; data["Vulkan"]["crashDiagnostic"] = vkCrashDiagnostic; + data["Audio"]["backend"] = audioBackend; data["Debug"]["DebugDump"] = isDebugDump; data["Debug"]["CollectShader"] = isShaderDebug; @@ -824,6 +840,7 @@ void setDefaultValues() { separateupdatefolder = false; compatibilityData = false; checkCompatibilityOnStartup = false; + audioBackend = "cubeb"; } } // namespace Config diff --git a/src/common/config.h b/src/common/config.h index ffaf276a4..07871ae53 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -24,6 +24,7 @@ bool getEnableDiscordRPC(); bool getSeparateUpdateEnabled(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); +std::string getAudioBackend(); std::string getLogFilter(); std::string getLogType(); @@ -76,6 +77,7 @@ void setSeparateUpdateEnabled(bool use); void setGameInstallDirs(const std::vector& settings_install_dirs_config); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); +void setAudioBackend(std::string backend); void setCursorState(s16 cursorState); void setCursorHideTimeout(int newcursorHideTimeout); diff --git a/src/common/ringbuffer.h b/src/common/ringbuffer.h new file mode 100644 index 000000000..6a71c2888 --- /dev/null +++ b/src/common/ringbuffer.h @@ -0,0 +1,374 @@ +// SPDX-FileCopyrightText: Copyright 2016 Mozilla Foundation +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include +#include +#include +#include "common/assert.h" + +/** + * Single producer single consumer lock-free and wait-free ring buffer. + * + * This data structure allows producing data from one thread, and consuming it + * on another thread, safely and without explicit synchronization. If used on + * two threads, this data structure uses atomics for thread safety. It is + * possible to disable the use of atomics at compile time and only use this data + * structure on one thread. + * + * The role for the producer and the consumer must be constant, i.e., the + * producer should always be on one thread and the consumer should always be on + * another thread. + * + * Some words about the inner workings of this class: + * - Capacity is fixed. Only one allocation is performed, in the constructor. + * When reading and writing, the return value of the method allows checking if + * the ring buffer is empty or full. + * - We always keep the read index at least one element ahead of the write + * index, so we can distinguish between an empty and a full ring buffer: an + * empty ring buffer is when the write index is at the same position as the + * read index. A full buffer is when the write index is exactly one position + * before the read index. + * - We synchronize updates to the read index after having read the data, and + * the write index after having written the data. This means that the each + * thread can only touch a portion of the buffer that is not touched by the + * other thread. + * - Callers are expected to provide buffers. When writing to the queue, + * elements are copied into the internal storage from the buffer passed in. + * When reading from the queue, the user is expected to provide a buffer. + * Because this is a ring buffer, data might not be contiguous in memory, + * providing an external buffer to copy into is an easy way to have linear + * data for further processing. + */ +template +class RingBuffer { +public: + /** + * Constructor for a ring buffer. + * + * This performs an allocation, but is the only allocation that will happen + * for the life time of a `RingBuffer`. + * + * @param capacity The maximum number of element this ring buffer will hold. + */ + RingBuffer(int capacity) + /* One more element to distinguish from empty and full buffer. */ + : capacity_(capacity + 1) { + ASSERT(storage_capacity() < std::numeric_limits::max() / 2 && + "buffer too large for the type of index used."); + ASSERT(capacity_ > 0); + + data_.reset(new T[storage_capacity()]); + /* If this queue is using atomics, initializing those members as the last + * action in the constructor acts as a full barrier, and allow capacity() to + * be thread-safe. */ + write_index_ = 0; + read_index_ = 0; + } + /** + * Push `count` zero or default constructed elements in the array. + * + * Only safely called on the producer thread. + * + * @param count The number of elements to enqueue. + * @return The number of element enqueued. + */ + int enqueue_default(int count) { + return enqueue(nullptr, count); + } + /** + * @brief Put an element in the queue + * + * Only safely called on the producer thread. + * + * @param element The element to put in the queue. + * + * @return 1 if the element was inserted, 0 otherwise. + */ + int enqueue(T& element) { + return enqueue(&element, 1); + } + /** + * Push `count` elements in the ring buffer. + * + * Only safely called on the producer thread. + * + * @param elements a pointer to a buffer containing at least `count` elements. + * If `elements` is nullptr, zero or default constructed elements are + * enqueued. + * @param count The number of elements to read from `elements` + * @return The number of elements successfully coped from `elements` and + * inserted into the ring buffer. + */ + int enqueue(T* elements, int count) { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + + int wr_idx = write_index_.load(std::memory_order_relaxed); + int rd_idx = read_index_.load(std::memory_order_acquire); + + if (full_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_write = std::min(available_write_internal(rd_idx, wr_idx), count); + + /* First part, from the write index to the end of the array. */ + int first_part = std::min(storage_capacity() - wr_idx, to_write); + /* Second part, from the beginning of the array */ + int second_part = to_write - first_part; + + if (elements) { + Copy(data_.get() + wr_idx, elements, first_part); + Copy(data_.get(), elements + first_part, second_part); + } else { + ConstructDefault(data_.get() + wr_idx, first_part); + ConstructDefault(data_.get(), second_part); + } + + write_index_.store(increment_index(wr_idx, to_write), std::memory_order_release); + + return to_write; + } + /** + * Retrieve at most `count` elements from the ring buffer, and copy them to + * `elements`, if non-null. + * + * Only safely called on the consumer side. + * + * @param elements A pointer to a buffer with space for at least `count` + * elements. If `elements` is `nullptr`, `count` element will be discarded. + * @param count The maximum number of elements to dequeue. + * @return The number of elements written to `elements`. + */ + int dequeue(T* elements, int count) { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + + int rd_idx = read_index_.load(std::memory_order_relaxed); + int wr_idx = write_index_.load(std::memory_order_acquire); + + if (empty_internal(rd_idx, wr_idx)) { + return 0; + } + + int to_read = std::min(available_read_internal(rd_idx, wr_idx), count); + + int first_part = std::min(storage_capacity() - rd_idx, to_read); + int second_part = to_read - first_part; + + if (elements) { + Copy(elements, data_.get() + rd_idx, first_part); + Copy(elements + first_part, data_.get(), second_part); + } + + read_index_.store(increment_index(rd_idx, to_read), std::memory_order_release); + + return to_read; + } + /** + * Get the number of available element for consuming. + * + * Only safely called on the consumer thread. + * + * @return The number of available elements for reading. + */ + int available_read() const { +#ifndef NDEBUG + assert_correct_thread(consumer_id); +#endif + return available_read_internal(read_index_.load(std::memory_order_relaxed), + write_index_.load(std::memory_order_acquire)); + } + /** + * Get the number of available elements for consuming. + * + * Only safely called on the producer thread. + * + * @return The number of empty slots in the buffer, available for writing. + */ + int available_write() const { +#ifndef NDEBUG + assert_correct_thread(producer_id); +#endif + return available_write_internal(read_index_.load(std::memory_order_acquire), + write_index_.load(std::memory_order_relaxed)); + } + /** + * Get the total capacity, for this ring buffer. + * + * Can be called safely on any thread. + * + * @return The maximum capacity of this ring buffer. + */ + int capacity() const { + return storage_capacity() - 1; + } + /** + * Reset the consumer and producer thread identifier, in case the thread are + * being changed. This has to be externally synchronized. This is no-op when + * asserts are disabled. + */ + void reset_thread_ids() { +#ifndef NDEBUG + consumer_id = producer_id = std::thread::id(); +#endif + } + +private: + /** Return true if the ring buffer is empty. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is empty, false otherwise. + **/ + bool empty_internal(int read_index, int write_index) const { + return write_index == read_index; + } + /** Return true if the ring buffer is full. + * + * This happens if the write index is exactly one element behind the read + * index. + * + * @param read_index the read index to consider + * @param write_index the write index to consider + * @return true if the ring buffer is full, false otherwise. + **/ + bool full_internal(int read_index, int write_index) const { + return (write_index + 1) % storage_capacity() == read_index; + } + /** + * Return the size of the storage. It is one more than the number of elements + * that can be stored in the buffer. + * + * @return the number of elements that can be stored in the buffer. + */ + int storage_capacity() const { + return capacity_; + } + /** + * Returns the number of elements available for reading. + * + * @return the number of available elements for reading. + */ + int available_read_internal(int read_index, int write_index) const { + if (write_index >= read_index) { + return write_index - read_index; + } else { + return write_index + storage_capacity() - read_index; + } + } + /** + * Returns the number of empty elements, available for writing. + * + * @return the number of elements that can be written into the array. + */ + int available_write_internal(int read_index, int write_index) const { + /* We substract one element here to always keep at least one sample + * free in the buffer, to distinguish between full and empty array. */ + int rv = read_index - write_index - 1; + if (write_index >= read_index) { + rv += storage_capacity(); + } + return rv; + } + /** + * Increments an index, wrapping it around the storage. + * + * @param index a reference to the index to increment. + * @param increment the number by which `index` is incremented. + * @return the new index. + */ + int increment_index(int index, int increment) const { + ASSERT(increment >= 0); + return (index + increment) % storage_capacity(); + } + /** + * @brief This allows checking that enqueue (resp. dequeue) are always called + * by the right thread. + * + * @param id the id of the thread that has called the calling method first. + */ +#ifndef NDEBUG + static void assert_correct_thread(std::thread::id& id) { + if (id == std::thread::id()) { + id = std::this_thread::get_id(); + return; + } + ASSERT(id == std::this_thread::get_id()); + } +#endif + /** Similar to memcpy, but accounts for the size of an element. */ + template + void PodCopy(CopyT* destination, const CopyT* source, size_t count) { + static_assert(std::is_trivial::value, "Requires trivial type"); + ASSERT(destination && source); + memcpy(destination, source, count * sizeof(CopyT)); + } + /** Similar to a memset to zero, but accounts for the size of an element. */ + template + void PodZero(ZeroT* destination, size_t count) { + static_assert(std::is_trivial::value, "Requires trivial type"); + ASSERT(destination); + memset(destination, 0, count * sizeof(ZeroT)); + } + template + void Copy(CopyT* destination, const CopyT* source, size_t count, Trait) { + for (size_t i = 0; i < count; i++) { + destination[i] = source[i]; + } + } + template + void Copy(CopyT* destination, const CopyT* source, size_t count, std::true_type) { + PodCopy(destination, source, count); + } + /** + * This allows copying a number of elements from a `source` pointer to a + * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that + * calls the constructors and destructors otherwise. + */ + template + void Copy(CopyT* destination, const T* source, size_t count) { + ASSERT(destination && source); + Copy(destination, source, count, typename std::is_trivial::type()); + } + template + void ConstructDefault(ConstructT* destination, size_t count, Trait) { + for (size_t i = 0; i < count; i++) { + destination[i] = ConstructT(); + } + } + template + void ConstructDefault(ConstructT* destination, size_t count, std::true_type) { + PodZero(destination, count); + } + /** + * This allows zeroing (using memset) or default-constructing a number of + * elements calling the constructors and destructors if necessary. + */ + template + void ConstructDefault(ConstructT* destination, size_t count) { + ASSERT(destination); + ConstructDefault(destination, count, typename std::is_arithmetic::type()); + } + /** Index at which the oldest element is at, in samples. */ + std::atomic read_index_; + /** Index at which to write new elements. `write_index` is always at + * least one element ahead of `read_index_`. */ + std::atomic write_index_; + /** Maximum number of elements that can be stored in the ring buffer. */ + const int capacity_; + /** Data storage */ + std::unique_ptr data_; +#ifndef NDEBUG + /** The id of the only thread that is allowed to read from the queue. */ + mutable std::thread::id consumer_id; + /** The id of the only thread that is allowed to write from the queue. */ + mutable std::thread::id producer_id; +#endif +}; diff --git a/src/core/libraries/audio/audioout.cpp b/src/core/libraries/audio/audioout.cpp index db43ee928..89ea1d3f5 100644 --- a/src/core/libraries/audio/audioout.cpp +++ b/src/core/libraries/audio/audioout.cpp @@ -7,26 +7,15 @@ #include #include "common/assert.h" +#include "common/config.h" #include "common/logging/log.h" #include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" #include "core/libraries/audio/audioout_error.h" -#include "core/libraries/audio/sdl_audio.h" #include "core/libraries/libs.h" namespace Libraries::AudioOut { -struct PortOut { - void* impl; - u32 samples_num; - u32 freq; - OrbisAudioOutParamFormat format; - OrbisAudioOutPort type; - int channels_num; - bool is_float; - std::array volume; - u8 sample_size; - bool is_open; -}; std::shared_mutex ports_mutex; std::array ports_out{}; @@ -104,7 +93,7 @@ static bool IsFormatFloat(const OrbisAudioOutParamFormat format) { } } -static int GetFormatNumChannels(const OrbisAudioOutParamFormat format) { +static u8 GetFormatNumChannels(const OrbisAudioOutParamFormat format) { switch (format) { case OrbisAudioOutParamFormat::S16Mono: case OrbisAudioOutParamFormat::FloatMono: @@ -187,13 +176,11 @@ int PS4_SYSV_ABI sceAudioOutClose(s32 handle) { std::scoped_lock lock(ports_mutex); auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - audio->Close(port.impl); port.impl = nullptr; - port.is_open = false; return ORBIS_OK; } @@ -264,7 +251,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta std::scoped_lock lock(ports_mutex); const auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } @@ -324,7 +311,16 @@ int PS4_SYSV_ABI sceAudioOutInit() { if (audio != nullptr) { return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT; } - audio = std::make_unique(); + const auto backend = Config::getAudioBackend(); + if (backend == "cubeb") { + audio = std::make_unique(); + } else if (backend == "sdl") { + audio = std::make_unique(); + } else { + // Cubeb as a default fallback. + LOG_ERROR(Lib_AudioOut, "Invalid audio backend '{}', defaulting to cubeb.", backend); + audio = std::make_unique(); + } return ORBIS_OK; } @@ -399,23 +395,25 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, } std::scoped_lock lock{ports_mutex}; - const auto port = std::ranges::find(ports_out, false, &PortOut::is_open); + const auto port = + std::ranges::find_if(ports_out, [&](const PortOut& p) { return p.impl == nullptr; }); if (port == ports_out.end()) { LOG_ERROR(Lib_AudioOut, "Audio ports are full"); return ORBIS_AUDIO_OUT_ERROR_PORT_FULL; } - port->is_open = true; port->type = port_type; - port->samples_num = length; - port->freq = sample_rate; port->format = format; port->is_float = IsFormatFloat(format); - port->channels_num = GetFormatNumChannels(format); port->sample_size = GetFormatSampleSize(format); + port->channels_num = GetFormatNumChannels(format); + port->samples_num = length; + port->frame_size = port->sample_size * port->channels_num; + port->buffer_size = port->frame_size * port->samples_num; + port->freq = sample_rate; port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB); + port->impl = audio->Open(*port); - port->impl = audio->Open(port->is_float, port->channels_num, port->freq); return std::distance(ports_out.begin(), port) + 1; } @@ -424,7 +422,7 @@ int PS4_SYSV_ABI sceAudioOutOpenEx() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { +s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr) { if (handle < 1 || handle > SCE_AUDIO_OUT_NUM_PORTS) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } @@ -434,12 +432,11 @@ s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr) { } auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } - const size_t data_size = port.samples_num * port.sample_size * port.channels_num; - audio->Output(port.impl, ptr, data_size); + port.impl->Output(ptr, port.buffer_size); return ORBIS_OK; } @@ -548,7 +545,7 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { std::scoped_lock lock(ports_mutex); auto& port = ports_out.at(handle - 1); - if (!port.is_open) { + if (!port.impl) { return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT; } @@ -579,7 +576,7 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) { } } - audio->SetVolume(port.impl, port.volume); + port.impl->SetVolume(port.volume); return ORBIS_OK; } diff --git a/src/core/libraries/audio/audioout.h b/src/core/libraries/audio/audioout.h index c66a0e9f5..58c77db99 100644 --- a/src/core/libraries/audio/audioout.h +++ b/src/core/libraries/audio/audioout.h @@ -3,12 +3,15 @@ #pragma once -#include "common/bit_field.h" +#include +#include "common/bit_field.h" #include "core/libraries/system/userservice.h" namespace Libraries::AudioOut { +class PortBackend; + // Main up to 8 ports, BGM 1 port, voice up to 4 ports, // personal up to 4 ports, padspk up to 5 ports, aux 1 port constexpr int SCE_AUDIO_OUT_NUM_PORTS = 22; @@ -43,7 +46,7 @@ union OrbisAudioOutParamExtendedInformation { struct OrbisAudioOutOutputParam { s32 handle; - const void* ptr; + void* ptr; }; struct OrbisAudioOutPortState { @@ -56,6 +59,21 @@ struct OrbisAudioOutPortState { u64 reserved64[2]; }; +struct PortOut { + std::unique_ptr impl{}; + + OrbisAudioOutPort type; + OrbisAudioOutParamFormat format; + bool is_float; + u8 sample_size; + u8 channels_num; + u32 samples_num; + u32 frame_size; + u32 buffer_size; + u32 freq; + std::array volume; +}; + int PS4_SYSV_ABI sceAudioOutDeviceIdOpen(); int PS4_SYSV_ABI sceAudioDeviceControlGet(); int PS4_SYSV_ABI sceAudioDeviceControlSet(); @@ -94,7 +112,7 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id, OrbisAudioOutPort port_type, s32 index, u32 length, u32 sample_rate, OrbisAudioOutParamExtendedInformation param_type); int PS4_SYSV_ABI sceAudioOutOpenEx(); -s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, const void* ptr); +s32 PS4_SYSV_ABI sceAudioOutOutput(s32 handle, void* ptr); s32 PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num); int PS4_SYSV_ABI sceAudioOutPtClose(); int PS4_SYSV_ABI sceAudioOutPtGetLastOutputTime(); diff --git a/src/core/libraries/audio/audioout_backend.h b/src/core/libraries/audio/audioout_backend.h index 238ef0201..ecc4cf7c6 100644 --- a/src/core/libraries/audio/audioout_backend.h +++ b/src/core/libraries/audio/audioout_backend.h @@ -3,17 +3,42 @@ #pragma once +typedef struct cubeb cubeb; + namespace Libraries::AudioOut { +struct PortOut; + +class PortBackend { +public: + virtual ~PortBackend() = default; + + virtual void Output(void* ptr, size_t size) = 0; + virtual void SetVolume(const std::array& ch_volumes) = 0; +}; + class AudioOutBackend { public: AudioOutBackend() = default; virtual ~AudioOutBackend() = default; - virtual void* Open(bool is_float, int num_channels, u32 sample_rate) = 0; - virtual void Close(void* impl) = 0; - virtual void Output(void* impl, const void* ptr, size_t size) = 0; - virtual void SetVolume(void* impl, std::array ch_volumes) = 0; + virtual std::unique_ptr Open(PortOut& port) = 0; +}; + +class CubebAudioOut final : public AudioOutBackend { +public: + CubebAudioOut(); + ~CubebAudioOut() override; + + std::unique_ptr Open(PortOut& port) override; + +private: + cubeb* ctx = nullptr; +}; + +class SDLAudioOut final : public AudioOutBackend { +public: + std::unique_ptr Open(PortOut& port) override; }; } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/cubeb_audio.cpp b/src/core/libraries/audio/cubeb_audio.cpp new file mode 100644 index 000000000..ca0a4c3b6 --- /dev/null +++ b/src/core/libraries/audio/cubeb_audio.cpp @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "common/assert.h" +#include "common/ringbuffer.h" +#include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" + +namespace Libraries::AudioOut { + +constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold + +class CubebPortBackend : public PortBackend { +public: + CubebPortBackend(cubeb* ctx, const PortOut& port) + : frame_size(port.frame_size), buffer(static_cast(port.buffer_size) * 4) { + if (!ctx) { + return; + } + const auto get_channel_layout = [&port] -> cubeb_channel_layout { + switch (port.channels_num) { + case 1: + return CUBEB_LAYOUT_MONO; + case 2: + return CUBEB_LAYOUT_STEREO; + case 8: + return CUBEB_LAYOUT_3F4_LFE; + default: + UNREACHABLE(); + } + }; + cubeb_stream_params stream_params = { + .format = port.is_float ? CUBEB_SAMPLE_FLOAT32LE : CUBEB_SAMPLE_S16LE, + .rate = port.freq, + .channels = port.channels_num, + .layout = get_channel_layout(), + .prefs = CUBEB_STREAM_PREF_NONE, + }; + u32 latency_frames = 512; + if (const auto ret = cubeb_get_min_latency(ctx, &stream_params, &latency_frames); + ret != CUBEB_OK) { + LOG_WARNING(Lib_AudioOut, + "Could not get minimum cubeb audio latency, falling back to default: {}", + ret); + } + char stream_name[64]; + snprintf(stream_name, sizeof(stream_name), "shadPS4 stream %p", this); + if (const auto ret = cubeb_stream_init(ctx, &stream, stream_name, nullptr, nullptr, nullptr, + &stream_params, latency_frames, &DataCallback, + &StateCallback, this); + ret != CUBEB_OK) { + LOG_ERROR(Lib_AudioOut, "Failed to create cubeb stream: {}", ret); + return; + } + if (const auto ret = cubeb_stream_start(stream); ret != CUBEB_OK) { + LOG_ERROR(Lib_AudioOut, "Failed to start cubeb stream: {}", ret); + return; + } + } + + ~CubebPortBackend() override { + if (!stream) { + return; + } + if (const auto ret = cubeb_stream_stop(stream); ret != CUBEB_OK) { + LOG_WARNING(Lib_AudioOut, "Failed to stop cubeb stream: {}", ret); + } + cubeb_stream_destroy(stream); + stream = nullptr; + } + + void Output(void* ptr, size_t size) override { + auto* data = static_cast(ptr); + + std::unique_lock lock{buffer_mutex}; + buffer_cv.wait(lock, [&] { return buffer.available_write() >= size; }); + buffer.enqueue(data, static_cast(size)); + } + + void SetVolume(const std::array& ch_volumes) override { + if (!stream) { + return; + } + // Cubeb does not have per-channel volumes, for now just take the maximum of the channels. + const auto vol = *std::ranges::max_element(ch_volumes); + if (const auto ret = + cubeb_stream_set_volume(stream, static_cast(vol) / SCE_AUDIO_OUT_VOLUME_0DB); + ret != CUBEB_OK) { + LOG_WARNING(Lib_AudioOut, "Failed to change cubeb stream volume: {}", ret); + } + } + +private: + static long DataCallback(cubeb_stream* stream, void* user_data, const void* in, void* out, + long num_frames) { + auto* stream_data = static_cast(user_data); + const auto out_data = static_cast(out); + const auto requested_size = static_cast(num_frames * stream_data->frame_size); + + std::unique_lock lock{stream_data->buffer_mutex}; + const auto dequeued_size = stream_data->buffer.dequeue(out_data, requested_size); + lock.unlock(); + stream_data->buffer_cv.notify_one(); + + if (dequeued_size < requested_size) { + // Need to fill remaining space with silence. + std::memset(out_data + dequeued_size, 0, requested_size - dequeued_size); + } + return num_frames; + } + + static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) { + switch (state) { + case CUBEB_STATE_STARTED: + LOG_INFO(Lib_AudioOut, "Cubeb stream started"); + break; + case CUBEB_STATE_STOPPED: + LOG_INFO(Lib_AudioOut, "Cubeb stream stopped"); + break; + case CUBEB_STATE_DRAINED: + LOG_INFO(Lib_AudioOut, "Cubeb stream drained"); + break; + case CUBEB_STATE_ERROR: + LOG_ERROR(Lib_AudioOut, "Cubeb stream encountered an error"); + break; + } + } + + size_t frame_size; + RingBuffer buffer; + std::mutex buffer_mutex; + std::condition_variable buffer_cv; + cubeb_stream* stream{}; +}; + +CubebAudioOut::CubebAudioOut() { + if (const auto ret = cubeb_init(&ctx, "shadPS4", nullptr); ret != CUBEB_OK) { + LOG_CRITICAL(Lib_AudioOut, "Failed to create cubeb context: {}", ret); + } +} + +CubebAudioOut::~CubebAudioOut() { + if (!ctx) { + return; + } + cubeb_destroy(ctx); + ctx = nullptr; +} + +std::unique_ptr CubebAudioOut::Open(PortOut& port) { + return std::make_unique(ctx, port); +} + +} // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.cpp b/src/core/libraries/audio/sdl_audio.cpp index ce385ad9c..7d7a7cee5 100644 --- a/src/core/libraries/audio/sdl_audio.cpp +++ b/src/core/libraries/audio/sdl_audio.cpp @@ -1,44 +1,60 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #include #include -#include -#include "common/assert.h" -#include "core/libraries/audio/sdl_audio.h" +#include "common/logging/log.h" +#include "core/libraries/audio/audioout.h" +#include "core/libraries/audio/audioout_backend.h" namespace Libraries::AudioOut { constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold -void* SDLAudioOut::Open(bool is_float, int num_channels, u32 sample_rate) { - SDL_AudioSpec fmt; - SDL_zero(fmt); - fmt.format = is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16; - fmt.channels = num_channels; - fmt.freq = sample_rate; - - auto* stream = - SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); - SDL_ResumeAudioStreamDevice(stream); - return stream; -} - -void SDLAudioOut::Close(void* impl) { - SDL_DestroyAudioStream(static_cast(impl)); -} - -void SDLAudioOut::Output(void* impl, const void* ptr, size_t size) { - auto* stream = static_cast(impl); - SDL_PutAudioStreamData(stream, ptr, size); - while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { - SDL_Delay(0); +class SDLPortBackend : public PortBackend { +public: + explicit SDLPortBackend(const PortOut& port) { + const SDL_AudioSpec fmt = { + .format = port.is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16, + .channels = port.channels_num, + .freq = static_cast(port.freq), + }; + stream = + SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &fmt, nullptr, nullptr); + if (stream == nullptr) { + LOG_ERROR(Lib_AudioOut, "Failed to create SDL audio stream: {}", SDL_GetError()); + } + SDL_ResumeAudioStreamDevice(stream); } -} -void SDLAudioOut::SetVolume(void* impl, std::array ch_volumes) { - // Not yet implemented + ~SDLPortBackend() override { + if (stream) { + SDL_DestroyAudioStream(stream); + stream = nullptr; + } + } + + void Output(void* ptr, size_t size) override { + SDL_PutAudioStreamData(stream, ptr, static_cast(size)); + while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) { + // Yield to allow the stream to drain. + std::this_thread::yield(); + } + } + + void SetVolume(const std::array& ch_volumes) override { + // TODO: Not yet implemented + } + +private: + SDL_AudioStream* stream; +}; + +std::unique_ptr SDLAudioOut::Open(PortOut& port) { + return std::make_unique(port); } } // namespace Libraries::AudioOut diff --git a/src/core/libraries/audio/sdl_audio.h b/src/core/libraries/audio/sdl_audio.h deleted file mode 100644 index d55f2f6e3..000000000 --- a/src/core/libraries/audio/sdl_audio.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "core/libraries/audio/audioout_backend.h" - -namespace Libraries::AudioOut { - -class SDLAudioOut final : public AudioOutBackend { -public: - void* Open(bool is_float, int num_channels, u32 sample_rate) override; - void Close(void* impl) override; - void Output(void* impl, const void* ptr, size_t size) override; - void SetVolume(void* impl, std::array ch_volumes) override; -}; - -} // namespace Libraries::AudioOut diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 1a6007bf8..566f8ce1f 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -29,7 +29,7 @@ namespace Libraries::GnmDriver { using namespace AmdGpu; -enum GnmEventIdents : u64 { +enum GnmEventType : u64 { Compute0RelMem = 0x00, Compute1RelMem = 0x01, Compute2RelMem = 0x02, @@ -337,6 +337,12 @@ static inline u32* ClearContextState(u32* cmdbuf) { return cmdbuf + ClearStateSequence.size(); } +static inline bool IsValidEventType(Platform::InterruptId id) { + return (static_cast(id) >= static_cast(Platform::InterruptId::Compute0RelMem) && + static_cast(id) <= static_cast(Platform::InterruptId::Compute6RelMem)) || + static_cast(id) == static_cast(Platform::InterruptId::GfxEop); +} + s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { LOG_TRACE(Lib_GnmDriver, "called"); @@ -347,8 +353,7 @@ s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { EqueueEvent kernel_event{}; kernel_event.event.ident = id; kernel_event.event.filter = SceKernelEvent::Filter::GraphicsCore; - // The library only sets EV_ADD but it is suspected the kernel driver forces EV_CLEAR - kernel_event.event.flags = SceKernelEvent::Flags::Clear; + kernel_event.event.flags = SceKernelEvent::Flags::Add; kernel_event.event.fflags = 0; kernel_event.event.data = id; kernel_event.event.udata = udata; @@ -357,11 +362,15 @@ s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) { Platform::IrqC::Instance()->Register( static_cast(id), [=](Platform::InterruptId irq) { - ASSERT_MSG(irq == static_cast(id), - "An unexpected IRQ occured"); // We need to convert IRQ# to event id and do - // proper filtering in trigger function - eq->TriggerEvent(static_cast(id), SceKernelEvent::Filter::GraphicsCore, - nullptr); + ASSERT_MSG(irq == static_cast(id), "An unexpected IRQ occured"); + + // We need to convert IRQ# to event id + if (!IsValidEventType(irq)) + return; + + // Event data is expected to be an event type as per sceGnmGetEqEventType. + eq->TriggerEvent(static_cast(id), SceKernelEvent::Filter::GraphicsCore, + reinterpret_cast(id)); }, eq); return ORBIS_OK; @@ -476,7 +485,7 @@ s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id) { return ORBIS_KERNEL_ERROR_EBADF; } - eq->RemoveEvent(id); + eq->RemoveEvent(id, SceKernelEvent::Filter::GraphicsCore); Platform::IrqC::Instance()->Unregister(static_cast(id), eq); return ORBIS_OK; @@ -1000,9 +1009,13 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp() { return ORBIS_OK; } -int PS4_SYSV_ABI sceGnmGetEqEventType() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev) { + LOG_TRACE(Lib_GnmDriver, "called"); + + auto data = sceKernelGetEventData(ev); + ASSERT(static_cast(data) == GnmEventType::GfxEop); + + return data; } int PS4_SYSV_ABI sceGnmGetEqTimeStamp() { diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 017dbe3ad..d15483323 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -85,7 +85,7 @@ int PS4_SYSV_ABI sceGnmGetCoredumpMode(); int PS4_SYSV_ABI sceGnmGetCoredumpProtectionFaultTimestamp(); int PS4_SYSV_ABI sceGnmGetDbgGcHandle(); int PS4_SYSV_ABI sceGnmGetDebugTimestamp(); -int PS4_SYSV_ABI sceGnmGetEqEventType(); +int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev); int PS4_SYSV_ABI sceGnmGetEqTimeStamp(); int PS4_SYSV_ABI sceGnmGetGpuBlockStatus(); u32 PS4_SYSV_ABI sceGnmGetGpuCoreClockFrequency(); diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 42a8eed89..3ae77e46b 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -12,6 +12,8 @@ namespace Libraries::Kernel { +// Events are uniquely identified by id and filter. + bool EqueueInternal::AddEvent(EqueueEvent& event) { std::scoped_lock lock{m_mutex}; @@ -27,12 +29,13 @@ bool EqueueInternal::AddEvent(EqueueEvent& event) { return true; } -bool EqueueInternal::RemoveEvent(u64 id) { +bool EqueueInternal::RemoveEvent(u64 id, s16 filter) { bool has_found = false; std::scoped_lock lock{m_mutex}; - const auto& it = - std::ranges::find_if(m_events, [id](auto& ev) { return ev.event.ident == id; }); + const auto& it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); if (it != m_events.cend()) { m_events.erase(it); has_found = true; @@ -68,7 +71,7 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) { if (ev->flags & SceKernelEvent::Flags::OneShot) { for (auto ev_id = 0u; ev_id < count; ++ev_id) { - RemoveEvent(ev->ident); + RemoveEvent(ev->ident, ev->filter); } } @@ -94,8 +97,11 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) { int count = 0; for (auto& event : m_events) { if (event.IsTriggered()) { + // Event should not trigger again + event.ResetTriggerState(); + if (event.event.flags & SceKernelEvent::Flags::Clear) { - event.Reset(); + event.Clear(); } ev[count++] = event.event; if (count == num) { @@ -334,7 +340,7 @@ int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!eq->RemoveEvent(id)) { + if (!eq->RemoveEvent(id, SceKernelEvent::Filter::User)) { return ORBIS_KERNEL_ERROR_ENOENT; } return ORBIS_OK; @@ -344,6 +350,10 @@ s16 PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) { return ev->filter; } +u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev) { + return ev->data; +} + void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("D0OdFMjp46I", "libkernel", 1, "libkernel", 1, 1, sceKernelCreateEqueue); LIB_FUNCTION("jpFjmgAC5AE", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteEqueue); @@ -356,6 +366,7 @@ void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent); LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId); LIB_FUNCTION("23CPPI1tyBY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventFilter); + LIB_FUNCTION("kwGyyjohI50", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventData); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 5a13bdecd..f8759137c 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -66,8 +66,11 @@ struct EqueueEvent { std::chrono::steady_clock::time_point time_added; std::unique_ptr timer; - void Reset() { + void ResetTriggerState() { is_triggered = false; + } + + void Clear() { event.fflags = 0; event.data = 0; } @@ -83,7 +86,7 @@ struct EqueueEvent { } bool operator==(const EqueueEvent& ev) const { - return ev.event.ident == event.ident; + return ev.event.ident == event.ident && ev.event.filter == event.filter; } private: @@ -99,7 +102,7 @@ public: } bool AddEvent(EqueueEvent& event); - bool RemoveEvent(u64 id); + bool RemoveEvent(u64 id, s16 filter); int WaitForEvents(SceKernelEvent* ev, int num, u32 micros); bool TriggerEvent(u64 ident, s16 filter, void* trigger_data); int GetTriggeredEvents(SceKernelEvent* ev, int num); @@ -122,6 +125,8 @@ private: using SceKernelUseconds = u32; using SceKernelEqueue = EqueueInternal*; +u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); + void RegisterEventQueue(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/threads/pthread.cpp b/src/core/libraries/kernel/threads/pthread.cpp index 610e61238..761d13346 100644 --- a/src/core/libraries/kernel/threads/pthread.cpp +++ b/src/core/libraries/kernel/threads/pthread.cpp @@ -244,8 +244,8 @@ int PS4_SYSV_ABI posix_pthread_create_name_np(PthreadT* thread, const PthreadAtt new_thread->tid = ++TidCounter; if (new_thread->attr.stackaddr_attr == 0) { - /* Enforce minimum stack size of 64 KB */ - static constexpr size_t MinimumStack = 64_KB; + /* Enforce minimum stack size of 128 KB */ + static constexpr size_t MinimumStack = 128_KB; auto& stacksize = new_thread->attr.stacksize_attr; stacksize = std::max(stacksize, MinimumStack); } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 41db7df4b..aa116fa3d 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -101,13 +101,17 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, auto dmem_area = FindDmemArea(search_start); const auto is_suitable = [&] { + if (dmem_area == dmem_map.end()) { + return false; + } const auto aligned_base = Common::AlignUp(dmem_area->second.base, alignment); const auto alignment_size = aligned_base - dmem_area->second.base; const auto remaining_size = dmem_area->second.size >= alignment_size ? dmem_area->second.size - alignment_size : 0; return dmem_area->second.is_free && remaining_size >= size; }; - while (!is_suitable() && dmem_area->second.GetEnd() <= search_end) { + while (dmem_area != dmem_map.end() && !is_suitable() && + dmem_area->second.GetEnd() <= search_end) { ++dmem_area; } ASSERT_MSG(is_suitable(), "Unable to find free direct memory area: size = {:#x}", size); diff --git a/src/emulator.cpp b/src/emulator.cpp index 252a34418..10d17a2db 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -311,8 +311,9 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string found_modules, [&](const auto& path) { return path.filename() == module_name; }); if (it != found_modules.end()) { LOG_INFO(Loader, "Loading {}", it->string()); - linker->LoadModule(*it); - continue; + if (linker->LoadModule(*it) != -1) { + continue; + } } if (init_func) { LOG_INFO(Loader, "Can't Load {} switching to HLE", module_name); @@ -321,7 +322,7 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file, std::string LOG_INFO(Loader, "No HLE available for {} module", module_name); } } - if (std::filesystem::exists(sys_module_path / game_serial)) { + if (!game_serial.empty() && std::filesystem::exists(sys_module_path / game_serial)) { for (const auto& entry : std::filesystem::directory_iterator(sys_module_path / game_serial)) { LOG_INFO(Loader, "Loading {} from game serial file {}", entry.path().string(), diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index bba3c2891..b1cd07216 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -133,16 +133,16 @@ void GameListFrame::PopulateGameList() { QString formattedPlayTime; if (hours > 0) { - formattedPlayTime += QString("%1h ").arg(hours); + formattedPlayTime += QString("%1").arg(hours) + tr("h"); } if (minutes > 0) { - formattedPlayTime += QString("%1m ").arg(minutes); + formattedPlayTime += QString("%1").arg(minutes) + tr("m"); } formattedPlayTime = formattedPlayTime.trimmed(); m_game_info->m_games[i].play_time = playTime.toStdString(); if (formattedPlayTime.isEmpty()) { - SetTableItem(i, 8, QString("%1s").arg(seconds)); + SetTableItem(i, 8, QString("%1").arg(seconds) + tr("s")); } else { SetTableItem(i, 8, formattedPlayTime); } diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 16c0307c8..ab9233886 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -30,10 +30,11 @@ struct GameInfo { CompatibilityEntry compatibility = CompatibilityEntry{CompatibilityStatus::Unknown}; }; -class GameListUtils { +class GameListUtils : public QObject { + Q_OBJECT public: static QString FormatSize(qint64 size) { - static const QStringList suffixes = {"B", "KB", "MB", "GB", "TB"}; + static const QStringList suffixes = {tr("B"), tr("KB"), tr("MB"), tr("GB"), tr("TB")}; int suffixIndex = 0; double gameSize = static_cast(size); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 3f668fce5..326fe9e10 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -78,8 +78,8 @@ bool MainWindow::Init() { this->setStatusBar(statusBar.data()); // Update status bar int numGames = m_game_info->m_games.size(); - QString statusMessage = - "Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)"; + QString statusMessage = tr("Games: ") + QString::number(numGames) + " (" + + QString::number(duration.count()) + "ms)"; statusBar->showMessage(statusMessage); #ifdef ENABLE_DISCORD_RPC diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index e01b203e9..f6868a123 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -214,6 +214,7 @@ SettingsDialog::SettingsDialog(std::span physical_devices, ui->enableCompatibilityCheckBox->installEventFilter(this); ui->checkCompatibilityOnStartupCheckBox->installEventFilter(this); ui->updateCompatibilityButton->installEventFilter(this); + ui->audioBackendComboBox->installEventFilter(this); // Input ui->hideCursorGroupBox->installEventFilter(this); @@ -312,6 +313,8 @@ void SettingsDialog::LoadValuesFromConfig() { toml::find_or(data, "General", "compatibilityEnabled", false)); ui->checkCompatibilityOnStartupCheckBox->setChecked( toml::find_or(data, "General", "checkCompatibilityOnStartup", false)); + ui->audioBackendComboBox->setCurrentText( + QString::fromStdString(toml::find_or(data, "Audio", "backend", "cubeb"))); #ifdef ENABLE_UPDATER ui->updateCheckBox->setChecked(toml::find_or(data, "General", "autoUpdate", false)); @@ -437,6 +440,8 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("checkCompatibilityOnStartupCheckBox"); } else if (elementName == "updateCompatibilityButton") { text = tr("updateCompatibilityButton"); + } else if (elementName == "audioBackendGroupBox") { + text = tr("audioBackendGroupBox"); } // Input @@ -553,6 +558,7 @@ void SettingsDialog::UpdateSettings() { Config::setUpdateChannel(ui->updateComboBox->currentText().toStdString()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked()); + Config::setAudioBackend(ui->audioBackendComboBox->currentText().toStdString()); #ifdef ENABLE_DISCORD_RPC auto* rpc = Common::Singleton::Instance(); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index a8b7799f4..c55206dab 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -11,7 +11,7 @@ 0 0 - 900 + 950 834 @@ -288,6 +288,29 @@ + + + + Audio Backend + + + + + + + cubeb + + + + + sdl + + + + + + + diff --git a/src/qt_gui/translations/ar.ts b/src/qt_gui/translations/ar.ts index cd71e083c..1f65db04a 100644 --- a/src/qt_gui/translations/ar.ts +++ b/src/qt_gui/translations/ar.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout مهلة إخفاء المؤشر عند الخمول + + + s + s + Controller @@ -707,6 +732,11 @@ Volume الصوت + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ فشل في إنشاء ملف سكريبت التحديث + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 677789d49..943e2d092 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Timeout for skjul markør ved inaktivitet + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Lydstyrke + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Kunne ikke oprette opdateringsscriptfilen + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/de.ts b/src/qt_gui/translations/de.ts index 64770f6fe..cbbef8215 100644 --- a/src/qt_gui/translations/de.ts +++ b/src/qt_gui/translations/de.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Inaktivitätszeitüberschreitung zum Ausblenden des Cursors + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Lautstärke + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Fehler beim Erstellen der Aktualisierungs-Skriptdatei + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/el.ts b/src/qt_gui/translations/el.ts index 05faa7bc9..8737f5216 100644 --- a/src/qt_gui/translations/el.ts +++ b/src/qt_gui/translations/el.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Χρόνος αδράνειας απόκρυψης δείκτη + + + s + s + Controller @@ -707,6 +732,11 @@ Volume ένταση + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Αποτυχία δημιουργίας του αρχείου σεναρίου ενημέρωσης + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 7488f2170..b0d49591c 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Hide Cursor Idle Timeout + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Audio Backend + MainWindow @@ -1419,6 +1449,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1578,4 +1623,32 @@ Failed to create the update script file + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 9b8b38129..70be2253d 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Tiempo de espera para ocultar cursor inactivo + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volumen + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ No se pudo crear el archivo del script de actualización + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 66ec0b4c0..54187cf9b 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -175,6 +175,26 @@ Delete DLC حذف محتوای اضافی (DLC) + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout مخفی کردن زمان توقف مکان نما + + + s + s + Controller @@ -707,6 +732,11 @@ Volume صدا + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played هرگز بازی نشده + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ فایل اسکریپت به روز رسانی ایجاد نشد + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + diff --git a/src/qt_gui/translations/fi.ts b/src/qt_gui/translations/fi.ts index 67ea079aa..bdc1eb703 100644 --- a/src/qt_gui/translations/fi.ts +++ b/src/qt_gui/translations/fi.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Inaktiivisuuden aikaraja kursorin piilottamiselle + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Äänenvoimakkuus + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Päivitysskripttitiedoston luominen epäonnistui + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/fr.ts b/src/qt_gui/translations/fr.ts index c092580a8..19b0f9358 100644 --- a/src/qt_gui/translations/fr.ts +++ b/src/qt_gui/translations/fr.ts @@ -175,6 +175,26 @@ Delete DLC Supprimer DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Délai d'inactivité pour masquer le curseur + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Jamais joué + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Échec de la création du fichier de script de mise à jour + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 7e60001f6..bc337f2cd 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -175,6 +175,26 @@ Delete DLC DLC-k törlése + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Kurzor inaktivitási időtúllépés + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Hangerő + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ A frissítési szkript fájl létrehozása nem sikerült + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/id.ts b/src/qt_gui/translations/id.ts index 31f377341..7a0bf5d05 100644 --- a/src/qt_gui/translations/id.ts +++ b/src/qt_gui/translations/id.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Batas waktu sembunyikan kursor tidak aktif + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Gagal membuat file skrip pembaruan + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/it.ts b/src/qt_gui/translations/it.ts index 8dc0817f1..1391fbc55 100644 --- a/src/qt_gui/translations/it.ts +++ b/src/qt_gui/translations/it.ts @@ -175,6 +175,26 @@ Delete DLC Elimina DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Timeout inattività per nascondere il cursore + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Mai Giocato + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Impossibile creare il file di script di aggiornamento - + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + + \ No newline at end of file diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 43fb37c2c..58f213e03 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout カーソル非アクティブタイムアウト + + + s + s + Controller @@ -707,6 +732,11 @@ Volume 音量 + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ アップデートスクリプトファイルの作成に失敗しました + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index ffaa8404f..75a1b53cf 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Hide Cursor Idle Timeout + + + s + s + Controller @@ -707,6 +732,11 @@ Volume 음량 + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Failed to create the update script file + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 5cf4d0e6b..092521fdf 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Žymeklio paslėpimo neveikimo laikas + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Garsumas + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Nepavyko sukurti atnaujinimo scenarijaus failo + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/nb.ts b/src/qt_gui/translations/nb.ts index ecc323879..cc41573db 100644 --- a/src/qt_gui/translations/nb.ts +++ b/src/qt_gui/translations/nb.ts @@ -108,32 +108,32 @@ SFO Viewer - SFO Viser + SFO viser Trophy Viewer - Trofé Viser + Trofé viser Open Folder... - Åpne Mappen... + Åpne mappen... Open Game Folder - Åpne Spillmappen + Åpne spillmappen Open Save Data Folder - Åpne Lagrede Data-mappen + Åpne lagrede datamappen Open Log Folder - Åpne Loggmappen + Åpne loggmappen @@ -143,17 +143,17 @@ Copy Name - Kopier Navn + Kopier navn Copy Serial - Kopier Serienummer + Kopier serienummer Copy All - Kopier Alle + Kopier alle @@ -163,18 +163,38 @@ Delete Game - Slett Spill + Slett spill Delete Update - Slett Oppdatering + Slett oppdatering Delete DLC Slett DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -251,12 +271,12 @@ Install Packages (PKG) - Installer Pakker (PKG) + Installer pakker (PKG) Boot Game - Start Spill + Start spill @@ -281,7 +301,7 @@ Recent Games - Nylige Spill + Nylige spill @@ -301,12 +321,12 @@ Show Game List - Vis Spill-listen + Vis spill-listen Game List Refresh - Oppdater Spill-listen + Oppdater spill-listen @@ -351,17 +371,17 @@ Download Cheats/Patches - Last ned Juks /Programrettelse + Last ned juks/programrettelse Dump Game List - Dump Spill-liste + Dump spill-liste PKG Viewer - PKG Viser + PKG viser @@ -381,12 +401,12 @@ Game List Icons - Spill-liste Ikoner + Spill-liste ikoner Game List Mode - Spill-liste Modus + Spill-liste modus @@ -444,7 +464,7 @@ Open Folder - Åpne Mappe + Åpne mappe @@ -452,7 +472,7 @@ Trophy Viewer - Trofé Viser + Trofé viser @@ -490,17 +510,17 @@ Enable Fullscreen - Aktiver Fullskjerm + Aktiver fullskjerm Enable Separate Update Folder - Aktiver Seperat Oppdateringsmappe + Aktiver seperat oppdateringsmappe Show Splash - Vis Velkomstbilde + Vis velkomstbilde @@ -525,12 +545,12 @@ Log Type - Log Type + Logg type Log Filter - Log Filter + Logg filter @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Skjul musepeker ved inaktivitet + + + s + s + Controller @@ -560,7 +585,7 @@ Back Button Behavior - Tilbakeknapp Atferd + Tilbakeknapp atferd @@ -585,7 +610,7 @@ Vblank Divider - Vblank Skillelinje + Vblank skillelinje @@ -595,7 +620,7 @@ Enable Shaders Dumping - Aktiver Skyggelegger Dumping + Aktiver dumping av skyggelegger @@ -630,22 +655,22 @@ Enable Debug Dumping - Aktiver Feilretting Dumping + Aktiver dumping av feilretting Enable Vulkan Validation Layers - Aktiver Vulkan Valideringslag + Aktiver Vulkan valideringslag Enable Vulkan Synchronization Validation - Aktiver Vulkan Synkroniseringslag + Aktiver Vulkan synkroniseringslag Enable RenderDoc Debugging - Aktiver RenderDoc Feilretting + Aktiver RenderDoc feilretting @@ -670,12 +695,12 @@ GUI Settings - GUI-Innstillinger + GUI-innstillinger Disable Trophy Pop-ups - Disable Trophy Pop-ups + Deaktiver trofé hurtigmeny @@ -685,28 +710,33 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + Oppdater kompatibilitets-database ved oppstart Game Compatibility - Game Compatibility + Spill kompatibilitet Display Compatibility Data - Display Compatibility Data + Vis kompatibilitets-data Update Compatibility Database - Update Compatibility Database + Oppdater kompatibilitets-database Volume Volum + + + Audio Backend + Audio Backend + MainWindow @@ -728,7 +758,7 @@ Download Patches For All Games - Last ned oppdateringer for alle spill + Last ned programrettelser for alle spill @@ -778,7 +808,7 @@ PKG Extraction - PKG-ekstraksjon + PKG-utpakking @@ -788,7 +818,7 @@ PKG and Game versions match: - PKG- og spillversjoner stemmer overens: + PKG og spillversjoner stemmer overens: @@ -813,7 +843,7 @@ DLC Installation - DLC-installasjon + DLC installasjon @@ -833,7 +863,7 @@ PKG is a patch, please install the game first! - PKG er en oppdatering, vennligst installer spillet først! + PKG er en programrettelse, vennligst installer spillet først! @@ -871,7 +901,7 @@ defaultTextEdit_MSG - Juks/programrettelse er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned juks individuelt ved å velge pakkebrønn og klikke på nedlastingsknappen.\nPå fanen programrettelse kan du laste ned alle programrettelser samtidig, velge hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler Juksene/Programrettelsene,\nvær vennlig å rapportere problemer til jukse/programrettelse utvikleren.\n\nHar du laget en ny juks? Besøk:\nhttps://github.com/shadps4-emu/ps4_cheats + Juks/programrettelse er eksperimentelle.\nBruk med forsiktighet.\n\nLast ned juks individuelt ved å velge pakkebrønn og klikke på nedlastingsknappen.\nPå fanen programrettelse kan du laste ned alle programrettelser samtidig, velge hvilke du ønsker å bruke, og lagre valget ditt.\n\nSiden vi ikke utvikler Juks/Programrettelse,\nvær vennlig å rapportere problemer til juks/programrettelse utvikleren.\n\nHar du laget en ny juks? Besøk:\nhttps://github.com/shadps4-emu/ps4_cheats @@ -921,7 +951,7 @@ You can delete the cheats you don't want after downloading them. - Du kan slette juksene du ikke ønsker etter å ha lastet dem ned. + Du kan slette juks du ikke ønsker etter å ha lastet dem ned. @@ -936,7 +966,7 @@ Download Patches - Last ned programrettelse + Last ned programrettelser @@ -946,7 +976,7 @@ Cheats - Jukser + Juks @@ -1001,7 +1031,7 @@ Invalid Source - Ugyldig Kilde + Ugyldig kilde @@ -1011,7 +1041,7 @@ File Exists - Filen Eksisterer + Filen eksisterer @@ -1031,7 +1061,7 @@ Cheats Not Found - Fant ikke juksene + Fant ikke juks @@ -1041,12 +1071,12 @@ Cheats Downloaded Successfully - Juksene ble lastet ned + Juks ble lastet ned CheatsDownloadedSuccessfully_MSG - Du har lastet ned jukser for denne versjonen av spillet fra den valgte pakkebrønnen. Du kan prøve å laste ned fra en annen pakkebrønn, hvis det er tilgjengelig, vil det også være mulig å bruke det ved å velge filen fra listen. + Du har lastet ned juks for denne versjonen av spillet fra den valgte pakkebrønnen. Du kan prøve å laste ned fra en annen pakkebrønn, hvis det er tilgjengelig, vil det også være mulig å bruke det ved å velge filen fra listen. @@ -1066,7 +1096,7 @@ DownloadComplete_MSG - Programrettelser ble lastet ned! Alle programrettelsene tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med jukser. Hvis programrettelsen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. + Programrettelser ble lastet ned! Alle programrettelsene tilgjengelige for alle spill har blitt lastet ned, det er ikke nødvendig å laste dem ned individuelt for hvert spill som skjer med juks. Hvis programrettelsen ikke vises, kan det hende at den ikke finnes for den spesifikke serienummeret og versjonen av spillet. @@ -1136,7 +1166,7 @@ Can't apply cheats before the game is started - Kan ikke bruke juksene før spillet er startet. + Kan ikke bruke juks før spillet er startet. @@ -1189,7 +1219,7 @@ showSplashCheckBox - Vis Velkomstbilde:\nViser spillets velkomstbilde (et spesialbilde) når spillet starter. + Vis velkomstbilde:\nViser spillets velkomstbilde (et spesialbilde) når spillet starter. @@ -1209,12 +1239,12 @@ logTypeGroupBox - Logtype:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter for etterligneren. + Logg type:\nAngir om loggvinduets utdata skal synkroniseres for ytelse. Kan ha negative effekter for etterligneren. logFilter - Loggfilter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. + Logg filter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i listen og logger alle nivåer etter det. @@ -1229,7 +1259,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). + Deaktiver trofé hurtigmeny:\nDeaktiver trofévarsler i spillet. Trofé-fremgang kan fortsatt ved help av troféviseren (høyreklikk på spillet i hovedvinduet). @@ -1249,17 +1279,17 @@ enableCompatibilityCheckBox - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. + Vis kompatibilitets-data:\nViser informasjon om spillkompatibilitet i tabellvisning. Aktiver "Oppdater kompatibilitets-data ved oppstart" for oppdatert informasjon. checkCompatibilityOnStartupCheckBox - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Oppdater kompatibilitets-data ved oppstart:\nOppdaterer kompatibilitets-databasen automatisk når shadPS4 starter. updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + Oppdater kompatibilitets-database:\nOppdater kompatibilitets-databasen nå. @@ -1299,7 +1329,7 @@ graphicsAdapterGroupBox - Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en etterligneren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å bestemme den automatisk. + Grafikkenhet:\nI systemer med flere GPU-er, velg GPU-en etterligneren skal bruke fra rullegardinlisten,\neller velg "Auto Select" for å velge automatisk. @@ -1309,12 +1339,12 @@ heightDivider - Vblank Skillelinje:\nBildehastigheten som etterligneren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten av spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! + Vblank skillelinje:\nBildehastigheten som etterligneren oppdaterer ved, multipliseres med dette tallet. Endring av dette kan ha negative effekter, som å øke hastigheten av spillet, eller ødelegge kritisk spillfunksjonalitet som ikke forventer at dette endres! dumpShadersCheckBox - Aktiver skyggelegger-dumping:\nFor teknisk feilsøking lagrer skyggeleggene fra spillet i en mappe mens de gjengis. + Aktiver dumping av skyggelegger:\nFor teknisk feilsøking lagrer skyggeleggerne fra spillet i en mappe mens de gjengis. @@ -1377,7 +1407,7 @@ Compatibility - Compatibility + Kompatibilitet @@ -1412,37 +1442,52 @@ Never Played - Never Played + Aldri spilt + + + + h + h + + + + m + m + + + + s + s Compatibility is untested - Compatibility is untested + kompatibilitet er utestet Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + Spillet initialiseres ikke riktig / krasjer etterligneren Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + Spillet starter, men viser bare en tom skjerm Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + Spillet viser et bilde, men går ikke forbi menyen Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + Spillet har spillbrytende feil eller uspillbar ytelse Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + Spillet kan fullføres med spillbar ytelse og ingen store feil @@ -1450,7 +1495,7 @@ Auto Updater - Automatisk oppdaterering + Automatisk oppdatering @@ -1465,7 +1510,7 @@ Failed to parse update information. - Kunne ikke analysere oppdateringsinformasjonen. + Kunne ikke analysere oppdaterings-informasjonen. @@ -1545,7 +1590,7 @@ Network error occurred while trying to access the URL - Nettverksfeil oppstod mens du prøvde å få tilgang til URL + Nettverksfeil oppstod mens vi prøvde å få tilgang til URL @@ -1573,4 +1618,32 @@ Kunne ikke opprette oppdateringsskriptfilen + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/nl.ts b/src/qt_gui/translations/nl.ts index eb49c83ab..5cd4a4224 100644 --- a/src/qt_gui/translations/nl.ts +++ b/src/qt_gui/translations/nl.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Inactiviteit timeout voor het verbergen van de cursor + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Kon het update-scriptbestand niet maken + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index cb9e7987d..b85393bb0 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Czas oczekiwania na ukrycie kursora przy bezczynności + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Głośność + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Nie udało się utworzyć pliku skryptu aktualizacji + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index a668c61d1..8ab8db093 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -175,6 +175,26 @@ Delete DLC Deletar DLC + + + Compatibility... + Compatibilidade... + + + + Update database + Atualizar banco de dados + + + + View report + Ver status + + + + Submit a report + Enviar status + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Tempo de Inatividade para Ocultar Cursor + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volume + + + Audio Backend + Backend de Áudio + MainWindow @@ -1414,6 +1444,21 @@ Never Played Nunca jogado + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Falha ao criar o arquivo de script de atualização + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + 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 ae7e2efcb..00547d6ba 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Timeout pentru ascunderea cursorului inactiv + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Volum + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Nu s-a putut crea fișierul script de actualizare + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 0349dfce7..505a05a3e 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -118,7 +118,7 @@ Open Folder... - Открыть Папку... + Открыть папку... @@ -128,12 +128,12 @@ Open Save Data Folder - Открыть Папку Сохранений + Открыть папку сохранений Open Log Folder - Открыть Папку Логов + Открыть папку логов @@ -175,6 +175,26 @@ Delete DLC Удалить DLC + + + Compatibility... + Совместимость... + + + + Update database + Обновить базу данных + + + + View report + Посмотреть отчет + + + + Submit a report + Отправить отчет + Shortcut creation @@ -452,7 +472,7 @@ Trophy Viewer - Трофеи + Просмотр трофеев @@ -550,7 +570,12 @@ Hide Cursor Idle Timeout - Тайм-аут скрытия курсора при бездействии + Время скрытия курсора при бездействии + + + + s + сек @@ -675,7 +700,7 @@ Disable Trophy Pop-ups - Disable Trophy Pop-ups + Отключить уведомления о трофеях @@ -685,28 +710,33 @@ 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 Громкость + + + Audio Backend + Звуковая Подсистема + MainWindow @@ -763,7 +793,7 @@ ELF files (*.bin *.elf *.oelf) - Файл ELF (*.bin *.elf *.oelf) + Файлы ELF (*.bin *.elf *.oelf) @@ -818,7 +848,7 @@ Would you like to install DLC: %1? - Вы хотите установить DLC: %1?? + Вы хотите установить DLC: %1? @@ -866,7 +896,7 @@ Cheats / Patches for - Cheats / Patches for + Читы и патчи для @@ -1189,7 +1219,7 @@ showSplashCheckBox - Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска игры. + Показывать заставку:\nОтображает заставку игры (специальное изображение) во время запуска. @@ -1229,12 +1259,12 @@ 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Всегда: Курсор всегда будет скрыт. @@ -1249,17 +1279,17 @@ 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. + Обновлять базу совместимости при запуске:\nАвтоматически обновлять базу данных совместимости при запуске shadPS4. updateCompatibilityButton - Update Compatibility Database:\nImmediately update the compatibility database. + Обновить базу совместимости:\nНемедленно обновить базу данных совместимости. @@ -1299,7 +1329,7 @@ graphicsAdapterGroupBox - Графическое устройство:\nВ системах с несколькими GPU выберите GPU, который будет использовать эмулятор из выпадающего списка,\nили выберите "Auto Select", чтобы определить его автоматически. + Графическое устройство:\nВ системах с несколькими GPU выберите GPU, который будет использовать эмулятор.\nВыберите "Auto Select", чтобы определить его автоматически. @@ -1377,7 +1407,7 @@ Compatibility - Compatibility + Совместимость @@ -1412,37 +1442,52 @@ Never Played - Never Played + Вы не играли + + + + h + ч + + + + m + м + + + + 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 + Игра может быть пройдена с хорошей производительностью и без серьезных сбоев @@ -1525,7 +1570,7 @@ Update - Обновиться + Обновить @@ -1573,4 +1618,32 @@ Не удалось создать файл скрипта обновления + + GameListUtils + + + B + Б + + + + KB + КБ + + + + MB + МБ + + + + GB + ГБ + + + + TB + ТБ + + \ No newline at end of file diff --git a/src/qt_gui/translations/sq.ts b/src/qt_gui/translations/sq.ts index c3280e0ea..0c318f4f7 100644 --- a/src/qt_gui/translations/sq.ts +++ b/src/qt_gui/translations/sq.ts @@ -175,6 +175,26 @@ Delete DLC Fshi DLC-në + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Koha për fshehjen e kursorit joaktiv + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Vëllimi i zërit + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Nuk është luajtur kurrë + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Krijimi i skedarit skript të përditësimit dështoi + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 4d644ecfe..2845af462 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout İmleç İçin Hareketsizlik Zaman Aşımı + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Ses seviyesi + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Güncelleme betiği dosyası oluşturulamadı + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 66cbf2bd9..8abfca435 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -175,6 +175,26 @@ Delete DLC Видалити DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Тайм-аут приховування курсора при бездіяльності + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Гучність + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Не вдалося створити файл скрипта оновлення + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 9441d1697..7d0e9a2cd 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout Thời gian chờ ẩn con trỏ + + + s + s + Controller @@ -707,6 +732,11 @@ Volume Âm lượng + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ Không thể tạo tệp kịch bản cập nhật + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 92071c5b2..4ceb91315 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -175,6 +175,26 @@ Delete DLC 删除DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout 光标空闲超时隐藏 + + + s + s + Controller @@ -707,6 +732,11 @@ Volume 音量 + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ 无法创建更新脚本文件 + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index be7cd69ec..3d27267b6 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -175,6 +175,26 @@ Delete DLC Delete DLC + + + Compatibility... + Compatibility... + + + + Update database + Update database + + + + View report + View report + + + + Submit a report + Submit a report + Shortcut creation @@ -552,6 +572,11 @@ Hide Cursor Idle Timeout 游標空閒超時隱藏 + + + s + s + Controller @@ -707,6 +732,11 @@ Volume 音量 + + + Audio Backend + Audio Backend + MainWindow @@ -1414,6 +1444,21 @@ Never Played Never Played + + + h + h + + + + m + m + + + + s + s + Compatibility is untested @@ -1573,4 +1618,32 @@ 無法創建更新腳本文件 + + GameListUtils + + + B + B + + + + KB + KB + + + + MB + MB + + + + GB + GB + + + + TB + TB + + \ No newline at end of file diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 2f606eb45..85bed589b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -304,10 +304,12 @@ Id EmitBitFieldSExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id Id EmitBitFieldUExtract(EmitContext& ctx, IR::Inst* inst, Id base, Id offset, Id count); Id EmitBitReverse32(EmitContext& ctx, Id value); Id EmitBitCount32(EmitContext& ctx, Id value); +Id EmitBitCount64(EmitContext& ctx, Id value); Id EmitBitwiseNot32(EmitContext& ctx, Id value); Id EmitFindSMsb32(EmitContext& ctx, Id value); Id EmitFindUMsb32(EmitContext& ctx, Id value); Id EmitFindILsb32(EmitContext& ctx, Id value); +Id EmitFindILsb64(EmitContext& ctx, Id value); Id EmitSMin32(EmitContext& ctx, Id a, Id b); Id EmitUMin32(EmitContext& ctx, Id a, Id b); Id EmitSMax32(EmitContext& ctx, Id a, Id b); @@ -318,7 +320,8 @@ Id EmitSLessThan32(EmitContext& ctx, Id lhs, Id rhs); Id EmitSLessThan64(EmitContext& ctx, Id lhs, Id rhs); Id EmitULessThan32(EmitContext& ctx, Id lhs, Id rhs); Id EmitULessThan64(EmitContext& ctx, Id lhs, Id rhs); -Id EmitIEqual(EmitContext& ctx, Id lhs, Id rhs); +Id EmitIEqual32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitIEqual64(EmitContext& ctx, Id lhs, Id rhs); Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs); Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs); Id EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 02af92385..def1f816e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -201,6 +201,10 @@ Id EmitBitCount32(EmitContext& ctx, Id value) { return ctx.OpBitCount(ctx.U32[1], value); } +Id EmitBitCount64(EmitContext& ctx, Id value) { + return ctx.OpBitCount(ctx.U64, value); +} + Id EmitBitwiseNot32(EmitContext& ctx, Id value) { return ctx.OpNot(ctx.U32[1], value); } @@ -217,6 +221,10 @@ Id EmitFindILsb32(EmitContext& ctx, Id value) { return ctx.OpFindILsb(ctx.U32[1], value); } +Id EmitFindILsb64(EmitContext& ctx, Id value) { + return ctx.OpFindILsb(ctx.U64, value); +} + Id EmitSMin32(EmitContext& ctx, Id a, Id b) { return ctx.OpSMin(ctx.U32[1], a, b); } @@ -277,7 +285,11 @@ Id EmitULessThan64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpULessThan(ctx.U1[1], lhs, rhs); } -Id EmitIEqual(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitIEqual32(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpIEqual(ctx.U1[1], lhs, rhs); +} + +Id EmitIEqual64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpIEqual(ctx.U1[1], lhs, rhs); } diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index f96fd0f40..3a2b01a90 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -100,8 +100,12 @@ void Translator::EmitScalarAlu(const GcnInst& inst) { return S_BREV_B32(inst); case Opcode::S_BCNT1_I32_B32: return S_BCNT1_I32_B32(inst); + case Opcode::S_BCNT1_I32_B64: + return S_BCNT1_I32_B64(inst); case Opcode::S_FF1_I32_B32: return S_FF1_I32_B32(inst); + case Opcode::S_FF1_I32_B64: + return S_FF1_I32_B64(inst); case Opcode::S_AND_SAVEEXEC_B64: return S_SAVEEXEC_B64(NegateMode::None, false, inst); case Opcode::S_ORN2_SAVEEXEC_B64: @@ -585,12 +589,25 @@ void Translator::S_BCNT1_I32_B32(const GcnInst& inst) { ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); } +void Translator::S_BCNT1_I32_B64(const GcnInst& inst) { + const IR::U32 result = ir.BitCount(GetSrc64(inst.src[0])); + SetDst(inst.dst[0], result); + ir.SetScc(ir.INotEqual(result, ir.Imm32(0))); +} + void Translator::S_FF1_I32_B32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 result{ir.Select(ir.IEqual(src0, ir.Imm32(0U)), ir.Imm32(-1), ir.FindILsb(src0))}; SetDst(inst.dst[0], result); } +void Translator::S_FF1_I32_B64(const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + const IR::U32 result{ + ir.Select(ir.IEqual(src0, ir.Imm64(u64(0))), ir.Imm32(-1), ir.FindILsb(src0))}; + SetDst(inst.dst[0], result); +} + void Translator::S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst) { // This instruction normally operates on 64-bit data (EXEC, VCC, SGPRs) // However here we flatten it to 1-bit EXEC and 1-bit VCC. For the destination diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index fd4d8d86a..e8584ec2f 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -111,7 +111,9 @@ public: void S_NOT_B64(const GcnInst& inst); void S_BREV_B32(const GcnInst& inst); void S_BCNT1_I32_B32(const GcnInst& inst); + void S_BCNT1_I32_B64(const GcnInst& inst); void S_FF1_I32_B32(const GcnInst& inst); + void S_FF1_I32_B64(const GcnInst& inst); void S_GETPC_B64(u32 pc, const GcnInst& inst); void S_SAVEEXEC_B64(NegateMode negate, bool is_or, const GcnInst& inst); void S_ABS_I32(const GcnInst& inst); diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index c241ec984..c9d97679f 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1273,8 +1273,15 @@ U32 IREmitter::BitReverse(const U32& value) { return Inst(Opcode::BitReverse32, value); } -U32 IREmitter::BitCount(const U32& value) { - return Inst(Opcode::BitCount32, value); +U32 IREmitter::BitCount(const U32U64& value) { + switch (value.Type()) { + case Type::U32: + return Inst(Opcode::BitCount32, value); + case Type::U64: + return Inst(Opcode::BitCount64, value); + default: + ThrowInvalidType(value.Type()); + } } U32 IREmitter::BitwiseNot(const U32& value) { @@ -1289,8 +1296,15 @@ U32 IREmitter::FindUMsb(const U32& value) { return Inst(Opcode::FindUMsb32, value); } -U32 IREmitter::FindILsb(const U32& value) { - return Inst(Opcode::FindILsb32, value); +U32 IREmitter::FindILsb(const U32U64& value) { + switch (value.Type()) { + case Type::U32: + return Inst(Opcode::FindILsb32, value); + case Type::U64: + return Inst(Opcode::FindILsb64, value); + default: + ThrowInvalidType(value.Type()); + } } U32 IREmitter::SMin(const U32& a, const U32& b) { @@ -1345,7 +1359,9 @@ U1 IREmitter::IEqual(const U32U64& lhs, const U32U64& rhs) { } switch (lhs.Type()) { case Type::U32: - return Inst(Opcode::IEqual, lhs, rhs); + return Inst(Opcode::IEqual32, lhs, rhs); + case Type::U64: + return Inst(Opcode::IEqual64, lhs, rhs); default: ThrowInvalidType(lhs.Type()); } diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 4cf44107e..4679a0133 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -229,12 +229,12 @@ public: [[nodiscard]] U32 BitFieldExtract(const U32& base, const U32& offset, const U32& count, bool is_signed = false); [[nodiscard]] U32 BitReverse(const U32& value); - [[nodiscard]] U32 BitCount(const U32& value); + [[nodiscard]] U32 BitCount(const U32U64& value); [[nodiscard]] U32 BitwiseNot(const U32& value); [[nodiscard]] U32 FindSMsb(const U32& value); [[nodiscard]] U32 FindUMsb(const U32& value); - [[nodiscard]] U32 FindILsb(const U32& value); + [[nodiscard]] U32 FindILsb(const U32U64& value); [[nodiscard]] U32 SMin(const U32& a, const U32& b); [[nodiscard]] U32 UMin(const U32& a, const U32& b); [[nodiscard]] U32 IMin(const U32& a, const U32& b, bool is_signed); diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index aafd43ea8..cf2c3b67e 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -284,11 +284,13 @@ OPCODE(BitFieldSExtract, U32, U32, OPCODE(BitFieldUExtract, U32, U32, U32, U32, ) OPCODE(BitReverse32, U32, U32, ) OPCODE(BitCount32, U32, U32, ) +OPCODE(BitCount64, U32, U64, ) OPCODE(BitwiseNot32, U32, U32, ) OPCODE(FindSMsb32, U32, U32, ) OPCODE(FindUMsb32, U32, U32, ) OPCODE(FindILsb32, U32, U32, ) +OPCODE(FindILsb64, U32, U64, ) OPCODE(SMin32, U32, U32, U32, ) OPCODE(UMin32, U32, U32, U32, ) OPCODE(SMax32, U32, U32, U32, ) @@ -299,7 +301,8 @@ OPCODE(SLessThan32, U1, U32, OPCODE(SLessThan64, U1, U64, U64, ) OPCODE(ULessThan32, U1, U32, U32, ) OPCODE(ULessThan64, U1, U64, U64, ) -OPCODE(IEqual, U1, U32, U32, ) +OPCODE(IEqual32, U1, U32, U32, ) +OPCODE(IEqual64, U1, U64, U64, ) OPCODE(SLessThanEqual, U1, U32, U32, ) OPCODE(ULessThanEqual, U1, U32, U32, ) OPCODE(SGreaterThan, U1, U32, U32, ) diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index 16b07e1a1..fcf2f7d9f 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -391,9 +391,12 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::UGreaterThanEqual: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a >= b; }); return; - case IR::Opcode::IEqual: + case IR::Opcode::IEqual32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a == b; }); return; + case IR::Opcode::IEqual64: + FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a == b; }); + return; case IR::Opcode::INotEqual: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a != b; }); return; diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index db1a2edd2..e6d23bfe7 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -249,7 +249,7 @@ std::pair TryDisableAnisoLod0(const IR::Inst* inst) { // Select should be based on zero check const auto* prod0 = inst->Arg(0).InstRecursive(); - if (prod0->GetOpcode() != IR::Opcode::IEqual || + if (prod0->GetOpcode() != IR::Opcode::IEqual32 || !(prod0->Arg(1).IsImmediate() && prod0->Arg(1).U32() == 0u)) { return not_found; } diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index d9a8b7cac..58b286e9c 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -364,6 +364,16 @@ enum class Filter : u64 { AnisoLinear = 3, }; +constexpr bool IsAnisoFilter(const Filter filter) { + switch (filter) { + case Filter::AnisoPoint: + case Filter::AnisoLinear: + return true; + default: + return false; + } +} + enum class MipFilter : u64 { None = 0, Point = 1, @@ -435,6 +445,23 @@ struct Sampler { float MaxLod() const noexcept { return static_cast(max_lod.Value()) / 256.0f; } + + float MaxAniso() const { + switch (max_aniso) { + case AnisoRatio::One: + return 1.0f; + case AnisoRatio::Two: + return 2.0f; + case AnisoRatio::Four: + return 4.0f; + case AnisoRatio::Eight: + return 8.0f; + case AnisoRatio::Sixteen: + return 16.0f; + default: + UNREACHABLE(); + } + } }; } // namespace AmdGpu diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 54a9b9873..62838140c 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -249,6 +249,11 @@ public: return properties.limits.maxSamplerLodBias; } + /// Returns the maximum sampler anisotropy. + float MaxSamplerAnisotropy() const { + return properties.limits.maxSamplerAnisotropy; + } + /// Returns the maximum number of push descriptors. u32 MaxPushDescriptors() const { return push_descriptor_props.maxPushDescriptors; diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index dbdabe0d9..ab61af6a4 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -137,6 +137,7 @@ std::vector GetInstanceExtensions(Frontend::WindowSystemType window // Add the windowing system specific extension std::vector extensions; extensions.reserve(7); + extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); switch (window_type) { case Frontend::WindowSystemType::Headless: @@ -347,6 +348,17 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e .valueCount = 1, .pValues = &enable_force_barriers, }, +#ifdef __APPLE__ + // MoltenVK debug mode turns on additional device loss error details, so + // use the crash diagnostic setting as an indicator of whether to turn it on. + vk::LayerSettingEXT{ + .pLayerName = "MoltenVK", + .pSettingName = "MVK_CONFIG_DEBUG", + .type = vk::LayerSettingTypeEXT::eBool32, + .valueCount = 1, + .pValues = &enable_crash_diagnostic, + } +#endif }; vk::StructureChain instance_ci_chain = { diff --git a/src/video_core/texture_cache/sampler.cpp b/src/video_core/texture_cache/sampler.cpp index 9f4bc7a7e..5ce10ff0e 100644 --- a/src/video_core/texture_cache/sampler.cpp +++ b/src/video_core/texture_cache/sampler.cpp @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include "video_core/amdgpu/resource.h" #include "video_core/renderer_vulkan/liverpool_to_vk.h" #include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/texture_cache/sampler.h" @@ -12,6 +14,11 @@ Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sample LOG_WARNING(Render_Vulkan, "Texture requires gamma correction"); } using namespace Vulkan; + const bool anisotropyEnable = instance.IsAnisotropicFilteringSupported() && + (AmdGpu::IsAnisoFilter(sampler.xy_mag_filter) || + AmdGpu::IsAnisoFilter(sampler.xy_min_filter)); + const float maxAnisotropy = + std::clamp(sampler.MaxAniso(), 1.0f, instance.MaxSamplerAnisotropy()); const vk::SamplerCreateInfo sampler_ci = { .magFilter = LiverpoolToVK::Filter(sampler.xy_mag_filter), .minFilter = LiverpoolToVK::Filter(sampler.xy_min_filter), @@ -20,6 +27,8 @@ Sampler::Sampler(const Vulkan::Instance& instance, const AmdGpu::Sampler& sample .addressModeV = LiverpoolToVK::ClampMode(sampler.clamp_y), .addressModeW = LiverpoolToVK::ClampMode(sampler.clamp_z), .mipLodBias = std::min(sampler.LodBias(), instance.MaxSamplerLodBias()), + .anisotropyEnable = anisotropyEnable, + .maxAnisotropy = maxAnisotropy, .compareEnable = sampler.depth_compare_func != AmdGpu::DepthCompare::Never, .compareOp = LiverpoolToVK::DepthCompare(sampler.depth_compare_func), .minLod = sampler.MinLod(), diff --git a/src/video_core/texture_cache/tile_manager.cpp b/src/video_core/texture_cache/tile_manager.cpp index f6b2e2beb..de108843b 100644 --- a/src/video_core/texture_cache/tile_manager.cpp +++ b/src/video_core/texture_cache/tile_manager.cpp @@ -25,6 +25,7 @@ static vk::Format DemoteImageFormatForDetiling(vk::Format format) { switch (format) { case vk::Format::eR8Uint: case vk::Format::eR8Unorm: + case vk::Format::eR8Snorm: return vk::Format::eR8Uint; case vk::Format::eR4G4B4A4UnormPack16: case vk::Format::eB5G6R5UnormPack16: @@ -41,6 +42,7 @@ static vk::Format DemoteImageFormatForDetiling(vk::Format format) { case vk::Format::eR8G8B8A8Snorm: case vk::Format::eR8G8B8A8Uint: case vk::Format::eR32Sfloat: + case vk::Format::eD32Sfloat: case vk::Format::eR32Uint: case vk::Format::eR16G16Sfloat: case vk::Format::eR16G16Unorm: