Merge branch 'main' into keys

This commit is contained in:
georgemoralis 2025-01-01 11:54:49 +02:00 committed by GitHub
commit d6325918dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
105 changed files with 1924 additions and 1541 deletions

View File

@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Application Bug Report
description: Problem with the application itself (ie. bad file path handling, UX issue)
title: "[APP BUG]: "
body:
- type: markdown
attributes:
value: |
## Important: Read First
**Please do not make support requests on GitHub. Our issue tracker is for tracking bugs and feature requests only.
If you have a support request or are unsure about the nature of your issue please contact us on [discord](https://discord.gg/bFJxfftGW6).**
Please make an effort to make sure your issue isn't already reported.
Do not create issues involving software piracy, our rules specifically prohibit this. Otherwise your issue will be closed and you will be banned in this repository.
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched for a similar issue in this repository and did not find one.
required: true
- label: I am using an official build obtained from [releases](https://github.com/shadps4-emu/shadPS4/releases) or updated one of those builds using its in-app updater.
required: true
- type: textarea
id: desc
attributes:
label: Describe the Bug
description: "A clear and concise description of what the bug is"
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction Steps
description: "Detailed steps to reproduce the behavior"
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: "A clear and concise description of what you expected to happen"
validations:
required: false
- type: input
id: os
attributes:
label: Specify OS Version
placeholder: "Example: Windows 11, Arch Linux, MacOS 15"
validations:
required: true

10
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/bFJxfftGW6
about: Get direct support and hang out with us
- name: Wiki
url: https://github.com/shadps4-emu/shadPS4/wiki
about: Information, guides, etc.

View File

@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Feature Request
description: Suggest a new feature or improve an existing one
title: "[Feature Request]: "
body:
- type: markdown
attributes:
value: |
## Important: Read First
Please make an effort to make sure your issue isn't already reported.
Do not create issues involving software piracy, our rules specifically prohibit this. Otherwise your issue will be closed and you will be banned in this repository.
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched for a similar issue in this repository and did not find one.
required: true
- type: textarea
id: desc
attributes:
label: Description
description: |
A concise description of the feature you want
Include step by step examples of how the feature should work under various circumstances
validations:
required: true
- type: textarea
id: reason
attributes:
label: Reason
description: |
Give a reason why you want this feature
- How will it make things easier for you?
- How does this feature help your enjoyment of the emulator?
- What does it provide that isn't being provided currently?
validations:
required: true
- type: textarea
id: examples
attributes:
label: Examples
description: |
Provide examples of the feature as implemented by other software
Include screenshots or video if you like to help demonstrate how you'd like this feature to work
validations:
required: false

View File

@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Docs - https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Game Emulation Bug Report
description: Problem in a game (ie. graphical artifacts, crashes, etc.)
title: "[GAME BUG]: "
body:
- type: markdown
attributes:
value: |
## Important: Read First
**Please do not make support requests on GitHub. Our issue tracker is for tracking bugs and feature requests only.
If you have a support request or are unsure about the nature of your issue please contact us on [discord](https://discord.gg/bFJxfftGW6).**
You can also check the [Game Compatibility Repository](https://github.com/shadps4-emu/shadps4-game-compatibility) for the information about the status of the game.
Please make an effort to make sure your issue isn't already reported.
Do not create issues involving software piracy, our rules specifically prohibit this. Otherwise your issue will be closed and you will be banned in this repository.
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched for a similar issue in this repository and did not find one.
required: true
- label: I am using an official build obtained from [releases](https://github.com/shadps4-emu/shadPS4/releases) or updated one of those builds using its in-app updater.
required: true
- label: I have re-dumped the game and performed a clean install without mods.
required: true
- label: I have disabled all patches and cheats.
required: true
- label: I have all the required [system modules](https://github.com/shadps4-emu/shadps4-game-compatibility?tab=readme-ov-file#informations) installed.
required: true
- type: textarea
id: desc
attributes:
label: Describe the Bug
description: "A clear and concise description of what the bug is"
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction Steps
description: "Detailed steps to reproduce the behavior"
validations:
required: true
- type: input
id: os
attributes:
label: Specify OS Version
placeholder: "Example: Windows 11, Arch Linux, MacOS 15"
validations:
required: true
- type: input
id: cpu
attributes:
label: CPU
placeholder: "Example: Intel Core i7-8700"
validations:
required: true
- type: input
id: gpu
attributes:
label: GPU
placeholder: "Example: nVidia GTX 1650"
validations:
required: true
- type: input
id: ram
attributes:
label: Amount of RAM in GB
placeholder: "Example: 16 GB"
validations:
required: true
- type: input
id: vram
attributes:
label: Amount of VRAM in GB
placeholder: "Example: 8 GB"
validations:
required: true
- type: textarea
id: logs
attributes:
label: "Logs"
description: Attach any logs here. Log can be found by right clicking on a game name -> Open Folder... -> Open Log Folder. Make sure that the log type is set to `sync`.
validations:
required: false

4
.gitmodules vendored
View File

@ -119,7 +119,3 @@
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

View File

@ -127,7 +127,6 @@ 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)
@ -189,6 +188,8 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp
src/core/libraries/ajm/ajm_context.cpp
src/core/libraries/ajm/ajm_context.h
src/core/libraries/ajm/ajm_error.h
src/core/libraries/ajm/ajm_instance_statistics.cpp
src/core/libraries/ajm/ajm_instance_statistics.h
src/core/libraries/ajm/ajm_instance.cpp
src/core/libraries/ajm/ajm_instance.h
src/core/libraries/ajm/ajm_mp3.cpp
@ -201,7 +202,6 @@ set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
src/core/libraries/audio/audioout.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
@ -425,6 +425,8 @@ set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp
set(MISC_LIBS src/core/libraries/screenshot/screenshot.cpp
src/core/libraries/screenshot/screenshot.h
src/core/libraries/move/move.cpp
src/core/libraries/move/move.h
)
set(DEV_TOOLS src/core/devtools/layer.cpp
@ -495,7 +497,6 @@ 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
@ -697,6 +698,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/ir/post_order.h
src/shader_recompiler/ir/program.cpp
src/shader_recompiler/ir/program.h
src/shader_recompiler/ir/reinterpret.h
src/shader_recompiler/ir/reg.h
src/shader_recompiler/ir/type.cpp
src/shader_recompiler/ir/type.h
@ -887,7 +889,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 cubeb::cubeb)
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_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")
@ -915,6 +917,7 @@ if (APPLE)
DEPENDS ${MVK_DYLIB_SRC}
COMMAND cmake -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST})
add_custom_target(CopyMoltenVK DEPENDS ${MVK_DYLIB_DST})
add_dependencies(CopyMoltenVK MoltenVK)
add_dependencies(shadps4 CopyMoltenVK)
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks")
else()

View File

@ -1,7 +0,0 @@
ISC License
<copyright notice>
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.

View File

@ -121,6 +121,11 @@ R3 | M | |
- [**skmp**](https://github.com/skmp)
- [**wheremyfoodat**](https://github.com/wheremyfoodat)
- [**raziel1000**](https://github.com/raziel1000)
- [**viniciuslrangel**](https://github.com/viniciuslrangel)
- [**roamic**](https://github.com/vladmikhalin)
- [**poly**](https://github.com/polybiusproxy)
- [**squidbus**](https://github.com/squidbus)
- [**frodo**](https://github.com/baggins183)
Logo is done by [**Xphalnos**](https://github.com/Xphalnos)

View File

@ -228,16 +228,6 @@ 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

@ -1 +1 @@
Subproject commit 5ad3ee5d2f84342950c3fe93dec97719574d1932
Subproject commit 9f0b616d9e2c39464d2a859b79dbc655c4a30e7e

1
externals/cubeb vendored

@ -1 +0,0 @@
Subproject commit 9a9d034c51859a045a34f201334f612c51e6c19d

View File

@ -68,7 +68,6 @@ static bool separateupdatefolder = false;
static bool compatibilityData = false;
static bool checkCompatibilityOnStartup = false;
static std::string trophyKey = "";
static std::string audioBackend = "cubeb";
// Gui
std::vector<std::filesystem::path> settings_install_dirs = {};
@ -249,10 +248,6 @@ bool getCheckCompatibilityOnStartup() {
return checkCompatibilityOnStartup;
}
std::string getAudioBackend() {
return audioBackend;
}
void setGpuId(s32 selectedGpuId) {
gpuId = selectedGpuId;
}
@ -385,10 +380,6 @@ 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;
@ -629,12 +620,6 @@ void load(const std::filesystem::path& path) {
vkCrashDiagnostic = toml::find_or<bool>(vk, "crashDiagnostic", false);
}
if (data.contains("Audio")) {
const toml::value& audio = data.at("Audio");
audioBackend = toml::find_or<std::string>(audio, "backend", "cubeb");
}
if (data.contains("Debug")) {
const toml::value& debug = data.at("Debug");
@ -738,7 +723,6 @@ 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;
@ -844,7 +828,6 @@ void setDefaultValues() {
separateupdatefolder = false;
compatibilityData = false;
checkCompatibilityOnStartup = false;
audioBackend = "cubeb";
}
} // namespace Config

View File

@ -27,7 +27,6 @@ bool getEnableDiscordRPC();
bool getSeparateUpdateEnabled();
bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup();
std::string getAudioBackend();
std::string getLogFilter();
std::string getLogType();
@ -79,7 +78,6 @@ void setSeparateUpdateEnabled(bool use);
void setGameInstallDirs(const std::vector<std::filesystem::path>& 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);

View File

@ -34,6 +34,7 @@ public:
static constexpr u32 FW_20 = 0x2000000;
static constexpr u32 FW_25 = 0x2500000;
static constexpr u32 FW_30 = 0x3000000;
static constexpr u32 FW_35 = 0x3500000;
static constexpr u32 FW_40 = 0x4000000;
static constexpr u32 FW_45 = 0x4500000;
static constexpr u32 FW_50 = 0x5000000;

View File

@ -97,6 +97,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, Http) \
SUB(Lib, Ssl) \
SUB(Lib, SysModule) \
SUB(Lib, Move) \
SUB(Lib, NpManager) \
SUB(Lib, NpScore) \
SUB(Lib, NpTrophy) \

View File

@ -57,8 +57,9 @@ enum class Class : u8 {
Lib_MsgDlg, ///< The LibSceMsgDialog implementation.
Lib_AudioOut, ///< The LibSceAudioOut implementation.
Lib_AudioIn, ///< The LibSceAudioIn implementation.
Lib_Move, ///< The LibSceMove implementation.
Lib_Net, ///< The LibSceNet implementation.
Lib_NetCtl, ///< The LibSecNetCtl implementation.
Lib_NetCtl, ///< The LibSceNetCtl implementation.
Lib_SaveData, ///< The LibSceSaveData implementation.
Lib_SaveDataDialog, ///< The LibSceSaveDataDialog implementation.
Lib_Ssl, ///< The LibSceSsl implementation.

View File

@ -4,11 +4,6 @@
#include "common/native_clock.h"
#include "common/rdtsc.h"
#include "common/uint128.h"
#ifdef _WIN64
#include <pthread_time.h>
#else
#include <time.h>
#endif
namespace Common {
@ -34,10 +29,4 @@ u64 NativeClock::GetUptime() const {
return FencedRDTSC();
}
u64 NativeClock::GetProcessTimeUS() const {
timespec ret;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ret);
return ret.tv_nsec / 1000 + ret.tv_sec * 1000000;
}
} // namespace Common

View File

@ -20,7 +20,6 @@ public:
u64 GetTimeUS(u64 base_ptc = 0) const;
u64 GetTimeMS(u64 base_ptc = 0) const;
u64 GetUptime() const;
u64 GetProcessTimeUS() const;
private:
u64 rdtsc_frequency;

View File

@ -1,374 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2016 Mozilla Foundation
// SPDX-License-Identifier: ISC
#pragma once
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <memory>
#include <thread>
#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 <typename T>
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<int>::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 <typename CopyT>
void PodCopy(CopyT* destination, const CopyT* source, size_t count) {
static_assert(std::is_trivial<CopyT>::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 <typename ZeroT>
void PodZero(ZeroT* destination, size_t count) {
static_assert(std::is_trivial<ZeroT>::value, "Requires trivial type");
ASSERT(destination);
memset(destination, 0, count * sizeof(ZeroT));
}
template <typename CopyT, typename Trait>
void Copy(CopyT* destination, const CopyT* source, size_t count, Trait) {
for (size_t i = 0; i < count; i++) {
destination[i] = source[i];
}
}
template <typename CopyT>
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 <typename CopyT>
void Copy(CopyT* destination, const T* source, size_t count) {
ASSERT(destination && source);
Copy(destination, source, count, typename std::is_trivial<CopyT>::type());
}
template <typename ConstructT, typename Trait>
void ConstructDefault(ConstructT* destination, size_t count, Trait) {
for (size_t i = 0; i < count; i++) {
destination[i] = ConstructT();
}
}
template <typename ConstructT>
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 <typename ConstructT>
void ConstructDefault(ConstructT* destination, size_t count) {
ASSERT(destination);
ConstructDefault(destination, count, typename std::is_arithmetic<ConstructT>::type());
}
/** Index at which the oldest element is at, in samples. */
std::atomic<int> read_index_;
/** Index at which to write new elements. `write_index` is always at
* least one element ahead of `read_index_`. */
std::atomic<int> write_index_;
/** Maximum number of elements that can be stored in the ring buffer. */
const int capacity_;
/** Data storage */
std::unique_ptr<T[]> 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
};

View File

@ -26,7 +26,7 @@ asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000");
namespace Core {
static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE_PRO;
static constexpr size_t BackingSize = SCE_KERNEL_TOTAL_MEM_PRO;
#ifdef _WIN32

View File

@ -183,13 +183,15 @@ int PS4_SYSV_ABI sceAjmInstanceSwitch() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAjmMemoryRegister() {
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
int PS4_SYSV_ABI sceAjmMemoryRegister(u32 context_id, void* ptr, size_t num_pages) {
// All memory is already shared with our implementation since we do not use any hardware.
LOG_TRACE(Lib_Ajm, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAjmMemoryUnregister() {
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
int PS4_SYSV_ABI sceAjmMemoryUnregister(u32 context_id, void* ptr) {
// All memory is already shared with our implementation since we do not use any hardware.
LOG_TRACE(Lib_Ajm, "(STUBBED) called");
return ORBIS_OK;
}

View File

@ -74,6 +74,26 @@ union AjmJobFlags {
};
};
enum class AjmStatisticsFlags : u64 {
Memory = 1 << 0,
EnginePerCodec = 1 << 15,
Engine = 1 << 16,
};
DECLARE_ENUM_FLAG_OPERATORS(AjmStatisticsFlags)
union AjmStatisticsJobFlags {
AjmStatisticsJobFlags(AjmJobFlags job_flags) : raw(job_flags.raw) {}
u64 raw;
struct {
u64 version : 3;
u64 : 12;
AjmStatisticsFlags statistics_flags : 17;
u64 : 32;
};
};
static_assert(sizeof(AjmStatisticsJobFlags) == 8);
struct AjmSidebandResult {
s32 result;
s32 internal_result;
@ -126,6 +146,31 @@ union AjmSidebandInitParameters {
u8 reserved[8];
};
struct AjmSidebandStatisticsEngine {
float usage_batch;
float usage_interval[3];
};
struct AjmSidebandStatisticsEnginePerCodec {
u8 codec_count;
u8 codec_id[3];
float codec_percentage[3];
};
struct AjmSidebandStatisticsMemory {
u32 instance_free;
u32 buffer_free;
u32 batch_size;
u32 input_size;
u32 output_size;
u32 small_size;
};
struct AjmSidebandStatisticsEngineParameters {
u32 interval_count;
float interval[3];
};
union AjmInstanceFlags {
u64 raw;
struct {
@ -178,8 +223,8 @@ int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmI
int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance);
int PS4_SYSV_ABI sceAjmInstanceExtend();
int PS4_SYSV_ABI sceAjmInstanceSwitch();
int PS4_SYSV_ABI sceAjmMemoryRegister();
int PS4_SYSV_ABI sceAjmMemoryUnregister();
int PS4_SYSV_ABI sceAjmMemoryRegister(u32 context_id, void* ptr, size_t num_pages);
int PS4_SYSV_ABI sceAjmMemoryUnregister(u32 context_id, void* ptr);
int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved);
int PS4_SYSV_ABI sceAjmModuleUnregister();
int PS4_SYSV_ABI sceAjmStrError();

View File

@ -54,6 +54,8 @@ public:
: m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {}
AjmBatchBuffer(std::span<u8> data)
: m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {}
AjmBatchBuffer(AjmChunkBuffer& buffer)
: AjmBatchBuffer(reinterpret_cast<u8*>(buffer.p_address), buffer.size) {}
AjmBatchBuffer SubBuffer(size_t size = s_dynamic_extent) {
auto current = m_p_current;
@ -113,6 +115,88 @@ private:
size_t m_size{};
};
AjmJob AjmStatisticsJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
std::optional<AjmJobFlags> job_flags = {};
std::optional<AjmChunkBuffer> input_control_buffer = {};
std::optional<AjmChunkBuffer> output_control_buffer = {};
AjmJob job;
job.instance_id = instance_id;
while (!batch_buffer.IsEmpty()) {
auto& header = batch_buffer.Peek<AjmChunkHeader>();
switch (header.ident) {
case Identifier::AjmIdentInputControlBuf: {
ASSERT_MSG(!input_control_buffer.has_value(),
"Only one instance of input control buffer is allowed per job");
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
input_control_buffer = buffer;
}
break;
}
case Identifier::AjmIdentControlFlags: {
ASSERT_MSG(!job_flags.has_value(), "Only one instance of job flags is allowed per job");
auto& chunk = batch_buffer.Consume<AjmChunkFlags>();
job_flags = AjmJobFlags{
.raw = (u64(chunk.header.payload) << 32) + chunk.flags_low,
};
break;
}
case Identifier::AjmIdentReturnAddressBuf: {
// Ignore return address buffers.
batch_buffer.Skip<AjmChunkBuffer>();
break;
}
case Identifier::AjmIdentOutputControlBuf: {
ASSERT_MSG(!output_control_buffer.has_value(),
"Only one instance of output control buffer is allowed per job");
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
output_control_buffer = buffer;
}
break;
}
default:
UNREACHABLE_MSG("Unknown chunk: {}", header.ident);
}
}
ASSERT(job_flags.has_value());
job.flags = job_flags.value();
AjmStatisticsJobFlags flags(job.flags);
if (input_control_buffer.has_value()) {
AjmBatchBuffer input_batch(input_control_buffer.value());
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
job.input.statistics_engine_parameters =
input_batch.Consume<AjmSidebandStatisticsEngineParameters>();
}
}
if (output_control_buffer.has_value()) {
AjmBatchBuffer output_batch(output_control_buffer.value());
job.output.p_result = &output_batch.Consume<AjmSidebandResult>();
*job.output.p_result = AjmSidebandResult{};
if (True(flags.statistics_flags & AjmStatisticsFlags::Engine)) {
job.output.p_engine = &output_batch.Consume<AjmSidebandStatisticsEngine>();
*job.output.p_engine = AjmSidebandStatisticsEngine{};
}
if (True(flags.statistics_flags & AjmStatisticsFlags::EnginePerCodec)) {
job.output.p_engine_per_codec =
&output_batch.Consume<AjmSidebandStatisticsEnginePerCodec>();
*job.output.p_engine_per_codec = AjmSidebandStatisticsEnginePerCodec{};
}
if (True(flags.statistics_flags & AjmStatisticsFlags::Memory)) {
job.output.p_memory = &output_batch.Consume<AjmSidebandStatisticsMemory>();
*job.output.p_memory = AjmSidebandStatisticsMemory{};
}
}
return job;
}
AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
std::optional<AjmJobFlags> job_flags = {};
std::optional<AjmChunkBuffer> input_control_buffer = {};
@ -155,15 +239,6 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
batch_buffer.Skip<AjmChunkBuffer>();
break;
}
case Identifier::AjmIdentInlineBuf: {
ASSERT_MSG(!output_control_buffer.has_value(),
"Only one instance of inline buffer is allowed per job");
const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
inline_buffer = buffer;
}
break;
}
case Identifier::AjmIdentOutputRunBuf: {
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
@ -186,13 +261,12 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
}
}
ASSERT(job_flags.has_value());
job.flags = job_flags.value();
// Initialize sideband input parameters
if (input_control_buffer.has_value()) {
AjmBatchBuffer input_batch(reinterpret_cast<u8*>(input_control_buffer->p_address),
input_control_buffer->size);
AjmBatchBuffer input_batch(input_control_buffer.value());
const auto sideband_flags = job_flags->sideband_flags;
if (True(sideband_flags & AjmJobSidebandFlags::Format) && !input_batch.IsEmpty()) {
job.input.format = input_batch.Consume<AjmSidebandFormat>();
@ -202,6 +276,9 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
}
const auto control_flags = job_flags.value().control_flags;
if (True(control_flags & AjmJobControlFlags::Resample)) {
job.input.resample_parameters = input_batch.Consume<AjmSidebandResampleParameters>();
}
if (True(control_flags & AjmJobControlFlags::Initialize)) {
job.input.init_params = AjmDecAt9InitializeParameters{};
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
@ -209,21 +286,9 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
}
}
if (inline_buffer.has_value()) {
AjmBatchBuffer inline_batch(reinterpret_cast<u8*>(inline_buffer->p_address),
inline_buffer->size);
const auto control_flags = job_flags.value().control_flags;
if (True(control_flags & AjmJobControlFlags::Resample)) {
job.input.resample_parameters = inline_batch.Consume<AjmSidebandResampleParameters>();
}
}
// Initialize sideband output parameters
if (output_control_buffer.has_value()) {
AjmBatchBuffer output_batch(reinterpret_cast<u8*>(output_control_buffer->p_address),
output_control_buffer->size);
AjmBatchBuffer output_batch(output_control_buffer.value());
job.output.p_result = &output_batch.Consume<AjmSidebandResult>();
*job.output.p_result = AjmSidebandResult{};
@ -260,9 +325,21 @@ std::shared_ptr<AjmBatch> AjmBatch::FromBatchBuffer(std::span<u8> data) {
AjmBatchBuffer buffer(data);
while (!buffer.IsEmpty()) {
auto& job_chunk = buffer.Consume<AjmChunkJob>();
if (job_chunk.header.ident == AjmIdentInlineBuf) {
// Inline buffers are used to store sideband input data.
// We should just skip them as they do not require any special handling.
buffer.Advance(job_chunk.size);
continue;
}
ASSERT(job_chunk.header.ident == AjmIdentJob);
auto instance_id = job_chunk.header.payload;
batch->jobs.push_back(AjmJobFromBatchBuffer(instance_id, buffer.SubBuffer(job_chunk.size)));
if (instance_id == AJM_INSTANCE_STATISTICS) {
batch->jobs.push_back(
AjmStatisticsJobFromBatchBuffer(instance_id, buffer.SubBuffer(job_chunk.size)));
} else {
batch->jobs.push_back(
AjmJobFromBatchBuffer(instance_id, buffer.SubBuffer(job_chunk.size)));
}
}
return batch;

View File

@ -12,6 +12,7 @@
#include <atomic>
#include <limits>
#include <memory>
#include <optional>
#include <semaphore>
#include <span>
#include <vector>
@ -22,6 +23,7 @@ struct AjmJob {
struct Input {
std::optional<AjmDecAt9InitializeParameters> init_params;
std::optional<AjmSidebandResampleParameters> resample_parameters;
std::optional<AjmSidebandStatisticsEngineParameters> statistics_engine_parameters;
std::optional<AjmSidebandFormat> format;
std::optional<AjmSidebandGaplessDecode> gapless_decode;
std::vector<u8> buffer;
@ -32,6 +34,9 @@ struct AjmJob {
AjmSidebandResult* p_result = nullptr;
AjmSidebandStream* p_stream = nullptr;
AjmSidebandFormat* p_format = nullptr;
AjmSidebandStatisticsMemory* p_memory = nullptr;
AjmSidebandStatisticsEnginePerCodec* p_engine_per_codec = nullptr;
AjmSidebandStatisticsEngine* p_engine = nullptr;
AjmSidebandGaplessDecode* p_gapless_decode = nullptr;
AjmSidebandMFrame* p_mframe = nullptr;
u8* p_codec_info = nullptr;

View File

@ -9,6 +9,7 @@
#include "core/libraries/ajm/ajm_context.h"
#include "core/libraries/ajm/ajm_error.h"
#include "core/libraries/ajm/ajm_instance.h"
#include "core/libraries/ajm/ajm_instance_statistics.h"
#include "core/libraries/ajm/ajm_mp3.h"
#include "core/libraries/error_codes.h"
@ -70,15 +71,19 @@ void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
LOG_TRACE(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id,
job.flags.raw);
std::shared_ptr<AjmInstance> instance;
{
std::shared_lock lock(instances_mutex);
auto* p_instance = instances.Get(job.instance_id);
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
instance = *p_instance;
}
if (job.instance_id == AJM_INSTANCE_STATISTICS) {
AjmInstanceStatistics::Getinstance().ExecuteJob(job);
} else {
std::shared_ptr<AjmInstance> instance;
{
std::shared_lock lock(instances_mutex);
auto* p_instance = instances.Get(job.instance_id);
ASSERT_MSG(p_instance != nullptr, "Attempting to execute job on null instance");
instance = *p_instance;
}
instance->ExecuteJob(job);
instance->ExecuteJob(job);
}
}
}

View File

@ -68,11 +68,11 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_codec->Initialize(&params, sizeof(params));
}
if (job.input.resample_parameters.has_value()) {
UNREACHABLE_MSG("Unimplemented: resample parameters");
LOG_ERROR(Lib_Ajm, "Unimplemented: resample parameters");
m_resample_parameters = job.input.resample_parameters.value();
}
if (job.input.format.has_value()) {
UNREACHABLE_MSG("Unimplemented: format parameters");
LOG_ERROR(Lib_Ajm, "Unimplemented: format parameters");
m_format = job.input.format.value();
}
if (job.input.gapless_decode.has_value()) {

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/ajm/ajm.h"
#include "core/libraries/ajm/ajm_instance_statistics.h"
namespace Libraries::Ajm {
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
if (job.output.p_engine) {
job.output.p_engine->usage_batch = 0.01;
const auto ic = job.input.statistics_engine_parameters->interval_count;
for (u32 idx = 0; idx < ic; ++idx) {
job.output.p_engine->usage_interval[idx] = 0.01;
}
}
if (job.output.p_engine_per_codec) {
job.output.p_engine_per_codec->codec_count = 1;
job.output.p_engine_per_codec->codec_id[0] = static_cast<u8>(AjmCodecType::At9Dec);
job.output.p_engine_per_codec->codec_percentage[0] = 0.01;
}
if (job.output.p_memory) {
job.output.p_memory->instance_free = 0x400000;
job.output.p_memory->buffer_free = 0x400000;
job.output.p_memory->batch_size = 0x4200;
job.output.p_memory->input_size = 0x2000;
job.output.p_memory->output_size = 0x2000;
job.output.p_memory->small_size = 0x200;
}
}
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
static AjmInstanceStatistics instance;
return instance;
}
} // namespace Libraries::Ajm

View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/ajm/ajm_batch.h"
namespace Libraries::Ajm {
class AjmInstanceStatistics {
public:
void ExecuteJob(AjmJob& job);
static AjmInstanceStatistics& Getinstance();
};
} // namespace Libraries::Ajm

View File

@ -9,6 +9,8 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/libraries/audio/audioout.h"
#include "core/libraries/audio/audioout_backend.h"
#include "core/libraries/audio/audioout_error.h"
@ -21,111 +23,28 @@ std::array<PortOut, SCE_AUDIO_OUT_NUM_PORTS> ports_out{};
static std::unique_ptr<AudioOutBackend> audio;
static std::string_view GetAudioOutPort(OrbisAudioOutPort port) {
switch (port) {
case OrbisAudioOutPort::Main:
return "MAIN";
case OrbisAudioOutPort::Bgm:
return "BGM";
case OrbisAudioOutPort::Voice:
return "VOICE";
case OrbisAudioOutPort::Personal:
return "PERSONAL";
case OrbisAudioOutPort::Padspk:
return "PADSPK";
case OrbisAudioOutPort::Aux:
return "AUX";
default:
return "INVALID";
}
}
static std::string_view GetAudioOutParamFormat(OrbisAudioOutParamFormat param) {
switch (param) {
case OrbisAudioOutParamFormat::S16Mono:
return "S16_MONO";
case OrbisAudioOutParamFormat::S16Stereo:
return "S16_STEREO";
case OrbisAudioOutParamFormat::S16_8CH:
return "S16_8CH";
case OrbisAudioOutParamFormat::FloatMono:
return "FLOAT_MONO";
case OrbisAudioOutParamFormat::FloatStereo:
return "FLOAT_STEREO";
case OrbisAudioOutParamFormat::Float_8CH:
return "FLOAT_8CH";
case OrbisAudioOutParamFormat::S16_8CH_Std:
return "S16_8CH_STD";
case OrbisAudioOutParamFormat::Float_8CH_Std:
return "FLOAT_8CH_STD";
default:
return "INVALID";
}
}
static std::string_view GetAudioOutParamAttr(OrbisAudioOutParamAttr attr) {
switch (attr) {
case OrbisAudioOutParamAttr::None:
return "NONE";
case OrbisAudioOutParamAttr::Restricted:
return "RESTRICTED";
case OrbisAudioOutParamAttr::MixToMain:
return "MIX_TO_MAIN";
default:
return "INVALID";
}
}
static bool IsFormatFloat(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
return false;
case OrbisAudioOutParamFormat::FloatMono:
case OrbisAudioOutParamFormat::FloatStereo:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return true;
default:
UNREACHABLE_MSG("Unknown format");
}
}
static u8 GetFormatNumChannels(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::FloatMono:
return 1;
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::FloatStereo:
return 2;
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return 8;
default:
UNREACHABLE_MSG("Unknown format");
}
}
static u8 GetFormatSampleSize(const OrbisAudioOutParamFormat format) {
switch (format) {
case OrbisAudioOutParamFormat::S16Mono:
case OrbisAudioOutParamFormat::S16Stereo:
case OrbisAudioOutParamFormat::S16_8CH:
case OrbisAudioOutParamFormat::S16_8CH_Std:
return 2;
case OrbisAudioOutParamFormat::FloatMono:
case OrbisAudioOutParamFormat::FloatStereo:
case OrbisAudioOutParamFormat::Float_8CH:
case OrbisAudioOutParamFormat::Float_8CH_Std:
return 4;
default:
UNREACHABLE_MSG("Unknown format");
}
static AudioFormatInfo GetFormatInfo(const OrbisAudioOutParamFormat format) {
static constexpr std::array<AudioFormatInfo, 8> format_infos = {{
// S16Mono
{false, 2, 1, {0}},
// S16Stereo
{false, 2, 2, {0, 1}},
// S16_8CH
{false, 2, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
// FloatMono
{true, 4, 1, {0}},
// FloatStereo
{true, 4, 2, {0, 1}},
// Float_8CH
{true, 4, 8, {0, 1, 2, 3, 4, 5, 6, 7}},
// S16_8CH_Std
{false, 2, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
// Float_8CH_Std
{true, 4, 8, {0, 1, 2, 3, 6, 7, 4, 5}},
}};
const auto index = static_cast<u32>(format);
ASSERT_MSG(index < format_infos.size(), "Unknown audio format {}", index);
return format_infos[index];
}
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen() {
@ -180,6 +99,10 @@ int PS4_SYSV_ABI sceAudioOutClose(s32 handle) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
port.output_thread.Stop();
std::free(port.output_buffer);
port.output_buffer = nullptr;
port.output_ready = false;
port.impl = nullptr;
return ORBIS_OK;
}
@ -263,7 +186,7 @@ int PS4_SYSV_ABI sceAudioOutGetPortState(s32 handle, OrbisAudioOutPortState* sta
case OrbisAudioOutPort::Bgm:
case OrbisAudioOutPort::Voice:
state->output = 1;
state->channel = port.channels_num > 2 ? 2 : port.channels_num;
state->channel = port.format_info.num_channels > 2 ? 2 : port.format_info.num_channels;
break;
case OrbisAudioOutPort::Personal:
case OrbisAudioOutPort::Padspk:
@ -311,16 +234,7 @@ int PS4_SYSV_ABI sceAudioOutInit() {
if (audio != nullptr) {
return ORBIS_AUDIO_OUT_ERROR_ALREADY_INIT;
}
const auto backend = Config::getAudioBackend();
if (backend == "cubeb") {
audio = std::make_unique<CubebAudioOut>();
} else if (backend == "sdl") {
audio = std::make_unique<SDLAudioOut>();
} else {
// Cubeb as a default fallback.
LOG_ERROR(Lib_AudioOut, "Invalid audio backend '{}', defaulting to cubeb.", backend);
audio = std::make_unique<CubebAudioOut>();
}
audio = std::make_unique<SDLAudioOut>();
return ORBIS_OK;
}
@ -354,6 +268,30 @@ int PS4_SYSV_ABI sceAudioOutMbusInit() {
return ORBIS_OK;
}
static void AudioOutputThread(PortOut* port, const std::stop_token& stop) {
{
const auto thread_name = fmt::format("shadPS4:AudioOutputThread:{}", fmt::ptr(port));
Common::SetCurrentThreadName(thread_name.c_str());
}
Common::AccurateTimer timer(
std::chrono::nanoseconds(1000000000ULL * port->buffer_frames / port->sample_rate));
while (true) {
timer.Start();
{
std::unique_lock lock{port->output_mutex};
Common::CondvarWait(port->output_cv, lock, stop, [&] { return port->output_ready; });
if (stop.stop_requested()) {
break;
}
port->impl->Output(port->output_buffer);
port->output_ready = false;
}
port->output_cv.notify_one();
timer.End();
}
}
s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
OrbisAudioOutPort port_type, s32 index, u32 length,
u32 sample_rate,
@ -361,9 +299,9 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
LOG_INFO(Lib_AudioOut,
"id = {} port_type = {} index = {} length = {} sample_rate = {} "
"param_type = {} attr = {}",
user_id, GetAudioOutPort(port_type), index, length, sample_rate,
GetAudioOutParamFormat(param_type.data_format),
GetAudioOutParamAttr(param_type.attributes));
user_id, magic_enum::enum_name(port_type), index, length, sample_rate,
magic_enum::enum_name(param_type.data_format.Value()),
magic_enum::enum_name(param_type.attributes.Value()));
if ((port_type < OrbisAudioOutPort::Main || port_type > OrbisAudioOutPort::Padspk) &&
(port_type != OrbisAudioOutPort::Aux)) {
LOG_ERROR(Lib_AudioOut, "Invalid port type");
@ -403,17 +341,18 @@ s32 PS4_SYSV_ABI sceAudioOutOpen(UserService::OrbisUserServiceUserId user_id,
}
port->type = port_type;
port->format = format;
port->is_float = IsFormatFloat(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->format_info = GetFormatInfo(format);
port->sample_rate = sample_rate;
port->buffer_frames = length;
port->volume.fill(SCE_AUDIO_OUT_VOLUME_0DB);
port->impl = audio->Open(*port);
port->output_buffer = std::malloc(port->BufferSize());
port->output_ready = false;
port->output_thread.Run(
[port](const std::stop_token& stop) { AudioOutputThread(&*port, stop); });
return std::distance(ports_out.begin(), port) + 1;
}
@ -426,24 +365,30 @@ 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;
}
if (ptr == nullptr) {
// Nothing to output
return ORBIS_OK;
}
auto& port = ports_out.at(handle - 1);
if (!port.impl) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
port.impl->Output(ptr, port.buffer_size);
{
std::unique_lock lock{port.output_mutex};
port.output_cv.wait(lock, [&] { return !port.output_ready; });
if (ptr != nullptr) {
std::memcpy(port.output_buffer, ptr, port.BufferSize());
port.output_ready = true;
}
}
port.output_cv.notify_one();
return ORBIS_OK;
}
int PS4_SYSV_ABI sceAudioOutOutputs(OrbisAudioOutOutputParam* param, u32 num) {
for (u32 i = 0; i < num; i++) {
if (const auto err = sceAudioOutOutput(param[i].handle, param[i].ptr); err != 0)
return err;
const auto [handle, ptr] = param[i];
if (const auto ret = sceAudioOutOutput(handle, ptr); ret != ORBIS_OK) {
return ret;
}
}
return ORBIS_OK;
}
@ -549,30 +494,9 @@ s32 PS4_SYSV_ABI sceAudioOutSetVolume(s32 handle, s32 flag, s32* vol) {
return ORBIS_AUDIO_OUT_ERROR_INVALID_PORT;
}
for (int i = 0; i < port.channels_num; i++, flag >>= 1u) {
auto bit = flag & 0x1u;
if (bit == 1) {
int src_index = i;
if (port.format == OrbisAudioOutParamFormat::Float_8CH_Std ||
port.format == OrbisAudioOutParamFormat::S16_8CH_Std) {
switch (i) {
case 4:
src_index = 6;
break;
case 5:
src_index = 7;
break;
case 6:
src_index = 4;
break;
case 7:
src_index = 5;
break;
default:
break;
}
}
port.volume[i] = vol[src_index];
for (int i = 0; i < port.format_info.num_channels; i++, flag >>= 1u) {
if (flag & 0x1u) {
port.volume[i] = vol[i];
}
}

View File

@ -6,6 +6,7 @@
#include <memory>
#include "common/bit_field.h"
#include "core/libraries/kernel/threads.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::AudioOut {
@ -14,12 +15,12 @@ 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;
constexpr int SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
constexpr s32 SCE_AUDIO_OUT_NUM_PORTS = 22;
constexpr s32 SCE_AUDIO_OUT_VOLUME_0DB = 32768; // max volume value
enum class OrbisAudioOutPort { Main = 0, Bgm = 1, Voice = 2, Personal = 3, Padspk = 4, Aux = 127 };
enum class OrbisAudioOutParamFormat {
enum class OrbisAudioOutParamFormat : u32 {
S16Mono = 0,
S16Stereo = 1,
S16_8CH = 2,
@ -30,7 +31,7 @@ enum class OrbisAudioOutParamFormat {
Float_8CH_Std = 7
};
enum class OrbisAudioOutParamAttr {
enum class OrbisAudioOutParamAttr : u32 {
None = 0,
Restricted = 1,
MixToMain = 2,
@ -59,19 +60,37 @@ struct OrbisAudioOutPortState {
u64 reserved64[2];
};
struct AudioFormatInfo {
bool is_float;
u8 sample_size;
u8 num_channels;
/// Layout array remapping channel indices, specified in this order:
/// FL, FR, FC, LFE, BL, BR, SL, SR
std::array<int, 8> channel_layout;
[[nodiscard]] u16 FrameSize() const {
return sample_size * num_channels;
}
};
struct PortOut {
std::unique_ptr<PortBackend> impl{};
void* output_buffer;
std::mutex output_mutex;
std::condition_variable_any output_cv;
bool output_ready;
Kernel::Thread output_thread{};
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<int, 8> volume;
AudioFormatInfo format_info;
u32 sample_rate;
u32 buffer_frames;
std::array<s32, 8> volume;
[[nodiscard]] u32 BufferSize() const {
return buffer_frames * format_info.FrameSize();
}
};
int PS4_SYSV_ABI sceAudioOutDeviceIdOpen();

View File

@ -3,8 +3,6 @@
#pragma once
typedef struct cubeb cubeb;
namespace Libraries::AudioOut {
struct PortOut;
@ -13,7 +11,10 @@ class PortBackend {
public:
virtual ~PortBackend() = default;
virtual void Output(void* ptr, size_t size) = 0;
/// Guaranteed to be called in intervals of at least port buffer time,
/// with size equal to port buffer size.
virtual void Output(void* ptr) = 0;
virtual void SetVolume(const std::array<int, 8>& ch_volumes) = 0;
};
@ -25,17 +26,6 @@ public:
virtual std::unique_ptr<PortBackend> Open(PortOut& port) = 0;
};
class CubebAudioOut final : public AudioOutBackend {
public:
CubebAudioOut();
~CubebAudioOut() override;
std::unique_ptr<PortBackend> Open(PortOut& port) override;
private:
cubeb* ctx = nullptr;
};
class SDLAudioOut final : public AudioOutBackend {
public:
std::unique_ptr<PortBackend> Open(PortOut& port) override;

View File

@ -1,158 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <condition_variable>
#include <mutex>
#include <cubeb/cubeb.h>
#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<int>(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<u8*>(ptr);
std::unique_lock lock{buffer_mutex};
buffer_cv.wait(lock, [&] { return buffer.available_write() >= size; });
buffer.enqueue(data, static_cast<int>(size));
}
void SetVolume(const std::array<int, 8>& 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<float>(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<CubebPortBackend*>(user_data);
const auto out_data = static_cast<u8*>(out);
const auto requested_size = static_cast<int>(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<u8> 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<PortBackend> CubebAudioOut::Open(PortOut& port) {
return std::make_unique<CubebPortBackend>(ctx, port);
}
} // namespace Libraries::AudioOut

View File

@ -2,9 +2,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_hints.h>
#include "common/logging/log.h"
#include "core/libraries/audio/audioout.h"
@ -12,44 +11,101 @@
namespace Libraries::AudioOut {
constexpr int AUDIO_STREAM_BUFFER_THRESHOLD = 65536; // Define constant for buffer threshold
class SDLPortBackend : public PortBackend {
public:
explicit SDLPortBackend(const PortOut& port) {
explicit SDLPortBackend(const PortOut& port)
: frame_size(port.format_info.FrameSize()), buffer_size(port.BufferSize()) {
// We want the latency for delivering frames out to be as small as possible,
// so set the sample frames hint to the number of frames per buffer.
const auto samples_num_str = std::to_string(port.buffer_frames);
if (!SDL_SetHint(SDL_HINT_AUDIO_DEVICE_SAMPLE_FRAMES, samples_num_str.c_str())) {
LOG_WARNING(Lib_AudioOut, "Failed to set SDL audio sample frames hint to {}: {}",
samples_num_str, SDL_GetError());
}
const SDL_AudioSpec fmt = {
.format = port.is_float ? SDL_AUDIO_F32 : SDL_AUDIO_S16,
.channels = port.channels_num,
.freq = static_cast<int>(port.freq),
.format = port.format_info.is_float ? SDL_AUDIO_F32LE : SDL_AUDIO_S16LE,
.channels = port.format_info.num_channels,
.freq = static_cast<int>(port.sample_rate),
};
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());
return;
}
queue_threshold = CalculateQueueThreshold();
if (!SDL_SetAudioStreamInputChannelMap(stream, port.format_info.channel_layout.data(),
port.format_info.num_channels)) {
LOG_ERROR(Lib_AudioOut, "Failed to configure SDL audio stream channel map: {}",
SDL_GetError());
SDL_DestroyAudioStream(stream);
stream = nullptr;
return;
}
if (!SDL_ResumeAudioStreamDevice(stream)) {
LOG_ERROR(Lib_AudioOut, "Failed to resume SDL audio stream: {}", SDL_GetError());
SDL_DestroyAudioStream(stream);
stream = nullptr;
return;
}
SDL_ResumeAudioStreamDevice(stream);
}
~SDLPortBackend() override {
if (stream) {
SDL_DestroyAudioStream(stream);
stream = nullptr;
if (!stream) {
return;
}
SDL_DestroyAudioStream(stream);
stream = nullptr;
}
void Output(void* ptr, size_t size) override {
SDL_PutAudioStreamData(stream, ptr, static_cast<int>(size));
while (SDL_GetAudioStreamAvailable(stream) > AUDIO_STREAM_BUFFER_THRESHOLD) {
// Yield to allow the stream to drain.
std::this_thread::yield();
void Output(void* ptr) override {
if (!stream) {
return;
}
// AudioOut library manages timing, but we still need to guard against the SDL
// audio queue stalling, which may happen during device changes, for example.
// Otherwise, latency may grow over time unbounded.
if (const auto queued = SDL_GetAudioStreamQueued(stream); queued >= queue_threshold) {
LOG_WARNING(Lib_AudioOut,
"SDL audio queue backed up ({} queued, {} threshold), clearing.", queued,
queue_threshold);
SDL_ClearAudioStream(stream);
// Recalculate the threshold in case this happened because of a device change.
queue_threshold = CalculateQueueThreshold();
}
if (!SDL_PutAudioStreamData(stream, ptr, static_cast<int>(buffer_size))) {
LOG_ERROR(Lib_AudioOut, "Failed to output to SDL audio stream: {}", SDL_GetError());
}
}
void SetVolume(const std::array<int, 8>& ch_volumes) override {
// TODO: Not yet implemented
if (!stream) {
return;
}
// SDL 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 (!SDL_SetAudioStreamGain(stream, static_cast<float>(vol) / SCE_AUDIO_OUT_VOLUME_0DB)) {
LOG_WARNING(Lib_AudioOut, "Failed to change SDL audio stream volume: {}",
SDL_GetError());
}
}
private:
[[nodiscard]] u32 CalculateQueueThreshold() const {
SDL_AudioSpec discard;
int sdl_buffer_frames;
if (!SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(stream), &discard,
&sdl_buffer_frames)) {
LOG_WARNING(Lib_AudioOut, "Failed to get SDL audio stream buffer size: {}",
SDL_GetError());
sdl_buffer_frames = 0;
}
return std::max<u32>(buffer_size, sdl_buffer_frames * frame_size) * 4;
}
u32 frame_size;
u32 buffer_size;
u32 queue_threshold;
SDL_AudioStream* stream;
};

View File

@ -3,6 +3,7 @@
#include "fiber.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "core/libraries/fiber/fiber_error.h"
#include "core/libraries/libs.h"
@ -41,6 +42,41 @@ void PS4_SYSV_ABI _sceFiberCheckStackOverflow(OrbisFiberContext* ctx) {
}
}
s32 PS4_SYSV_ABI _sceFiberAttachContext(OrbisFiber* fiber, void* addr_context, u64 size_context) {
if (size_context && size_context < ORBIS_FIBER_CONTEXT_MINIMUM_SIZE) {
return ORBIS_FIBER_ERROR_RANGE;
}
if (size_context & 15) {
return ORBIS_FIBER_ERROR_INVALID;
}
if (!addr_context || !size_context) {
return ORBIS_FIBER_ERROR_INVALID;
}
if (fiber->addr_context) {
return ORBIS_FIBER_ERROR_INVALID;
}
fiber->addr_context = addr_context;
fiber->size_context = size_context;
fiber->context_start = addr_context;
fiber->context_end = reinterpret_cast<u8*>(addr_context) + size_context;
/* Apply signature to start of stack */
*(u64*)addr_context = kFiberStackSignature;
if (fiber->flags & FiberFlags::ContextSizeCheck) {
u64* stack_start = reinterpret_cast<u64*>(fiber->context_start);
u64* stack_end = reinterpret_cast<u64*>(fiber->context_end);
u64* stack_ptr = stack_start + 1;
while (stack_ptr < stack_end) {
*stack_ptr++ = kFiberStackSizeCheck;
}
}
return ORBIS_OK;
}
void PS4_SYSV_ABI _sceFiberSwitchToFiber(OrbisFiber* fiber, u64 arg_on_run_to,
OrbisFiberContext* ctx) {
OrbisFiberContext* fiber_ctx = fiber->context;
@ -62,8 +98,7 @@ void PS4_SYSV_ABI _sceFiberSwitchToFiber(OrbisFiber* fiber, u64 arg_on_run_to,
data.entry = fiber->entry;
data.arg_on_initialize = fiber->arg_on_initialize;
data.arg_on_run_to = arg_on_run_to;
data.stack_addr =
reinterpret_cast<void*>(reinterpret_cast<u64>(fiber->addr_context) + fiber->size_context);
data.stack_addr = reinterpret_cast<u8*>(fiber->addr_context) + fiber->size_context;
if (fiber->flags & FiberFlags::SetFpuRegs) {
data.fpucw = 0x037f;
data.mxcsr = 0x9fc0;
@ -111,9 +146,10 @@ void PS4_SYSV_ABI _sceFiberTerminate(OrbisFiber* fiber, u64 arg_on_return, Orbis
__builtin_trap();
}
s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry,
u64 arg_on_initialize, void* addr_context, u64 size_context,
const OrbisFiberOptParam* opt_param, u32 build_ver) {
s32 PS4_SYSV_ABI sceFiberInitializeImpl(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry,
u64 arg_on_initialize, void* addr_context, u64 size_context,
const OrbisFiberOptParam* opt_param, u32 flags,
u32 build_ver) {
if (!fiber || !name || !entry) {
return ORBIS_FIBER_ERROR_NULL;
}
@ -139,12 +175,12 @@ s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFi
return ORBIS_FIBER_ERROR_INVALID;
}
u32 flags = FiberFlags::None;
if (build_ver >= 0x3500000) {
flags |= FiberFlags::SetFpuRegs;
u32 user_flags = flags;
if (build_ver >= Common::ElfInfo::FW_35) {
user_flags |= FiberFlags::SetFpuRegs;
}
if (context_size_check) {
flags |= FiberFlags::ContextSizeCheck;
user_flags |= FiberFlags::ContextSizeCheck;
}
strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH);
@ -154,7 +190,7 @@ s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFi
fiber->addr_context = addr_context;
fiber->size_context = size_context;
fiber->context = nullptr;
fiber->flags = flags;
fiber->flags = user_flags;
/*
A low stack area is problematic, as we can easily
@ -169,8 +205,7 @@ s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFi
if (addr_context != nullptr) {
fiber->context_start = addr_context;
fiber->context_end =
reinterpret_cast<void*>(reinterpret_cast<u64>(addr_context) + size_context);
fiber->context_end = reinterpret_cast<u8*>(addr_context) + size_context;
/* Apply signature to start of stack */
*(u64*)addr_context = kFiberStackSignature;
@ -221,11 +256,12 @@ s32 PS4_SYSV_ABI sceFiberFinalize(OrbisFiber* fiber) {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return) {
s32 PS4_SYSV_ABI sceFiberRunImpl(OrbisFiber* fiber, void* addr_context, u64 size_context,
u64 arg_on_run_to, u64* arg_on_return) {
if (!fiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber & 7) {
if ((u64)fiber & 7 || (u64)addr_context & 15) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) {
@ -237,6 +273,14 @@ s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_r
return ORBIS_FIBER_ERROR_PERMISSION;
}
/* Caller wants to attach context and run. */
if (addr_context != nullptr || size_context != 0) {
s32 res = _sceFiberAttachContext(fiber, addr_context, size_context);
if (res < 0) {
return res;
}
}
FiberState expected = FiberState::Idle;
if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) {
return ORBIS_FIBER_ERROR_STATE;
@ -288,11 +332,12 @@ s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_r
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run) {
s32 PS4_SYSV_ABI sceFiberSwitchImpl(OrbisFiber* fiber, void* addr_context, u64 size_context,
u64 arg_on_run_to, u64* arg_on_run) {
if (!fiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber & 7) {
if ((u64)fiber & 7 || (u64)addr_context & 15) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
if (fiber->magic_start != kFiberSignature0 || fiber->magic_end != kFiberSignature1) {
@ -304,6 +349,14 @@ s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_o
return ORBIS_FIBER_ERROR_PERMISSION;
}
/* Caller wants to attach context and switch. */
if (addr_context != nullptr || size_context != 0) {
s32 res = _sceFiberAttachContext(fiber, addr_context, size_context);
if (res < 0) {
return res;
}
}
FiberState expected = FiberState::Idle;
if (!fiber->state.compare_exchange_strong(expected, FiberState::Run)) {
return ORBIS_FIBER_ERROR_STATE;
@ -462,9 +515,39 @@ s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name) {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberGetThreadFramePointerAddress(u64* addr_frame_pointer) {
if (!addr_frame_pointer) {
return ORBIS_FIBER_ERROR_NULL;
}
OrbisFiberContext* g_ctx = GetFiberContext();
if (!g_ctx) {
return ORBIS_FIBER_ERROR_PERMISSION;
}
*addr_frame_pointer = g_ctx->rbp;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberInitialize(OrbisFiber* fiber, const char* name, OrbisFiberEntry entry,
u64 arg_on_initialize, void* addr_context, u64 size_context,
const OrbisFiberOptParam* opt_param, u32 build_ver) {
return sceFiberInitializeImpl(fiber, name, entry, arg_on_initialize, addr_context, size_context,
opt_param, 0, build_ver);
}
s32 PS4_SYSV_ABI sceFiberRun(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_return) {
return sceFiberRunImpl(fiber, nullptr, 0, arg_on_run_to, arg_on_return);
}
s32 PS4_SYSV_ABI sceFiberSwitch(OrbisFiber* fiber, u64 arg_on_run_to, u64* arg_on_run) {
return sceFiberSwitchImpl(fiber, nullptr, 0, arg_on_run_to, arg_on_run);
}
void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize);
LIB_FUNCTION("7+OJIpko9RY", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize);
LIB_FUNCTION("7+OJIpko9RY", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberInitializeImpl); // _sceFiberInitializeWithInternalOptionImpl
LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize);
LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize);
@ -473,12 +556,20 @@ void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("p+zLIOg27zU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetSelf);
LIB_FUNCTION("B0ZX2hx9DMw", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberReturnToThread);
LIB_FUNCTION("avfGJ94g36Q", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberRunImpl); // _sceFiberAttachContextAndRun
LIB_FUNCTION("ZqhZFuzKT6U", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberSwitchImpl); // _sceFiberAttachContextAndSwitch
LIB_FUNCTION("uq2Y5BFz0PE", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetInfo);
LIB_FUNCTION("Lcqty+QNWFc", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberStartContextSizeCheck);
LIB_FUNCTION("Kj4nXMpnM8Y", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberStopContextSizeCheck);
LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename);
LIB_FUNCTION("0dy4JtMUcMQ", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberGetThreadFramePointerAddress);
}
} // namespace Libraries::Fiber

View File

@ -114,5 +114,7 @@ s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void);
s32 PS4_SYSV_ABI sceFiberRename(OrbisFiber* fiber, const char* name);
s32 PS4_SYSV_ABI sceFiberGetThreadFramePointerAddress(u64* addr_frame_pointer);
void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Fiber

View File

@ -513,10 +513,14 @@ void PS4_SYSV_ABI sceGnmDingDong(u32 gnm_vqid, u32 next_offs_dw) {
auto vqid = gnm_vqid - 1;
auto& asc_queue = liverpool->asc_queues[{vqid}];
const auto& offs_dw = asc_next_offs_dw[vqid];
auto& offs_dw = asc_next_offs_dw[vqid];
if (next_offs_dw < offs_dw) {
ASSERT_MSG(next_offs_dw == 0, "ACB submission is split at the end of ring buffer");
if (next_offs_dw < offs_dw && next_offs_dw != 0) {
// For cases if a submission is split at the end of the ring buffer, we need to submit it in
// two parts to handle the wrap
liverpool->SubmitAsc(gnm_vqid, {reinterpret_cast<const u32*>(asc_queue.map_addr) + offs_dw,
asc_queue.ring_size_dw - offs_dw});
offs_dw = 0;
}
const auto* acb_ptr = reinterpret_cast<const u32*>(asc_queue.map_addr) + offs_dw;
@ -1011,11 +1015,7 @@ int PS4_SYSV_ABI sceGnmGetDebugTimestamp() {
int PS4_SYSV_ABI sceGnmGetEqEventType(const SceKernelEvent* ev) {
LOG_TRACE(Lib_GnmDriver, "called");
auto data = sceKernelGetEventData(ev);
ASSERT(static_cast<GnmEventType>(data) == GnmEventType::GfxEop);
return data;
return sceKernelGetEventData(ev);
}
int PS4_SYSV_ABI sceGnmGetEqTimeStamp() {

View File

@ -283,6 +283,19 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec*
return ORBIS_OK;
}
int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(SceKernelEqueue eq, int id) {
if (eq == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (eq->HasSmallTimer()) {
return eq->RemoveSmallTimer(id) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOENT;
} else {
return eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer) ? ORBIS_OK
: ORBIS_KERNEL_ERROR_ENOENT;
}
}
int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id) {
if (eq == nullptr) {
return ORBIS_KERNEL_ERROR_EBADF;
@ -346,7 +359,7 @@ int PS4_SYSV_ABI sceKernelDeleteUserEvent(SceKernelEqueue eq, int id) {
return ORBIS_OK;
}
s16 PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) {
int PS4_SYSV_ABI sceKernelGetEventFilter(const SceKernelEvent* ev) {
return ev->filter;
}
@ -362,6 +375,7 @@ void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("4R6-OvI2cEA", "libkernel", 1, "libkernel", 1, 1, sceKernelAddUserEvent);
LIB_FUNCTION("WDszmSbWuDk", "libkernel", 1, "libkernel", 1, 1, sceKernelAddUserEventEdge);
LIB_FUNCTION("R74tt43xP6k", "libkernel", 1, "libkernel", 1, 1, sceKernelAddHRTimerEvent);
LIB_FUNCTION("J+LF6LwObXU", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteHRTimerEvent);
LIB_FUNCTION("F6e0kwo4cnk", "libkernel", 1, "libkernel", 1, 1, sceKernelTriggerUserEvent);
LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent);
LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId);

View File

@ -111,6 +111,13 @@ public:
bool HasSmallTimer() const {
return small_timer_event.event.data != 0;
}
bool RemoveSmallTimer(u64 id) {
if (HasSmallTimer() && small_timer_event.event.ident == id) {
small_timer_event = {};
return true;
}
return false;
}
int WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros);

View File

@ -6,9 +6,11 @@
#include "common/bit_field.h"
#include "common/types.h"
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5056_MB; // ~ 5GB
// TODO: Confirm this value on hardware.
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE_PRO = 5568_MB; // ~ 5.5GB
constexpr u64 SCE_KERNEL_TOTAL_MEM = 5248_MB;
constexpr u64 SCE_KERNEL_TOTAL_MEM_PRO = 5888_MB;
constexpr u64 SCE_FLEXIBLE_MEMORY_BASE = 64_MB;
constexpr u64 SCE_FLEXIBLE_MEMORY_SIZE = 512_MB;
namespace Core::Loader {
class SymbolsResolver;
@ -129,10 +131,6 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, size_t len, int flags);
int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len);
void* Malloc(size_t size);
void Free(void* ptr);
void RegisterMemory(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel

View File

@ -244,10 +244,9 @@ 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 128 KB */
static constexpr size_t MinimumStack = 128_KB;
auto& stacksize = new_thread->attr.stacksize_attr;
stacksize = std::max(stacksize, MinimumStack);
/* Add additional stack space for HLE */
static constexpr size_t AdditionalStack = 128_KB;
new_thread->attr.stacksize_attr += AdditionalStack;
}
if (thread_state->CreateStack(&new_thread->attr) != 0) {

View File

@ -18,6 +18,7 @@
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libpng/pngdec.h"
#include "core/libraries/libs.h"
#include "core/libraries/move/move.h"
#include "core/libraries/network/http.h"
#include "core/libraries/network/net.h"
#include "core/libraries/network/netctl.h"
@ -91,6 +92,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Remoteplay::RegisterlibSceRemoteplay(sym);
Libraries::Videodec::RegisterlibSceVideodec(sym);
Libraries::RazorCpu::RegisterlibSceRazorCpu(sym);
Libraries::Move::RegisterlibSceMove(sym);
}
} // namespace Libraries

View File

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "move.h"
namespace Libraries::Move {
int PS4_SYSV_ABI sceMoveOpen() {
LOG_ERROR(Lib_Move, "(STUBBED) called");
return ORBIS_FAIL;
}
int PS4_SYSV_ABI sceMoveGetDeviceInfo() {
LOG_ERROR(Lib_Move, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceMoveReadStateRecent() {
LOG_TRACE(Lib_Move, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceMoveTerm() {
LOG_ERROR(Lib_Move, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceMoveInit() {
LOG_ERROR(Lib_Move, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterlibSceMove(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("HzC60MfjJxU", "libSceMove", 1, "libSceMove", 1, 1, sceMoveOpen);
LIB_FUNCTION("GWXTyxs4QbE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveGetDeviceInfo);
LIB_FUNCTION("f2bcpK6kJfg", "libSceMove", 1, "libSceMove", 1, 1, sceMoveReadStateRecent);
LIB_FUNCTION("tsZi60H4ypY", "libSceMove", 1, "libSceMove", 1, 1, sceMoveTerm);
LIB_FUNCTION("j1ITE-EoJmE", "libSceMove", 1, "libSceMove", 1, 1, sceMoveInit);
};
} // namespace Libraries::Move

View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Move {
int PS4_SYSV_ABI sceMoveOpen();
int PS4_SYSV_ABI sceMoveGetDeviceInfo();
int PS4_SYSV_ABI sceMoveReadStateRecent();
int PS4_SYSV_ABI sceMoveTerm();
int PS4_SYSV_ABI sceMoveInit();
void RegisterlibSceMove(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Move

View File

@ -498,7 +498,7 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
OrbisNpTrophyHandle handle,
OrbisNpTrophyFlagArray* flags, u32* count) {
LOG_INFO(Lib_NpTrophy, "GetTrophyUnlockState called");
LOG_INFO(Lib_NpTrophy, "called");
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
@ -519,8 +519,9 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
if (!result) {
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy xml : {}", result.description());
return -1;
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy XML: {}", result.description());
*count = 0;
return ORBIS_OK;
}
int num_trophies = 0;

View File

@ -5,6 +5,7 @@
#include "common/arch.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "common/string_util.h"
@ -65,21 +66,41 @@ void Linker::Execute() {
Relocate(m.get());
}
// Configure used flexible memory size.
if (const auto* proc_param = GetProcParam()) {
if (proc_param->size >=
offsetof(OrbisProcParam, mem_param) + sizeof(OrbisKernelMemParam*)) {
if (const auto* mem_param = proc_param->mem_param) {
if (mem_param->size >=
offsetof(OrbisKernelMemParam, flexible_memory_size) + sizeof(u64*)) {
if (const auto* flexible_size = mem_param->flexible_memory_size) {
memory->SetupMemoryRegions(*flexible_size);
}
// Configure the direct and flexible memory regions.
u64 fmem_size = SCE_FLEXIBLE_MEMORY_SIZE;
bool use_extended_mem1 = true, use_extended_mem2 = true;
const auto* proc_param = GetProcParam();
ASSERT(proc_param);
Core::OrbisKernelMemParam mem_param{};
if (proc_param->size >= offsetof(OrbisProcParam, mem_param) + sizeof(OrbisKernelMemParam*)) {
if (proc_param->mem_param) {
mem_param = *proc_param->mem_param;
if (mem_param.size >=
offsetof(OrbisKernelMemParam, flexible_memory_size) + sizeof(u64*)) {
if (const auto* flexible_size = mem_param.flexible_memory_size) {
fmem_size = *flexible_size + SCE_FLEXIBLE_MEMORY_BASE;
}
}
}
}
if (mem_param.size < offsetof(OrbisKernelMemParam, extended_memory_1) + sizeof(u64*)) {
mem_param.extended_memory_1 = nullptr;
}
if (mem_param.size < offsetof(OrbisKernelMemParam, extended_memory_2) + sizeof(u64*)) {
mem_param.extended_memory_2 = nullptr;
}
const u64 sdk_ver = proc_param->sdk_version;
if (sdk_ver < Common::ElfInfo::FW_50) {
use_extended_mem1 = mem_param.extended_memory_1 ? *mem_param.extended_memory_1 : false;
use_extended_mem2 = mem_param.extended_memory_2 ? *mem_param.extended_memory_2 : false;
}
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
main_thread.Run([this, module](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
LoadSharedLibraries();

View File

@ -22,8 +22,9 @@ struct OrbisKernelMemParam {
u8* extended_memory_1;
u64* extended_gpu_page_table;
u8* extended_memory_2;
u64* exnteded_cpu_page_table;
u64* extended_cpu_page_table;
};
static_assert(sizeof(OrbisKernelMemParam) == 0x38);
struct OrbisProcParam {
u64 size;

View File

@ -12,12 +12,7 @@
namespace Core {
constexpr u64 SCE_DEFAULT_FLEXIBLE_MEMORY_SIZE = 448_MB;
MemoryManager::MemoryManager() {
// Set up the direct and flexible memory regions.
SetupMemoryRegions(SCE_DEFAULT_FLEXIBLE_MEMORY_SIZE);
// Insert a virtual memory area that covers the entire area we manage.
const VAddr system_managed_base = impl.SystemManagedVirtualBase();
const size_t system_managed_size = impl.SystemManagedVirtualSize();
@ -38,10 +33,17 @@ MemoryManager::MemoryManager() {
MemoryManager::~MemoryManager() = default;
void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
const auto total_size =
Config::isNeoMode() ? SCE_KERNEL_MAIN_DMEM_SIZE_PRO : SCE_KERNEL_MAIN_DMEM_SIZE;
total_flexible_size = flexible_size;
void MemoryManager::SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1,
bool use_extended_mem2) {
const bool is_neo = Config::isNeoMode();
auto total_size = is_neo ? SCE_KERNEL_TOTAL_MEM_PRO : SCE_KERNEL_TOTAL_MEM;
if (!use_extended_mem1 && is_neo) {
total_size -= 256_MB;
}
if (!use_extended_mem2 && !is_neo) {
total_size -= 128_MB;
}
total_flexible_size = flexible_size - SCE_FLEXIBLE_MEMORY_BASE;
total_direct_size = total_size - flexible_size;
// Insert an area that covers direct memory physical block.

View File

@ -166,7 +166,7 @@ public:
bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
void SetupMemoryRegions(u64 flexible_size);
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment);

View File

@ -45,10 +45,6 @@ Frontend::WindowSDL* g_window = nullptr;
namespace Core {
Emulator::Emulator() {
// Read configuration file.
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(config_dir / "config.toml");
// Initialize NT API functions and set high priority
#ifdef _WIN32
Common::NtApi::Initialize();

View File

@ -4,11 +4,14 @@
#include "functional"
#include "iostream"
#include "string"
#include "system_error"
#include "unordered_map"
#include <fmt/core.h>
#include "common/config.h"
#include "common/memory_patcher.h"
#include "common/path_util.h"
#include "core/file_sys/fs.h"
#include "emulator.h"
#ifdef _WIN32
@ -20,6 +23,10 @@ int main(int argc, char* argv[]) {
SetConsoleOutputCP(CP_UTF8);
#endif
// Load configurations
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");
bool has_game_argument = false;
std::string game_path;
@ -33,6 +40,7 @@ int main(int argc, char* argv[]) {
" -p, --patch <patch_file> Apply specified patch file\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen "
"state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" -h, --help Display this help message\n";
exit(0);
}},
@ -81,6 +89,25 @@ int main(int argc, char* argv[]) {
Config::setFullscreenMode(is_fullscreen);
}},
{"--fullscreen", [&](int& i) { arg_map["-f"](i); }},
{"--add-game-folder",
[&](int& i) {
if (++i >= argc) {
std::cerr << "Error: Missing argument for --add-game-folder\n";
exit(1);
}
std::string config_dir(argv[i]);
std::filesystem::path config_path = std::filesystem::path(config_dir);
std::error_code discard;
if (!std::filesystem::exists(config_path, discard)) {
std::cerr << "Error: File does not exist: " << config_path << "\n";
exit(1);
}
Config::addGameInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
std::cout << "Game folder successfully saved.\n";
exit(0);
}},
};
if (argc == 1) {
@ -105,20 +132,41 @@ int main(int argc, char* argv[]) {
}
}
// If no game directory is set and no command line argument, prompt for it
if (Config::getGameInstallDirs().empty()) {
std::cout << "Warning: No game folder set, please set it by calling shadps4"
" with the --add-game-folder <folder_name> argument";
}
if (!has_game_argument) {
std::cerr << "Error: Please provide a game path or ID.\n";
exit(1);
}
// Check if the game path or ID exists
if (!std::filesystem::exists(game_path)) {
std::cerr << "Error: Game file not found\n";
return -1;
std::filesystem::path eboot_path(game_path);
// Check if the provided path is a valid file
if (!std::filesystem::exists(eboot_path)) {
// If not a file, treat it as a game ID and search in install directories
bool game_found = false;
for (const auto& install_dir : Config::getGameInstallDirs()) {
const auto candidate_path = install_dir / game_path / "eboot.bin";
if (std::filesystem::exists(candidate_path)) {
eboot_path = candidate_path;
game_found = true;
break;
}
}
if (!game_found) {
std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl;
return 1;
}
}
// Run the emulator with the specified game
// Run the emulator with the resolved eboot path
Core::Emulator emulator;
emulator.Run(game_path);
emulator.Run(eboot_path);
return 0;
}

View File

@ -14,6 +14,7 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QStandardPaths>
#include <QString>
@ -24,11 +25,9 @@
#include <common/path_util.h>
#include <common/scm_rev.h>
#include <common/version.h>
#include <qprogressbar.h>
#include "check_update.h"
using namespace Common::FS;
namespace fs = std::filesystem;
CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent)
: QDialog(parent), networkManager(new QNetworkAccessManager(this)) {
@ -254,7 +253,11 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
connect(noButton, &QPushButton::clicked, this, [this]() { close(); });
autoUpdateCheckBox->setChecked(Config::autoUpdate());
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) {
#else
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) {
#endif
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::setAutoUpdate(state == Qt::Checked);
Config::save(user_dir / "config.toml");

View File

@ -347,9 +347,8 @@ public:
if (selected == deleteUpdate) {
if (!std::filesystem::exists(Common::FS::PathFromQString(game_update_path))) {
QMessageBox::critical(
nullptr, tr("Error"),
QString(tr("This game has no separate update to delete!")));
QMessageBox::critical(nullptr, tr("Error"),
QString(tr("This game has no update to delete!")));
error = true;
} else {
folder_path = game_update_path;

View File

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "iostream"
#include "system_error"
#include "unordered_map"
#include "common/config.h"
@ -31,7 +32,7 @@ int main(int argc, char* argv[]) {
bool has_command_line_argument = argc > 1;
bool show_gui = false, has_game_argument = false;
std::string gamePath;
std::string game_path;
// Map of argument strings to lambda functions
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
@ -46,6 +47,7 @@ int main(int argc, char* argv[]) {
" -s, --show-gui Show the GUI\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen "
"state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" -h, --help Display this help message\n";
exit(0);
}},
@ -57,7 +59,7 @@ int main(int argc, char* argv[]) {
{"-g",
[&](int& i) {
if (i + 1 < argc) {
gamePath = argv[++i];
game_path = argv[++i];
has_game_argument = true;
} else {
std::cerr << "Error: Missing argument for -g/--game\n";
@ -98,6 +100,25 @@ int main(int argc, char* argv[]) {
Config::setFullscreenMode(is_fullscreen);
}},
{"--fullscreen", [&](int& i) { arg_map["-f"](i); }},
{"--add-game-folder",
[&](int& i) {
if (++i >= argc) {
std::cerr << "Error: Missing argument for --add-game-folder\n";
exit(1);
}
std::string config_dir(argv[i]);
std::filesystem::path config_path = std::filesystem::path(config_dir);
std::error_code discard;
if (!std::filesystem::is_directory(config_path, discard)) {
std::cerr << "Error: Directory does not exist: " << config_path << "\n";
exit(1);
}
Config::addGameInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
std::cout << "Game folder successfully saved.\n";
exit(0);
}},
};
// Parse command-line arguments using the map
@ -106,6 +127,10 @@ int main(int argc, char* argv[]) {
auto it = arg_map.find(cur_arg);
if (it != arg_map.end()) {
it->second(i); // Call the associated lambda function
} else if (i == argc - 1 && !has_game_argument) {
// Assume the last argument is the game file if not specified via -g/--game
game_path = argv[i];
has_game_argument = true;
} else {
std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n";
return 1;
@ -134,14 +159,14 @@ int main(int argc, char* argv[]) {
// Process game path or ID if provided
if (has_game_argument) {
std::filesystem::path game_file_path(gamePath);
std::filesystem::path game_file_path(game_path);
// Check if the provided path is a valid file
if (!std::filesystem::exists(game_file_path)) {
// If not a file, treat it as a game ID and search in install directories
bool game_found = false;
for (const auto& install_dir : Config::getGameInstallDirs()) {
auto potential_game_path = install_dir / gamePath / "eboot.bin";
auto potential_game_path = install_dir / game_path / "eboot.bin";
if (std::filesystem::exists(potential_game_path)) {
game_file_path = potential_game_path;
game_found = true;
@ -149,7 +174,7 @@ int main(int argc, char* argv[]) {
}
}
if (!game_found) {
std::cerr << "Error: Game ID or file path not found: " << gamePath << std::endl;
std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl;
return 1;
}
}

View File

@ -112,6 +112,7 @@ void MainWindow::CreateActions() {
m_theme_act_group->addAction(ui->setThemeBlue);
m_theme_act_group->addAction(ui->setThemeViolet);
m_theme_act_group->addAction(ui->setThemeGruvbox);
m_theme_act_group->addAction(ui->setThemeTokyoNight);
}
void MainWindow::AddUiWidgets() {
@ -559,6 +560,14 @@ void MainWindow::CreateConnects() {
isIconBlack = false;
}
});
connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() {
m_window_themes.SetWindowTheme(Theme::TokyoNight, ui->mw_searchbar);
Config::setMainWindowTheme(static_cast<int>(Theme::TokyoNight));
if (isIconBlack) {
SetUiIcons(false);
isIconBlack = false;
}
});
}
void MainWindow::StartGame() {
@ -934,6 +943,11 @@ void MainWindow::SetLastUsedTheme() {
isIconBlack = false;
SetUiIcons(false);
break;
case Theme::TokyoNight:
ui->setThemeTokyoNight->setChecked(true);
isIconBlack = false;
SetUiIcons(false);
break;
}
}

View File

@ -143,5 +143,27 @@ void WindowThemes::SetWindowTheme(Theme theme, QLineEdit* mw_searchbar) {
themePalette.setColor(QPalette::HighlightedText, Qt::black);
qApp->setPalette(themePalette);
break;
case Theme::TokyoNight:
mw_searchbar->setStyleSheet(
"QLineEdit {"
"background-color: #1a1b26; color: #9d7cd8; border: 1px solid #9d7cd8; "
"border-radius: 4px; padding: 5px; }"
"QLineEdit:focus {"
"border: 1px solid #7aa2f7; }");
themePalette.setColor(QPalette::Window, QColor(31, 35, 53));
themePalette.setColor(QPalette::WindowText, QColor(192, 202, 245));
themePalette.setColor(QPalette::Base, QColor(25, 28, 39));
themePalette.setColor(QPalette::AlternateBase, QColor(36, 40, 59));
themePalette.setColor(QPalette::ToolTipBase, QColor(192, 202, 245));
themePalette.setColor(QPalette::ToolTipText, QColor(192, 202, 245));
themePalette.setColor(QPalette::Text, QColor(192, 202, 245));
themePalette.setColor(QPalette::Button, QColor(30, 30, 41));
themePalette.setColor(QPalette::ButtonText, QColor(192, 202, 245));
themePalette.setColor(QPalette::BrightText, QColor(197, 59, 83));
themePalette.setColor(QPalette::Link, QColor(79, 214, 190));
themePalette.setColor(QPalette::Highlight, QColor(79, 214, 190));
themePalette.setColor(QPalette::HighlightedText, Qt::black);
qApp->setPalette(themePalette);
break;
}
}

View File

@ -7,7 +7,7 @@
#include <QLineEdit>
#include <QWidget>
enum class Theme : int { Dark, Light, Green, Blue, Violet, Gruvbox };
enum class Theme : int { Dark, Light, Green, Blue, Violet, Gruvbox, TokyoNight };
class WindowThemes : public QObject {
Q_OBJECT

View File

@ -37,6 +37,7 @@ public:
QAction* setThemeBlue;
QAction* setThemeViolet;
QAction* setThemeGruvbox;
QAction* setThemeTokyoNight;
QWidget* centralWidget;
QLineEdit* mw_searchbar;
QPushButton* playButton;
@ -162,6 +163,9 @@ public:
setThemeGruvbox = new QAction(MainWindow);
setThemeGruvbox->setObjectName("setThemeGruvbox");
setThemeGruvbox->setCheckable(true);
setThemeTokyoNight = new QAction(MainWindow);
setThemeTokyoNight->setObjectName("setThemeTokyoNight");
setThemeTokyoNight->setCheckable(true);
centralWidget = new QWidget(MainWindow);
centralWidget->setObjectName("centralWidget");
sizePolicy.setHeightForWidth(centralWidget->sizePolicy().hasHeightForWidth());
@ -287,6 +291,7 @@ public:
menuThemes->addAction(setThemeBlue);
menuThemes->addAction(setThemeViolet);
menuThemes->addAction(setThemeGruvbox);
menuThemes->addAction(setThemeTokyoNight);
menuGame_List_Icons->addAction(setIconSizeTinyAct);
menuGame_List_Icons->addAction(setIconSizeSmallAct);
menuGame_List_Icons->addAction(setIconSizeMediumAct);
@ -374,6 +379,7 @@ public:
setThemeBlue->setText(QCoreApplication::translate("MainWindow", "Blue", nullptr));
setThemeViolet->setText(QCoreApplication::translate("MainWindow", "Violet", nullptr));
setThemeGruvbox->setText("Gruvbox");
setThemeTokyoNight->setText("Tokyo Night");
toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr));
} // retranslateUi
};

View File

@ -3,22 +3,24 @@
#include <QCompleter>
#include <QDirIterator>
#include <QFileDialog>
#include <QHoverEvent>
#include <fmt/format.h>
#include <common/version.h>
#include "common/config.h"
#include "common/version.h"
#include "qt_gui/compatibility_info.h"
#ifdef ENABLE_DISCORD_RPC
#include "common/discord_rpc_handler.h"
#include "common/singleton.h"
#endif
#ifdef ENABLE_UPDATER
#include "check_update.h"
#endif
#include <toml.hpp>
#include "background_music_player.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/logging/formatter.h"
#include "main_window.h"
#include "settings_dialog.h"
#include "ui_settings_dialog.h"
QStringList languageNames = {"Arabic",
@ -130,8 +132,13 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices,
// GENERAL TAB
{
#ifdef ENABLE_UPDATER
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this,
[](int state) { Config::setAutoUpdate(state == Qt::Checked); });
#else
connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state) { Config::setAutoUpdate(state == Qt::Checked); });
#endif
connect(ui->updateComboBox, &QComboBox::currentTextChanged, this,
[](const QString& channel) { Config::setUpdateChannel(channel.toStdString()); });
@ -150,7 +157,12 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices,
emit CompatibilityChanged();
});
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->enableCompatibilityCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
#else
connect(ui->enableCompatibilityCheckBox, &QCheckBox::checkStateChanged, this,
[this](Qt::CheckState state) {
#endif
Config::setCompatibilityEnabled(state);
emit CompatibilityChanged();
});
@ -199,7 +211,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices,
ui->fullscreenCheckBox->installEventFilter(this);
ui->separateUpdatesCheckBox->installEventFilter(this);
ui->showSplashCheckBox->installEventFilter(this);
ui->ps4proCheckBox->installEventFilter(this);
ui->discordRPCCheckbox->installEventFilter(this);
ui->userName->installEventFilter(this);
ui->logTypeGroupBox->installEventFilter(this);
@ -212,7 +223,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices,
ui->enableCompatibilityCheckBox->installEventFilter(this);
ui->checkCompatibilityOnStartupCheckBox->installEventFilter(this);
ui->updateCompatibilityButton->installEventFilter(this);
ui->audioBackendComboBox->installEventFilter(this);
// Input
ui->hideCursorGroupBox->installEventFilter(this);
@ -291,7 +301,6 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->separateUpdatesCheckBox->setChecked(
toml::find_or<bool>(data, "General", "separateUpdateEnabled", false));
ui->showSplashCheckBox->setChecked(toml::find_or<bool>(data, "General", "showSplash", false));
ui->ps4proCheckBox->setChecked(toml::find_or<bool>(data, "General", "isPS4Pro", false));
ui->logTypeComboBox->setCurrentText(
QString::fromStdString(toml::find_or<std::string>(data, "General", "logType", "async")));
ui->logFilterLineEdit->setText(
@ -307,8 +316,6 @@ void SettingsDialog::LoadValuesFromConfig() {
toml::find_or<bool>(data, "General", "compatibilityEnabled", false));
ui->checkCompatibilityOnStartupCheckBox->setChecked(
toml::find_or<bool>(data, "General", "checkCompatibilityOnStartup", false));
ui->audioBackendComboBox->setCurrentText(
QString::fromStdString(toml::find_or<std::string>(data, "Audio", "backend", "cubeb")));
#ifdef ENABLE_UPDATER
ui->updateCheckBox->setChecked(toml::find_or<bool>(data, "General", "autoUpdate", false));
@ -363,7 +370,7 @@ void SettingsDialog::InitializeEmulatorLanguages() {
idx++;
}
connect(ui->emulatorLanguageComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
connect(ui->emulatorLanguageComboBox, &QComboBox::currentIndexChanged, this,
&SettingsDialog::OnLanguageChanged);
}
@ -408,8 +415,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("separateUpdatesCheckBox");
} else if (elementName == "showSplashCheckBox") {
text = tr("showSplashCheckBox");
} else if (elementName == "ps4proCheckBox") {
text = tr("ps4proCheckBox");
} else if (elementName == "discordRPCCheckbox") {
text = tr("discordRPCCheckbox");
} else if (elementName == "userName") {
@ -432,8 +437,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("checkCompatibilityOnStartupCheckBox");
} else if (elementName == "updateCompatibilityButton") {
text = tr("updateCompatibilityButton");
} else if (elementName == "audioBackendGroupBox") {
text = tr("audioBackendGroupBox");
}
// Input
@ -520,11 +523,9 @@ void SettingsDialog::UpdateSettings() {
const QVector<std::string> TouchPadIndex = {"left", "center", "right", "none"};
Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]);
Config::setNeoMode(ui->ps4proCheckBox->isChecked());
Config::setFullscreenMode(ui->fullscreenCheckBox->isChecked());
Config::setisTrophyPopupDisabled(ui->disableTrophycheckBox->isChecked());
Config::setPlayBGM(ui->playBGMCheckBox->isChecked());
Config::setNeoMode(ui->ps4proCheckBox->isChecked());
Config::setLogType(ui->logTypeComboBox->currentText().toStdString());
Config::setLogFilter(ui->logFilterLineEdit->text().toStdString());
Config::setUserName(ui->userNameLineEdit->text().toStdString());
@ -549,7 +550,6 @@ 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<DiscordRPCHandler::RPC>::Instance();

View File

@ -228,13 +228,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="ps4proCheckBox">
<property name="text">
<string>Is PS4 Pro</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="discordRPCCheckbox">
<property name="text">
@ -270,29 +263,6 @@
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="audioBackendGroupBox">
<property name="title">
<string>Audio Backend</string>
</property>
<layout class="QVBoxLayout" name="audioBackendBoxLayout">
<item>
<widget class="QComboBox" name="audioBackendComboBox">
<item>
<property name="text">
<string>cubeb</string>
</property>
</item>
<item>
<property name="text">
<string>sdl</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -2,7 +2,7 @@
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<!-- SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later -->
SPDX-License-Identifier: GPL-2.0-or-later -->
<context>
<name>AboutDialog</name>
<message>
@ -18,7 +18,7 @@
<message>
<location filename="../about_dialog.ui" line="78"/>
<source>shadPS4 is an experimental open-source emulator for the PlayStation 4.</source>
<translation>shadPS4 PlayStation 4</translation>
<translation>shadPS4 PlayStation 4 </translation>
</message>
<message>
<location filename="../about_dialog.ui" line="99"/>
@ -103,7 +103,7 @@
<message>
<location filename="../gui_context_menus.h" line="48"/>
<source>Cheats / Patches</source>
<translation> / </translation>
<translation>/</translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="49"/>
@ -113,7 +113,7 @@
<message>
<location filename="../gui_context_menus.h" line="50"/>
<source>Trophy Viewer</source>
<translation>Trophy </translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="49"/>
@ -128,7 +128,7 @@
<message>
<location filename="../gui_context_menus.h" line="51"/>
<source>Open Save Data Folder</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="52"/>
@ -173,27 +173,27 @@
<message>
<location filename="../gui_context_menus.h" line="75"/>
<source>Delete DLC</source>
<translation>DLC</translation>
<translation> DLC</translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="99"/>
<source>Compatibility...</source>
<translation>Compatibility...</translation>
<translation>...</translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="100"/>
<source>Update database</source>
<translation>Update database</translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="101"/>
<source>View report</source>
<translation>View report</translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="102"/>
<source>Submit a report</source>
<translation>Submit a report</translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="195"/>
@ -203,7 +203,7 @@
<message>
<location filename="../gui_context_menus.h" line="196"/>
<source>Shortcut created successfully!</source>
<translation>!</translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="199"/>
@ -213,7 +213,7 @@
<message>
<location filename="../gui_context_menus.h" line="200"/>
<source>Error creating shortcut!</source>
<translation>!</translation>
<translation></translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="275"/>
@ -228,7 +228,7 @@
<message>
<location filename="../gui_context_menus.h" line="305"/>
<source>requiresEnableSeparateUpdateFolder_MSG</source>
<translation>使</translation>
<translation>使</translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="312"/>
@ -243,7 +243,7 @@
<message>
<location filename="../gui_context_menus.h" line="321"/>
<source>This game has no DLC to delete!</source>
<translation>DLC可以删除</translation>
<translation> DLC </translation>
</message>
<message>
<location filename="../gui_context_menus.h" line="325"/>
@ -258,7 +258,7 @@
<message>
<location filename="../gui_context_menus.h" line="333"/>
<source>Are you sure you want to delete %1's %2 directory?</source>
<translation> %1 %2 </translation>
<translation> %1 %2</translation>
</message>
</context>
<context>
@ -266,7 +266,7 @@
<message>
<location filename="../main_window_ui.h" line="310"/>
<source>Open/Add Elf Folder</source>
<translation>/Elf文件夹</translation>
<translation>/ Elf </translation>
</message>
<message>
<location filename="../main_window_ui.h" line="312"/>
@ -316,7 +316,7 @@
<message>
<location filename="../main_window_ui.h" line="327"/>
<source>Exit the application.</source>
<translation>退.</translation>
<translation>退</translation>
</message>
<message>
<location filename="../main_window_ui.h" line="330"/>
@ -341,12 +341,12 @@
<message>
<location filename="../main_window_ui.h" line="335"/>
<source>Medium</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../main_window_ui.h" line="336"/>
<source>Large</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../main_window_ui.h" line="338"/>
@ -376,7 +376,7 @@
<message>
<location filename="../main_window_ui.h" line="345"/>
<source>Dump Game List</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../main_window_ui.h" line="346"/>
@ -472,7 +472,7 @@
<message>
<location filename="../trophy_viewer.cpp" line="8"/>
<source>Trophy Viewer</source>
<translation>Trophy </translation>
<translation></translation>
</message>
</context>
<context>
@ -485,7 +485,7 @@
<message>
<location filename="../settings_dialog.ui" line="67"/>
<source>General</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="77"/>
@ -520,12 +520,12 @@
<message>
<location filename="../settings_dialog.ui" line="129"/>
<source>Show Splash</source>
<translation>Splash</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="136"/>
<source>Is PS4 Pro</source>
<translation> PS4 Pro</translation>
<translation> PS4 Pro</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="154"/>
@ -570,17 +570,17 @@
<message>
<location filename="../settings_dialog.ui" line="668"/>
<source>Hide Cursor Idle Timeout</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="816"/>
<source>s</source>
<translation>s</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="767"/>
<source>Controller</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="797"/>
@ -595,7 +595,7 @@
<message>
<location filename="../settings_dialog.ui" line="282"/>
<source>Graphics Device</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="326"/>
@ -700,7 +700,7 @@
<message>
<location filename="../settings_dialog.ui" line="475"/>
<source>Disable Trophy Pop-ups</source>
<translation>Disable Trophy Pop-ups</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="375"/>
@ -710,22 +710,22 @@
<message>
<location filename="../settings_dialog.ui"/>
<source>Update Compatibility Database On Startup</source>
<translation>Update Compatibility Database On Startup</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui"/>
<source>Game Compatibility</source>
<translation>Game Compatibility</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui"/>
<source>Display Compatibility Data</source>
<translation>Display Compatibility Data</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui"/>
<source>Update Compatibility Database</source>
<translation>Update Compatibility Database</translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="394"/>
@ -735,7 +735,7 @@
<message>
<location filename="../settings_dialog.ui"/>
<source>Audio Backend</source>
<translation>Audio Backend</translation>
<translation></translation>
</message>
</context>
<context>
@ -778,12 +778,12 @@
<message>
<location filename="../main_window.cpp" line="392"/>
<source>All Patches available for all games have been downloaded.</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../main_window.cpp" line="549"/>
<source>Games: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../main_window.cpp" line="575"/>
@ -798,7 +798,7 @@
<message>
<location filename="../main_window.cpp" line="600"/>
<source>Game Boot</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../main_window.cpp" line="600"/>
@ -818,7 +818,7 @@
<message>
<location filename="../main_window.cpp" line="646"/>
<source>PKG and Game versions match: </source>
<translation>PKG : </translation>
<translation>PKG </translation>
</message>
<message>
<location filename="../main_window.cpp" line="647"/>
@ -828,17 +828,17 @@
<message>
<location filename="../main_window.cpp" line="639"/>
<source>PKG Version %1 is older than installed version: </source>
<translation>PKG %1 : </translation>
<translation>PKG %1 </translation>
</message>
<message>
<location filename="../main_window.cpp" line="660"/>
<source>Game is installed: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../main_window.cpp" line="660"/>
<source>Would you like to install Patch: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../main_window.cpp" line="673"/>
@ -848,12 +848,12 @@
<message>
<location filename="../main_window.cpp" line="674"/>
<source>Would you like to install DLC: %1?</source>
<translation> DLC: %1 </translation>
<translation> DLC%1 </translation>
</message>
<message>
<location filename="../main_window.cpp" line="688"/>
<source>DLC already installed:</source>
<translation>DLC :</translation>
<translation>DLC </translation>
</message>
<message>
<location filename="../main_window.cpp" line="701"/>
@ -896,42 +896,42 @@
<message>
<location filename="../cheats_patches.cpp" line="44"/>
<source>Cheats / Patches for </source>
<translation>Cheats / Patches for </translation>
<translation>/</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="50"/>
<source>defaultTextEdit_MSG</source>
<translation>/\n请小心使用\n\n通过选择存储库并点击下载按钮\n在使\n\n由于我们不开发作弊程序/\n请将问题报告给作弊程序的作者\n\n创建了新的作弊程序访\nhttps://github.com/shadps4-emu/ps4_cheats</translation>
<translation>/\n请小心使用\n\n通过选择存储库并点击下载按钮\n在使\n\n由于我们不开发作弊/\n请将问题报告给作弊码/\n\n创建了新的作弊码/\nhttps://github.com/shadps4-emu/ps4_cheats</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="69"/>
<source>No Image Available</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="79"/>
<source>Serial: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="83"/>
<source>Version: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="87"/>
<source>Size: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="126"/>
<source>Select Cheat File:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="133"/>
<source>Repository:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="149"/>
@ -961,7 +961,7 @@
<message>
<location filename="../cheats_patches.cpp" line="213"/>
<source>Select Patch File:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="241"/>
@ -1016,7 +1016,7 @@
<message>
<location filename="../cheats_patches.cpp" line="432"/>
<source>Failed to parse XML: </source>
<translation> XML : </translation>
<translation> XML </translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="434"/>
@ -1046,17 +1046,17 @@
<message>
<location filename="../cheats_patches.cpp" line="520"/>
<source>File already exists. Do you want to replace it?</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="539"/>
<source>Failed to save file:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="545"/>
<source>Failed to download file:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="556"/>
@ -1076,17 +1076,17 @@
<message>
<location filename="../cheats_patches.cpp" line="594"/>
<source>CheatsDownloadedSuccessfully_MSG</source>
<translation> 使</translation>
<translation>使</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="747"/>
<source>Failed to save:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="754"/>
<source>Failed to download:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="762"/>
@ -1096,7 +1096,7 @@
<message>
<location filename="../cheats_patches.cpp" line="763"/>
<source>DownloadComplete_MSG</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="773"/>
@ -1111,12 +1111,12 @@
<message>
<location filename="../cheats_patches.cpp" line="850"/>
<source>The game is in version: %1</source>
<translation>: %1</translation>
<translation>%1</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="851"/>
<source>The downloaded patch only works on version: %1</source>
<translation>: %1</translation>
<translation>%1</translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="856"/>
@ -1131,12 +1131,12 @@
<message>
<location filename="../cheats_patches.cpp" line="801"/>
<source>Failed to open file:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="819"/>
<source>XML ERROR:</source>
<translation>XML :</translation>
<translation>XML </translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="826"/>
@ -1146,12 +1146,12 @@
<message>
<location filename="../cheats_patches.cpp" line="925"/>
<source>Author: </source>
<translation>: </translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="997"/>
<source>Directory does not exist:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="1006"/>
@ -1161,12 +1161,12 @@
<message>
<location filename="../cheats_patches.cpp" line="1006"/>
<source>Name:</source>
<translation>:</translation>
<translation></translation>
</message>
<message>
<location filename="../cheats_patches.cpp" line="1163"/>
<source>Can't apply cheats before the game is started</source>
<translation></translation>
<translation></translation>
</message>
</context>
<context>
@ -1199,17 +1199,17 @@
<message>
<location filename="../settings_dialog.cpp" line="289"/>
<source>consoleLanguageGroupBox</source>
<translation>:\n设置 PS4 使\n建议设置为支持的语言</translation>
<translation>\n设置 PS4 使\n建议设置为支持的语言</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="291"/>
<source>emulatorLanguageGroupBox</source>
<translation>:\n设置模拟器用户界面的语言</translation>
<translation>\n设置模拟器用户界面的语言</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="293"/>
<source>fullscreenCheckBox</source>
<translation>:\n自动将游戏窗口设置为全屏模式\n您可以按 F11 </translation>
<translation>\n以全屏模式启动游戏\n您可以按 F11 </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="293"/>
@ -1219,77 +1219,77 @@
<message>
<location filename="../settings_dialog.cpp" line="295"/>
<source>showSplashCheckBox</source>
<translation>:\n在游戏启动时显示游戏的启动画面</translation>
<translation>\n在游戏启动时显示游戏的启动画面</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="297"/>
<source>ps4proCheckBox</source>
<translation> PS4 Pro:\n使模拟器作为 PS4 PRO </translation>
<translation> PS4 Pro:\n使模拟器作为 PS4 Pro </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="438"/>
<source>discordRPCCheckbox</source>
<translation> Discord Rich Presence:\n在您的 Discord 仿</translation>
<translation> Discord Rich Presence\n在您的 Discord </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="299"/>
<source>userName</source>
<translation>:\n设置 PS4 </translation>
<translation>\n设置 PS4 </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="301"/>
<source>logTypeGroupBox</source>
<translation>:\n设置是否同步日志窗口的输出以提高性能</translation>
<translation>\n设置日志窗口输出的同步方式以提高性能</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="303"/>
<source>logFilter</source>
<translation>:\n过滤日志\n例如"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - </translation>
<translation>\n过滤日志\n例如"Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" 级别: Trace, Debug, Info, Warning, Error, Critical - </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="305"/>
<source>updaterGroupBox</source>
<translation>:\nRelease: 官方版本\nNightly: 开发版本</translation>
<translation>\nRelease\nNightly</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="306"/>
<source>GUIgroupBox</source>
<translation>:\n如果游戏支持</translation>
<translation>\n如果游戏支持</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="267"/>
<source>disableTrophycheckBox</source>
<translation>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).</translation>
<translation>\n禁用游戏内奖杯通知</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="450"/>
<source>hideCursorGroupBox</source>
<translation>:\n选择光标何时消失:\n从不: 您将始终看到鼠标\n空闲: 设置光标在空闲后消失的时间\n始终: 您将永远看不到鼠</translation>
<translation>\n选择光标何时消失:\n从不: 始终显示光标\闲置: 光标在闲置若干秒后消失\n始终: 始终显示光</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="452"/>
<source>idleTimeoutGroupBox</source>
<translation></translation>
<translation>\n光标自动隐藏之前的闲置时长</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="454"/>
<source>backButtonBehaviorGroupBox</source>
<translation>:\n设置控制器的返回按钮以模拟在 PS4 </translation>
<translation>\n设置手柄的返回按钮模拟在 PS4 </translation>
</message>
<message>
<location filename="../settings_dialog.cpp"/>
<source>enableCompatibilityCheckBox</source>
<translation>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information.</translation>
<translation>\n在列表视图中显示游戏兼容性信息</translation>
</message>
<message>
<location filename="../settings_dialog.cpp"/>
<source>checkCompatibilityOnStartupCheckBox</source>
<translation>Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts.</translation>
<translation>\n当 shadPS4 </translation>
</message>
<message>
<location filename="../settings_dialog.cpp"/>
<source>updateCompatibilityButton</source>
<translation>Update Compatibility Database:\nImmediately update the compatibility database.</translation>
<translation>\n立即更新兼容性数据库</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="70"/>
@ -1299,7 +1299,7 @@
<message>
<location filename="../settings_dialog.cpp" line="71"/>
<source>Idle</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="72"/>
@ -1329,62 +1329,62 @@
<message>
<location filename="../settings_dialog.cpp" line="312"/>
<source>graphicsAdapterGroupBox</source>
<translation>:\n在具有多个 GPU 使 GPU\n或者选择</translation>
<translation>\n在具有多个 GPU 使 GPU\n或者选择</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="314"/>
<source>resolutionLayout</source>
<translation>/:\n设置启动时模拟器的窗口大小\n这与游戏中的分辨率不同</translation>
<translation>/\n设置启动游戏时的窗口大小\n这与游戏内的分辨率不同</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="318"/>
<source>heightDivider</source>
<translation>Vblank :\n模拟器更新的帧速率乘以此数字!</translation>
<translation>Vblank Divider\n模拟器刷新的帧率会乘以此数字</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="320"/>
<source>dumpShadersCheckBox</source>
<translation>:\n为了技术调试</translation>
<translation>\n用于技术调试</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="322"/>
<source>nullGpuCheckBox</source>
<translation> GPU:\n为了技术调试仿</translation>
<translation> NULL GPU\n用于技术调试</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="465"/>
<source>gameFoldersBox</source>
<translation>:\n检查已安装游戏的文件夹列表</translation>
<translation>\n检查已安装游戏的文件夹列表</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="465"/>
<source>addFolderButton</source>
<translation>:\n将文件夹添加到列表</translation>
<translation>\n将文件夹添加到列表</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="465"/>
<source>removeFolderButton</source>
<translation>:\n从列表中移除文件夹</translation>
<translation>\n从列表中移除文件夹</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source>
<translation>:\n将当前正在运行的 PS4 </translation>
<translation>\n将当前正在运行的 PS4 </translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="331"/>
<source>vkValidationCheckBox</source>
<translation> Vulkan :\n启用验证 Vulkan </translation>
<translation> Vulkan \n启用一个系统来验证 Vulkan \n这将降低性能并可能改变模拟的行</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="333"/>
<source>vkSyncValidationCheckBox</source>
<translation> Vulkan :\n启用验证 Vulkan </translation>
<translation> Vulkan \n启用一个系统来验证 Vulkan \n这将降低性能并可能改变模拟的行</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="335"/>
<source>rdocCheckBox</source>
<translation> RenderDoc :\n如果启用Renderdoc </translation>
<translation> RenderDoc :\n启用后模拟器将提供Renderdoc </translation>
</message>
</context>
<context>
@ -1407,7 +1407,7 @@
<message>
<location filename="../game_list_frame.cpp"/>
<source>Compatibility</source>
<translation>Compatibility</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp" line="34"/>
@ -1442,52 +1442,52 @@
<message>
<location filename="../game_list_frame.cpp" line="108"/>
<source>Never Played</source>
<translation>Never Played</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>h</source>
<translation>h</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>m</source>
<translation>m</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>s</source>
<translation>s</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Compatibility is untested</source>
<translation>Compatibility is untested</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Game does not initialize properly / crashes the emulator</source>
<translation>Game does not initialize properly / crashes the emulator</translation>
<translation>/</translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Game boots, but only displays a blank screen</source>
<translation>Game boots, but only displays a blank screen</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Game displays an image but does not go past the menu</source>
<translation>Game displays an image but does not go past the menu</translation>
<translation></translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Game has game-breaking glitches or unplayable performance</source>
<translation>Game has game-breaking glitches or unplayable performance</translation>
<translation> Bug </translation>
</message>
<message>
<location filename="../game_list_frame.cpp"/>
<source>Game can be completed with playable performance and no major glitches</source>
<translation>Game can be completed with playable performance and no major glitches</translation>
<translation> Bug</translation>
</message>
</context>
<context>
@ -1525,7 +1525,7 @@
<message>
<location filename="../check_update.cpp" line="142"/>
<source>No download URL found for the specified asset.</source>
<translation> URL</translation>
<translation></translation>
</message>
<message>
<location filename="../check_update.cpp" line="148"/>
@ -1560,7 +1560,7 @@
<message>
<location filename="../check_update.cpp" line="193"/>
<source>Show Changelog</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../check_update.cpp" line="198"/>
@ -1580,17 +1580,17 @@
<message>
<location filename="../check_update.cpp" line="223"/>
<source>Hide Changelog</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../check_update.cpp" line="296"/>
<source>Changes</source>
<translation></translation>
<translation></translation>
</message>
<message>
<location filename="../check_update.cpp" line="310"/>
<source>Network error occurred while trying to access the URL</source>
<translation>访 URL </translation>
<translation>访</translation>
</message>
<message>
<location filename="../check_update.cpp" line="330"/>

View File

@ -92,6 +92,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
UNREACHABLE_MSG("Failed to create window handle: {}", SDL_GetError());
}
SDL_SetWindowMinimumSize(window, 640, 360);
SDL_SetWindowFullscreen(window, Config::isFullscreenMode());
SDL_InitSubSystem(SDL_INIT_GAMEPAD);

View File

@ -24,9 +24,11 @@ static constexpr spv::ExecutionMode GetInputPrimitiveType(AmdGpu::PrimitiveType
case AmdGpu::PrimitiveType::PointList:
return spv::ExecutionMode::InputPoints;
case AmdGpu::PrimitiveType::LineList:
case AmdGpu::PrimitiveType::LineStrip:
return spv::ExecutionMode::InputLines;
case AmdGpu::PrimitiveType::TriangleList:
case AmdGpu::PrimitiveType::TriangleStrip:
case AmdGpu::PrimitiveType::RectList:
return spv::ExecutionMode::Triangles;
case AmdGpu::PrimitiveType::AdjTriangleList:
return spv::ExecutionMode::InputTrianglesAdjacency;

View File

@ -6,16 +6,22 @@
namespace Shader::Backend::SPIRV {
Id EmitCompositeConstructU32x2(EmitContext& ctx, Id e1, Id e2) {
return ctx.OpCompositeConstruct(ctx.U32[2], e1, e2);
template <typename... Args>
Id EmitCompositeConstruct(EmitContext& ctx, IR::Inst* inst, Args&&... args) {
return inst->AreAllArgsImmediates() ? ctx.ConstantComposite(args...)
: ctx.OpCompositeConstruct(args...);
}
Id EmitCompositeConstructU32x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
return ctx.OpCompositeConstruct(ctx.U32[3], e1, e2, e3);
Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2) {
return EmitCompositeConstruct(ctx, inst, ctx.U32[2], e1, e2);
}
Id EmitCompositeConstructU32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
return ctx.OpCompositeConstruct(ctx.U32[4], e1, e2, e3, e4);
Id EmitCompositeConstructU32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3) {
return EmitCompositeConstruct(ctx, inst, ctx.U32[3], e1, e2, e3);
}
Id EmitCompositeConstructU32x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4) {
return EmitCompositeConstruct(ctx, inst, ctx.U32[4], e1, e2, e3, e4);
}
Id EmitCompositeExtractU32x2(EmitContext& ctx, Id composite, u32 index) {
@ -42,16 +48,30 @@ Id EmitCompositeInsertU32x4(EmitContext& ctx, Id composite, Id object, u32 index
return ctx.OpCompositeInsert(ctx.U32[4], object, composite, index);
}
Id EmitCompositeConstructF16x2(EmitContext& ctx, Id e1, Id e2) {
return ctx.OpCompositeConstruct(ctx.F16[2], e1, e2);
Id EmitCompositeShuffleU32x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1) {
return ctx.OpVectorShuffle(ctx.U32[2], composite1, composite2, comp0, comp1);
}
Id EmitCompositeConstructF16x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
return ctx.OpCompositeConstruct(ctx.F16[3], e1, e2, e3);
Id EmitCompositeShuffleU32x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2) {
return ctx.OpVectorShuffle(ctx.U32[3], composite1, composite2, comp0, comp1, comp2);
}
Id EmitCompositeConstructF16x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
return ctx.OpCompositeConstruct(ctx.F16[4], e1, e2, e3, e4);
Id EmitCompositeShuffleU32x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3) {
return ctx.OpVectorShuffle(ctx.U32[4], composite1, composite2, comp0, comp1, comp2, comp3);
}
Id EmitCompositeConstructF16x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2) {
return EmitCompositeConstruct(ctx, inst, ctx.F16[2], e1, e2);
}
Id EmitCompositeConstructF16x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3) {
return EmitCompositeConstruct(ctx, inst, ctx.F16[3], e1, e2, e3);
}
Id EmitCompositeConstructF16x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4) {
return EmitCompositeConstruct(ctx, inst, ctx.F16[4], e1, e2, e3, e4);
}
Id EmitCompositeExtractF16x2(EmitContext& ctx, Id composite, u32 index) {
@ -78,16 +98,30 @@ Id EmitCompositeInsertF16x4(EmitContext& ctx, Id composite, Id object, u32 index
return ctx.OpCompositeInsert(ctx.F16[4], object, composite, index);
}
Id EmitCompositeConstructF32x2(EmitContext& ctx, Id e1, Id e2) {
return ctx.OpCompositeConstruct(ctx.F32[2], e1, e2);
Id EmitCompositeShuffleF16x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1) {
return ctx.OpVectorShuffle(ctx.F16[2], composite1, composite2, comp0, comp1);
}
Id EmitCompositeConstructF32x3(EmitContext& ctx, Id e1, Id e2, Id e3) {
return ctx.OpCompositeConstruct(ctx.F32[3], e1, e2, e3);
Id EmitCompositeShuffleF16x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2) {
return ctx.OpVectorShuffle(ctx.F16[3], composite1, composite2, comp0, comp1, comp2);
}
Id EmitCompositeConstructF32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4) {
return ctx.OpCompositeConstruct(ctx.F32[4], e1, e2, e3, e4);
Id EmitCompositeShuffleF16x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3) {
return ctx.OpVectorShuffle(ctx.F16[4], composite1, composite2, comp0, comp1, comp2, comp3);
}
Id EmitCompositeConstructF32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2) {
return EmitCompositeConstruct(ctx, inst, ctx.F32[2], e1, e2);
}
Id EmitCompositeConstructF32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3) {
return EmitCompositeConstruct(ctx, inst, ctx.F32[3], e1, e2, e3);
}
Id EmitCompositeConstructF32x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4) {
return EmitCompositeConstruct(ctx, inst, ctx.F32[4], e1, e2, e3, e4);
}
Id EmitCompositeExtractF32x2(EmitContext& ctx, Id composite, u32 index) {
@ -114,6 +148,20 @@ Id EmitCompositeInsertF32x4(EmitContext& ctx, Id composite, Id object, u32 index
return ctx.OpCompositeInsert(ctx.F32[4], object, composite, index);
}
Id EmitCompositeShuffleF32x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1) {
return ctx.OpVectorShuffle(ctx.F32[2], composite1, composite2, comp0, comp1);
}
Id EmitCompositeShuffleF32x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2) {
return ctx.OpVectorShuffle(ctx.F32[3], composite1, composite2, comp0, comp1, comp2);
}
Id EmitCompositeShuffleF32x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3) {
return ctx.OpVectorShuffle(ctx.F32[4], composite1, composite2, comp0, comp1, comp2, comp3);
}
void EmitCompositeConstructF64x2(EmitContext&) {
UNREACHABLE_MSG("SPIR-V Instruction");
}
@ -150,4 +198,18 @@ Id EmitCompositeInsertF64x4(EmitContext& ctx, Id composite, Id object, u32 index
return ctx.OpCompositeInsert(ctx.F64[4], object, composite, index);
}
Id EmitCompositeShuffleF64x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1) {
return ctx.OpVectorShuffle(ctx.F64[2], composite1, composite2, comp0, comp1);
}
Id EmitCompositeShuffleF64x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2) {
return ctx.OpVectorShuffle(ctx.F64[3], composite1, composite2, comp0, comp1, comp2);
}
Id EmitCompositeShuffleF64x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3) {
return ctx.OpVectorShuffle(ctx.F64[4], composite1, composite2, comp0, comp1, comp2, comp3);
}
} // namespace Shader::Backend::SPIRV

View File

@ -217,14 +217,6 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) {
const auto pointer{
ctx.OpAccessChain(component_ptr, ctx.tess_coord, ctx.ConstU32(component))};
return ctx.OpLoad(ctx.F32[1], pointer);
} else if (IR::IsParam(attr)) {
const u32 param_id{u32(attr) - u32(IR::Attribute::Param0)};
const auto param = ctx.input_params.at(param_id).id;
const auto param_arr_ptr = ctx.TypePointer(spv::StorageClass::Input, ctx.F32[4]);
const auto pointer{ctx.OpAccessChain(param_arr_ptr, param, index)};
const auto position_comp_ptr = ctx.TypePointer(spv::StorageClass::Input, ctx.F32[1]);
return ctx.OpLoad(ctx.F32[1],
ctx.OpAccessChain(position_comp_ptr, pointer, ctx.ConstU32(comp)));
}
UNREACHABLE();
}
@ -351,6 +343,13 @@ Id EmitGetTessGenericAttribute(EmitContext& ctx, Id vertex_index, Id attr_index,
vertex_index, attr_index, comp_index));
}
Id EmitReadTcsGenericOuputAttribute(EmitContext& ctx, Id vertex_index, Id attr_index,
Id comp_index) {
const auto attr_comp_ptr = ctx.TypePointer(spv::StorageClass::Output, ctx.F32[1]);
return ctx.OpLoad(ctx.F32[1], ctx.OpAccessChain(attr_comp_ptr, ctx.output_attr_array,
vertex_index, attr_index, comp_index));
}
void EmitSetTcsGenericAttribute(EmitContext& ctx, Id value, Id attr_index, Id comp_index) {
// Implied vertex index is invocation_id
const auto component_ptr = ctx.TypePointer(spv::StorageClass::Output, ctx.F32[1]);

View File

@ -238,7 +238,7 @@ Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod
}
texel = ctx.OpImageRead(color_type, image, coords, operands.mask, operands.operands);
}
return !texture.is_integer ? ctx.OpBitcast(ctx.U32[4], texel) : texel;
return texture.is_integer ? ctx.OpBitcast(ctx.F32[4], texel) : texel;
}
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id ms,
@ -253,8 +253,8 @@ void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id
} else if (Sirit::ValidId(lod)) {
LOG_WARNING(Render, "Image write with LOD not supported by driver");
}
ctx.OpImageWrite(image, coords, ctx.OpBitcast(color_type, color), operands.mask,
operands.operands);
const Id texel = texture.is_integer ? ctx.OpBitcast(color_type, color) : color;
ctx.OpImageWrite(image, coords, texel, operands.mask, operands.operands);
}
} // namespace Shader::Backend::SPIRV

View File

@ -89,6 +89,8 @@ Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp);
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 comp);
Id EmitGetTessGenericAttribute(EmitContext& ctx, Id vertex_index, Id attr_index, Id comp_index);
void EmitSetTcsGenericAttribute(EmitContext& ctx, Id value, Id attr_index, Id comp_index);
Id EmitReadTcsGenericOuputAttribute(EmitContext& ctx, Id vertex_index, Id attr_index,
Id comp_index);
Id EmitGetPatch(EmitContext& ctx, IR::Patch patch);
void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value);
void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value);
@ -118,33 +120,48 @@ Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value);
Id EmitCompositeConstructU32x2(EmitContext& ctx, Id e1, Id e2);
Id EmitCompositeConstructU32x3(EmitContext& ctx, Id e1, Id e2, Id e3);
Id EmitCompositeConstructU32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2);
Id EmitCompositeConstructU32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3);
Id EmitCompositeConstructU32x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeExtractU32x2(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractU32x3(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractU32x4(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeInsertU32x2(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertU32x3(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertU32x4(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeConstructF16x2(EmitContext& ctx, Id e1, Id e2);
Id EmitCompositeConstructF16x3(EmitContext& ctx, Id e1, Id e2, Id e3);
Id EmitCompositeConstructF16x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeShuffleU32x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1);
Id EmitCompositeShuffleU32x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2);
Id EmitCompositeShuffleU32x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3);
Id EmitCompositeConstructF16x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2);
Id EmitCompositeConstructF16x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3);
Id EmitCompositeConstructF16x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeExtractF16x2(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractF16x3(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractF16x4(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeInsertF16x2(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF16x3(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF16x4(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeConstructF32x2(EmitContext& ctx, Id e1, Id e2);
Id EmitCompositeConstructF32x3(EmitContext& ctx, Id e1, Id e2, Id e3);
Id EmitCompositeConstructF32x4(EmitContext& ctx, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeShuffleF16x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1);
Id EmitCompositeShuffleF16x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2);
Id EmitCompositeShuffleF16x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3);
Id EmitCompositeConstructF32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2);
Id EmitCompositeConstructF32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3);
Id EmitCompositeConstructF32x4(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3, Id e4);
Id EmitCompositeExtractF32x2(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractF32x3(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeExtractF32x4(EmitContext& ctx, Id composite, u32 index);
Id EmitCompositeInsertF32x2(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF32x3(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF32x4(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeShuffleF32x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1);
Id EmitCompositeShuffleF32x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2);
Id EmitCompositeShuffleF32x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3);
void EmitCompositeConstructF64x2(EmitContext& ctx);
void EmitCompositeConstructF64x3(EmitContext& ctx);
void EmitCompositeConstructF64x4(EmitContext& ctx);
@ -154,6 +171,11 @@ void EmitCompositeExtractF64x4(EmitContext& ctx);
Id EmitCompositeInsertF64x2(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF64x3(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeInsertF64x4(EmitContext& ctx, Id composite, Id object, u32 index);
Id EmitCompositeShuffleF64x2(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1);
Id EmitCompositeShuffleF64x3(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2);
Id EmitCompositeShuffleF64x4(EmitContext& ctx, Id composite1, Id composite2, u32 comp0, u32 comp1,
u32 comp2, u32 comp3);
Id EmitSelectU1(EmitContext& ctx, Id cond, Id true_value, Id false_value);
Id EmitSelectU8(EmitContext& ctx, Id cond, Id true_value, Id false_value);
Id EmitSelectU16(EmitContext& ctx, Id cond, Id true_value, Id false_value);

View File

@ -202,7 +202,14 @@ Id EmitBitCount32(EmitContext& ctx, Id value) {
}
Id EmitBitCount64(EmitContext& ctx, Id value) {
return ctx.OpBitCount(ctx.U64, value);
// Vulkan restricts some bitwise operations to 32-bit only, so decompose into
// two 32-bit values and add the result.
const Id unpacked{ctx.OpBitcast(ctx.U32[2], value)};
const Id lo{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 0U)};
const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)};
const Id lo_count{ctx.OpBitCount(ctx.U32[1], lo)};
const Id hi_count{ctx.OpBitCount(ctx.U32[1], hi)};
return ctx.OpIAdd(ctx.U32[1], lo_count, hi_count);
}
Id EmitBitwiseNot32(EmitContext& ctx, Id value) {
@ -222,7 +229,15 @@ Id EmitFindILsb32(EmitContext& ctx, Id value) {
}
Id EmitFindILsb64(EmitContext& ctx, Id value) {
return ctx.OpFindILsb(ctx.U64, value);
// Vulkan restricts some bitwise operations to 32-bit only, so decompose into
// two 32-bit values and select the correct result.
const Id unpacked{ctx.OpBitcast(ctx.U32[2], value)};
const Id lo{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 0U)};
const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)};
const Id lo_lsb{ctx.OpFindILsb(ctx.U32[1], lo)};
const Id hi_lsb{ctx.OpFindILsb(ctx.U32[1], hi)};
const Id found_lo{ctx.OpINotEqual(ctx.U32[1], lo_lsb, ctx.ConstU32(u32(-1)))};
return ctx.OpSelect(ctx.U32[1], found_lo, lo_lsb, hi_lsb);
}
Id EmitSMin32(EmitContext& ctx, Id a, Id b) {

View File

@ -43,9 +43,11 @@ static constexpr u32 NumVertices(AmdGpu::PrimitiveType type) {
case AmdGpu::PrimitiveType::PointList:
return 1u;
case AmdGpu::PrimitiveType::LineList:
case AmdGpu::PrimitiveType::LineStrip:
return 2u;
case AmdGpu::PrimitiveType::TriangleList:
case AmdGpu::PrimitiveType::TriangleStrip:
case AmdGpu::PrimitiveType::RectList:
return 3u;
case AmdGpu::PrimitiveType::AdjTriangleList:
return 6u;

View File

@ -3565,8 +3565,8 @@ constexpr std::array<InstFormat, 112> InstructionFormatMIMG = {{
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Float32,
ScalarType::Float32},
// 64 = IMAGE_GATHER4
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Float32},
// 65 = IMAGE_GATHER4_CL
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
@ -3603,10 +3603,10 @@ constexpr std::array<InstFormat, 112> InstructionFormatMIMG = {{
ScalarType::Undefined},
// 79 = IMAGE_GATHER4_C_LZ
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Uint32},
ScalarType::Float32},
// 80 = IMAGE_GATHER4_O
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Float32},
// 81 = IMAGE_GATHER4_CL_O
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined},

View File

@ -25,34 +25,28 @@ void Translator::EmitExport(const GcnInst& inst) {
IR::VectorReg(inst.src[3].code),
};
const auto swizzle = [&](u32 comp) {
const auto set_attribute = [&](u32 comp, IR::F32 value) {
if (!IR::IsMrt(attrib)) {
return comp;
ir.SetAttribute(attrib, value, comp);
return;
}
const u32 index = u32(attrib) - u32(IR::Attribute::RenderTarget0);
switch (runtime_info.fs_info.color_buffers[index].mrt_swizzle) {
case MrtSwizzle::Identity:
return comp;
case MrtSwizzle::Alt:
static constexpr std::array<u32, 4> AltSwizzle = {2, 1, 0, 3};
return AltSwizzle[comp];
case MrtSwizzle::Reverse:
static constexpr std::array<u32, 4> RevSwizzle = {3, 2, 1, 0};
return RevSwizzle[comp];
case MrtSwizzle::ReverseAlt:
static constexpr std::array<u32, 4> AltRevSwizzle = {3, 0, 1, 2};
return AltRevSwizzle[comp];
default:
UNREACHABLE();
const auto [r, g, b, a] = runtime_info.fs_info.color_buffers[index].swizzle;
const std::array swizzle_array = {r, g, b, a};
const auto swizzled_comp = swizzle_array[comp];
if (u32(swizzled_comp) < u32(AmdGpu::CompSwizzle::Red)) {
ir.SetAttribute(attrib, value, comp);
return;
}
ir.SetAttribute(attrib, value, u32(swizzled_comp) - u32(AmdGpu::CompSwizzle::Red));
};
const auto unpack = [&](u32 idx) {
const IR::Value value = ir.UnpackHalf2x16(ir.GetVectorReg(vsrc[idx]));
const IR::F32 r = IR::F32{ir.CompositeExtract(value, 0)};
const IR::F32 g = IR::F32{ir.CompositeExtract(value, 1)};
ir.SetAttribute(attrib, r, swizzle(idx * 2));
ir.SetAttribute(attrib, g, swizzle(idx * 2 + 1));
set_attribute(idx * 2, r);
set_attribute(idx * 2 + 1, g);
};
// Components are float16 packed into a VGPR
@ -73,7 +67,7 @@ void Translator::EmitExport(const GcnInst& inst) {
continue;
}
const IR::F32 comp = ir.GetVectorReg<IR::F32>(vsrc[i]);
ir.SetAttribute(attrib, comp, swizzle(i));
set_attribute(i, comp);
}
}
if (IR::IsMrt(attrib)) {

View File

@ -597,14 +597,13 @@ void Translator::S_BCNT1_I32_B64(const GcnInst& inst) {
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))};
const IR::U32 result{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))};
const IR::U32 result{ir.FindILsb(src0)};
SetDst(inst.dst[0], result);
}

View File

@ -10,6 +10,7 @@
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/attribute.h"
#include "shader_recompiler/ir/reg.h"
#include "shader_recompiler/ir/reinterpret.h"
#include "shader_recompiler/runtime_info.h"
#include "video_core/amdgpu/resource.h"
#include "video_core/amdgpu/types.h"
@ -124,12 +125,12 @@ void Translator::EmitPrologue() {
}
break;
case LogicalStage::TessellationControl: {
ir.SetVectorReg(IR::VectorReg::V0, ir.GetAttributeU32(IR::Attribute::PrimitiveId));
// Should be laid out like:
// [0:8]: patch id within VGT
// [8:12]: output control point id
ir.SetVectorReg(IR::VectorReg::V1,
ir.GetAttributeU32(IR::Attribute::PackedHullInvocationInfo));
// TODO PrimitiveId is probably V2 but haven't seen it yet
break;
}
case LogicalStage::TessellationEval:
@ -475,26 +476,12 @@ void Translator::EmitFetch(const GcnInst& inst) {
// Read the V# of the attribute to figure out component number and type.
const auto buffer = info.ReadUdReg<AmdGpu::Buffer>(attrib.sgpr_base, attrib.dword_offset);
const auto values =
ir.CompositeConstruct(ir.GetAttribute(attr, 0), ir.GetAttribute(attr, 1),
ir.GetAttribute(attr, 2), ir.GetAttribute(attr, 3));
const auto swizzled = ApplySwizzle(ir, values, buffer.DstSelect());
for (u32 i = 0; i < 4; i++) {
const IR::F32 comp = [&] {
switch (buffer.GetSwizzle(i)) {
case AmdGpu::CompSwizzle::One:
return ir.Imm32(1.f);
case AmdGpu::CompSwizzle::Zero:
return ir.Imm32(0.f);
case AmdGpu::CompSwizzle::Red:
return ir.GetAttribute(attr, 0);
case AmdGpu::CompSwizzle::Green:
return ir.GetAttribute(attr, 1);
case AmdGpu::CompSwizzle::Blue:
return ir.GetAttribute(attr, 2);
case AmdGpu::CompSwizzle::Alpha:
return ir.GetAttribute(attr, 3);
default:
UNREACHABLE();
}
}();
ir.SetVectorReg(dst_reg++, comp);
ir.SetVectorReg(dst_reg++, IR::F32{ir.CompositeExtract(swizzled, i)});
}
// In case of programmable step rates we need to fallback to instance data pulling in

View File

@ -144,8 +144,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
return IMAGE_SAMPLE(inst);
// Image gather operations
case Opcode::IMAGE_GATHER4:
case Opcode::IMAGE_GATHER4_LZ:
case Opcode::IMAGE_GATHER4_C:
case Opcode::IMAGE_GATHER4_O:
case Opcode::IMAGE_GATHER4_C_O:
case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O:
@ -253,10 +255,6 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_typed, const GcnInst& inst
"Non immediate offset not supported");
}
if (info.stage == Stage::Hull) {
// printf("here\n"); // break
}
IR::Value address = [&] -> IR::Value {
if (is_ring) {
return ir.CompositeConstruct(ir.GetVectorReg(vaddr), soffset);
@ -328,7 +326,7 @@ void Translator::BUFFER_STORE_FORMAT(u32 num_dwords, const GcnInst& inst) {
const IR::VectorReg src_reg{inst.src[1].code};
std::array<IR::Value, 4> comps{};
std::array<IR::F32, 4> comps{};
for (u32 i = 0; i < num_dwords; i++) {
comps[i] = ir.GetVectorReg<IR::F32>(src_reg + i);
}
@ -426,7 +424,7 @@ void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) {
if (((mimg.dmask >> i) & 1) == 0) {
continue;
}
IR::U32 value = IR::U32{ir.CompositeExtract(texel, i)};
IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value);
}
}

View File

@ -288,6 +288,12 @@ void IREmitter::SetTcsGenericAttribute(const F32& value, const U32& attr_index,
Inst(Opcode::SetTcsGenericAttribute, value, attr_index, comp_index);
}
F32 IREmitter::ReadTcsGenericOuputAttribute(const U32& vertex_index, const U32& attr_index,
const U32& comp_index) {
return Inst<F32>(IR::Opcode::ReadTcsGenericOuputAttribute, vertex_index, attr_index,
comp_index);
}
F32 IREmitter::GetPatch(Patch patch) {
return Inst<F32>(Opcode::GetPatch, patch);
}
@ -657,6 +663,86 @@ Value IREmitter::CompositeInsert(const Value& vector, const Value& object, size_
}
}
Value IREmitter::CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1) {
if (vector1.Type() != vector2.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", vector1.Type(), vector2.Type());
}
if (comp0 >= 4 || comp1 >= 4) {
UNREACHABLE_MSG("One or more out of bounds elements {}, {}", comp0, comp1);
}
const auto shuffle{[&](Opcode opcode) -> Value {
return Inst(opcode, vector1, vector2, Value{static_cast<u32>(comp0)},
Value{static_cast<u32>(comp1)});
}};
switch (vector1.Type()) {
case Type::U32x4:
return shuffle(Opcode::CompositeShuffleU32x2);
case Type::F16x4:
return shuffle(Opcode::CompositeShuffleF16x2);
case Type::F32x4:
return shuffle(Opcode::CompositeShuffleF32x2);
case Type::F64x4:
return shuffle(Opcode::CompositeShuffleF64x2);
default:
ThrowInvalidType(vector1.Type());
}
}
Value IREmitter::CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1, size_t comp2) {
if (vector1.Type() != vector2.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", vector1.Type(), vector2.Type());
}
if (comp0 >= 6 || comp1 >= 6 || comp2 >= 6) {
UNREACHABLE_MSG("One or more out of bounds elements {}, {}, {}", comp0, comp1, comp2);
}
const auto shuffle{[&](Opcode opcode) -> Value {
return Inst(opcode, vector1, vector2, Value{static_cast<u32>(comp0)},
Value{static_cast<u32>(comp1)}, Value{static_cast<u32>(comp2)});
}};
switch (vector1.Type()) {
case Type::U32x4:
return shuffle(Opcode::CompositeShuffleU32x3);
case Type::F16x4:
return shuffle(Opcode::CompositeShuffleF16x3);
case Type::F32x4:
return shuffle(Opcode::CompositeShuffleF32x3);
case Type::F64x4:
return shuffle(Opcode::CompositeShuffleF64x3);
default:
ThrowInvalidType(vector1.Type());
}
}
Value IREmitter::CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1, size_t comp2, size_t comp3) {
if (vector1.Type() != vector2.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", vector1.Type(), vector2.Type());
}
if (comp0 >= 8 || comp1 >= 8 || comp2 >= 8 || comp3 >= 8) {
UNREACHABLE_MSG("One or more out of bounds elements {}, {}, {}, {}", comp0, comp1, comp2,
comp3);
}
const auto shuffle{[&](Opcode opcode) -> Value {
return Inst(opcode, vector1, vector2, Value{static_cast<u32>(comp0)},
Value{static_cast<u32>(comp1)}, Value{static_cast<u32>(comp2)},
Value{static_cast<u32>(comp3)});
}};
switch (vector1.Type()) {
case Type::U32x4:
return shuffle(Opcode::CompositeShuffleU32x4);
case Type::F16x4:
return shuffle(Opcode::CompositeShuffleF16x4);
case Type::F32x4:
return shuffle(Opcode::CompositeShuffleF32x4);
case Type::F64x4:
return shuffle(Opcode::CompositeShuffleF64x4);
default:
ThrowInvalidType(vector1.Type());
}
}
Value IREmitter::Select(const U1& condition, const Value& true_value, const Value& false_value) {
if (true_value.Type() != false_value.Type()) {
UNREACHABLE_MSG("Mismatching types {} and {}", true_value.Type(), false_value.Type());

View File

@ -90,6 +90,9 @@ public:
const U32& comp_index);
void SetTcsGenericAttribute(const F32& value, const U32& attr_index, const U32& comp_index);
[[nodiscard]] F32 ReadTcsGenericOuputAttribute(const U32& vertex_index, const U32& attr_index,
const U32& comp_index);
[[nodiscard]] F32 GetPatch(Patch patch);
void SetPatch(Patch patch, const F32& value);
@ -152,6 +155,13 @@ public:
[[nodiscard]] Value CompositeExtract(const Value& vector, size_t element);
[[nodiscard]] Value CompositeInsert(const Value& vector, const Value& object, size_t element);
[[nodiscard]] Value CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1);
[[nodiscard]] Value CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1, size_t comp2);
[[nodiscard]] Value CompositeShuffle(const Value& vector1, const Value& vector2, size_t comp0,
size_t comp1, size_t comp2, size_t comp3);
[[nodiscard]] Value Select(const U1& condition, const Value& true_value,
const Value& false_value);

View File

@ -64,6 +64,8 @@ OPCODE(GetPatch, F32, Patc
OPCODE(SetPatch, Void, Patch, F32, )
OPCODE(GetTessGenericAttribute, F32, U32, U32, U32, )
OPCODE(SetTcsGenericAttribute, Void, F32, U32, U32, )
OPCODE(ReadTcsGenericOuputAttribute, F32, U32, U32, U32, )
// Flags
OPCODE(GetScc, U1, Void, )
@ -97,7 +99,7 @@ OPCODE(StoreBufferU32, Void, Opaq
OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, )
OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, )
OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, )
OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, U32x4, )
OPCODE(StoreBufferFormatF32, Void, Opaque, Opaque, F32x4, )
// Buffer atomic operations
OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 )
@ -122,6 +124,9 @@ OPCODE(CompositeExtractU32x4, U32, U32x
OPCODE(CompositeInsertU32x2, U32x2, U32x2, U32, U32, )
OPCODE(CompositeInsertU32x3, U32x3, U32x3, U32, U32, )
OPCODE(CompositeInsertU32x4, U32x4, U32x4, U32, U32, )
OPCODE(CompositeShuffleU32x2, U32x2, U32x2, U32x2, U32, U32, )
OPCODE(CompositeShuffleU32x3, U32x3, U32x3, U32x3, U32, U32, U32, )
OPCODE(CompositeShuffleU32x4, U32x4, U32x4, U32x4, U32, U32, U32, U32, )
OPCODE(CompositeConstructF16x2, F16x2, F16, F16, )
OPCODE(CompositeConstructF16x3, F16x3, F16, F16, F16, )
OPCODE(CompositeConstructF16x4, F16x4, F16, F16, F16, F16, )
@ -131,6 +136,9 @@ OPCODE(CompositeExtractF16x4, F16, F16x
OPCODE(CompositeInsertF16x2, F16x2, F16x2, F16, U32, )
OPCODE(CompositeInsertF16x3, F16x3, F16x3, F16, U32, )
OPCODE(CompositeInsertF16x4, F16x4, F16x4, F16, U32, )
OPCODE(CompositeShuffleF16x2, F16x2, F16x2, F16x2, U32, U32, )
OPCODE(CompositeShuffleF16x3, F16x3, F16x3, F16x3, U32, U32, U32, )
OPCODE(CompositeShuffleF16x4, F16x4, F16x4, F16x4, U32, U32, U32, U32, )
OPCODE(CompositeConstructF32x2, F32x2, F32, F32, )
OPCODE(CompositeConstructF32x3, F32x3, F32, F32, F32, )
OPCODE(CompositeConstructF32x4, F32x4, F32, F32, F32, F32, )
@ -140,6 +148,9 @@ OPCODE(CompositeExtractF32x4, F32, F32x
OPCODE(CompositeInsertF32x2, F32x2, F32x2, F32, U32, )
OPCODE(CompositeInsertF32x3, F32x3, F32x3, F32, U32, )
OPCODE(CompositeInsertF32x4, F32x4, F32x4, F32, U32, )
OPCODE(CompositeShuffleF32x2, F32x2, F32x2, F32x2, U32, U32, )
OPCODE(CompositeShuffleF32x3, F32x3, F32x3, F32x3, U32, U32, U32, )
OPCODE(CompositeShuffleF32x4, F32x4, F32x4, F32x4, U32, U32, U32, U32, )
OPCODE(CompositeConstructF64x2, F64x2, F64, F64, )
OPCODE(CompositeConstructF64x3, F64x3, F64, F64, F64, )
OPCODE(CompositeConstructF64x4, F64x4, F64, F64, F64, F64, )
@ -149,6 +160,9 @@ OPCODE(CompositeExtractF64x4, F64, F64x
OPCODE(CompositeInsertF64x2, F64x2, F64x2, F64, U32, )
OPCODE(CompositeInsertF64x3, F64x3, F64x3, F64, U32, )
OPCODE(CompositeInsertF64x4, F64x4, F64x4, F64, U32, )
OPCODE(CompositeShuffleF64x2, F64x2, F64x2, F64x2, U32, U32, )
OPCODE(CompositeShuffleF64x3, F64x3, F64x3, F64x3, U32, U32, U32, )
OPCODE(CompositeShuffleF64x4, F64x4, F64x4, F64x4, U32, U32, U32, U32, )
// Select operations
OPCODE(SelectU1, U1, U1, U1, U1, )
@ -344,8 +358,8 @@ OPCODE(ImageGatherDref, F32x4, Opaq
OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, )
OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, )
OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, F32, )
OPCODE(ImageRead, U32x4, Opaque, Opaque, U32, U32, )
OPCODE(ImageWrite, Void, Opaque, Opaque, U32, U32, U32x4, )
OPCODE(ImageRead, F32x4, Opaque, Opaque, U32, U32, )
OPCODE(ImageWrite, Void, Opaque, Opaque, U32, U32, F32x4, )
// Image atomic operations
OPCODE(ImageAtomicIAdd32, U32, Opaque, Opaque, U32, )

View File

@ -343,8 +343,8 @@ static IR::U32 TryOptimizeAddressModulo(IR::U32 addr, u32 stride, IR::IREmitter&
// TODO: can optimize div in control point index similarly to mod
// Read a TCS input (InputCP region) or TES input (OutputCP region)
static IR::F32 ReadTessInputComponent(IR::U32 addr, const u32 stride, IR::IREmitter& ir,
u32 off_dw) {
static IR::F32 ReadTessControlPointAttribute(IR::U32 addr, const u32 stride, IR::IREmitter& ir,
u32 off_dw, bool is_output_read_in_tcs) {
if (off_dw > 0) {
addr = ir.IAdd(addr, ir.Imm32(off_dw));
}
@ -354,7 +354,11 @@ static IR::F32 ReadTessInputComponent(IR::U32 addr, const u32 stride, IR::IREmit
ir.ShiftRightLogical(ir.IMod(addr_for_attrs, ir.Imm32(stride)), ir.Imm32(4u));
const IR::U32 comp_index =
ir.ShiftRightLogical(ir.BitwiseAnd(addr_for_attrs, ir.Imm32(0xFU)), ir.Imm32(2u));
return ir.GetTessGenericAttribute(control_point_index, attr_index, comp_index);
if (is_output_read_in_tcs) {
return ir.ReadTcsGenericOuputAttribute(control_point_index, attr_index, comp_index);
} else {
return ir.GetTessGenericAttribute(control_point_index, attr_index, comp_index);
}
}
} // namespace
@ -481,21 +485,25 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
case IR::Opcode::LoadSharedU128:
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
const IR::U32 addr{inst.Arg(0)};
AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info);
const AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info);
const u32 num_dwords = opcode == IR::Opcode::LoadSharedU32
? 1
: (opcode == IR::Opcode::LoadSharedU64 ? 2 : 4);
ASSERT_MSG(region == AttributeRegion::InputCP,
"Unhandled read of output or patchconst attribute in hull shader");
ASSERT_MSG(region == AttributeRegion::InputCP ||
region == AttributeRegion::OutputCP,
"Unhandled read of patchconst attribute in hull shader");
const bool is_tcs_output_read = region == AttributeRegion::OutputCP;
const u32 stride = is_tcs_output_read ? runtime_info.hs_info.hs_output_cp_stride
: runtime_info.hs_info.ls_stride;
IR::Value attr_read;
if (num_dwords == 1) {
attr_read = ir.BitCast<IR::U32>(
ReadTessInputComponent(addr, runtime_info.hs_info.ls_stride, ir, 0));
ReadTessControlPointAttribute(addr, stride, ir, 0, is_tcs_output_read));
} else {
boost::container::static_vector<IR::Value, 4> read_components;
for (auto i = 0; i < num_dwords; i++) {
const IR::F32 component =
ReadTessInputComponent(addr, runtime_info.hs_info.ls_stride, ir, i);
ReadTessControlPointAttribute(addr, stride, ir, i, is_tcs_output_read);
read_components.push_back(ir.BitCast<IR::U32>(component));
}
attr_read = ir.CompositeConstruct(read_components);
@ -565,8 +573,8 @@ void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
: (opcode == IR::Opcode::LoadSharedU64 ? 2 : 4);
const auto GetInput = [&](IR::U32 addr, u32 off_dw) -> IR::F32 {
if (region == AttributeRegion::OutputCP) {
return ReadTessInputComponent(
addr, runtime_info.vs_info.hs_output_cp_stride, ir, off_dw);
return ReadTessControlPointAttribute(
addr, runtime_info.vs_info.hs_output_cp_stride, ir, off_dw, false);
} else {
ASSERT(region == AttributeRegion::PatchConst);
return ir.GetPatch(IR::PatchGeneric((addr.U32() >> 2) + off_dw));

View File

@ -8,6 +8,7 @@
#include "shader_recompiler/ir/breadth_first_search.h"
#include "shader_recompiler/ir/ir_emitter.h"
#include "shader_recompiler/ir/program.h"
#include "shader_recompiler/ir/reinterpret.h"
#include "video_core/amdgpu/resource.h"
namespace Shader::Optimization {
@ -128,35 +129,6 @@ bool IsImageInstruction(const IR::Inst& inst) {
}
}
IR::Value SwizzleVector(IR::IREmitter& ir, auto sharp, IR::Value texel) {
boost::container::static_vector<IR::Value, 4> comps;
for (u32 i = 0; i < 4; i++) {
switch (sharp.GetSwizzle(i)) {
case AmdGpu::CompSwizzle::Zero:
comps.emplace_back(ir.Imm32(0.f));
break;
case AmdGpu::CompSwizzle::One:
comps.emplace_back(ir.Imm32(1.f));
break;
case AmdGpu::CompSwizzle::Red:
comps.emplace_back(ir.CompositeExtract(texel, 0));
break;
case AmdGpu::CompSwizzle::Green:
comps.emplace_back(ir.CompositeExtract(texel, 1));
break;
case AmdGpu::CompSwizzle::Blue:
comps.emplace_back(ir.CompositeExtract(texel, 2));
break;
case AmdGpu::CompSwizzle::Alpha:
comps.emplace_back(ir.CompositeExtract(texel, 3));
break;
default:
UNREACHABLE();
}
}
return ir.CompositeConstruct(comps[0], comps[1], comps[2], comps[3]);
};
class Descriptors {
public:
explicit Descriptors(Info& info_)
@ -409,15 +381,6 @@ void PatchTextureBufferInstruction(IR::Block& block, IR::Inst& inst, Info& info,
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
inst.SetArg(0, ir.Imm32(binding));
ASSERT(!buffer.swizzle_enable && !buffer.add_tid_enable);
// Apply dst_sel swizzle on formatted buffer instructions
if (inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32) {
inst.SetArg(2, SwizzleVector(ir, buffer, inst.Arg(2)));
} else {
const auto inst_info = inst.Flags<IR::BufferInstInfo>();
const auto texel = ir.LoadBufferFormat(inst.Arg(0), inst.Arg(1), inst_info);
inst.ReplaceUsesWith(SwizzleVector(ir, buffer, texel));
}
}
IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value& t,
@ -765,10 +728,6 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
}();
inst.SetArg(1, coords);
if (inst.GetOpcode() == IR::Opcode::ImageWrite) {
inst.SetArg(4, SwizzleVector(ir, image, inst.Arg(4)));
}
if (inst_info.has_lod) {
ASSERT(inst.GetOpcode() == IR::Opcode::ImageRead ||
inst.GetOpcode() == IR::Opcode::ImageWrite);
@ -783,6 +742,50 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
}
}
void PatchTextureBufferInterpretation(IR::Block& block, IR::Inst& inst, Info& info) {
const auto binding = inst.Arg(0).U32();
const auto buffer_res = info.texture_buffers[binding];
const auto buffer = buffer_res.GetSharp(info);
if (!buffer.Valid()) {
// Don't need to swizzle invalid buffer.
return;
}
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
if (inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32) {
inst.SetArg(2, ApplySwizzle(ir, inst.Arg(2), buffer.DstSelect()));
} else if (inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32) {
const auto inst_info = inst.Flags<IR::BufferInstInfo>();
const auto texel = ir.LoadBufferFormat(inst.Arg(0), inst.Arg(1), inst_info);
const auto swizzled = ApplySwizzle(ir, texel, buffer.DstSelect());
inst.ReplaceUsesWith(swizzled);
}
}
void PatchImageInterpretation(IR::Block& block, IR::Inst& inst, Info& info) {
const auto binding = inst.Arg(0).U32();
const auto image_res = info.images[binding & 0xFFFF];
const auto image = image_res.GetSharp(info);
if (!image.Valid() || !image_res.IsStorage(image)) {
// Don't need to swizzle invalid or non-storage image.
return;
}
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
if (inst.GetOpcode() == IR::Opcode::ImageWrite) {
inst.SetArg(4, ApplySwizzle(ir, inst.Arg(4), image.DstSelect()));
} else if (inst.GetOpcode() == IR::Opcode::ImageRead) {
const auto inst_info = inst.Flags<IR::TextureInstInfo>();
const auto lod = inst.Arg(2);
const auto ms = inst.Arg(3);
const auto texel =
ir.ImageRead(inst.Arg(0), inst.Arg(1), lod.IsEmpty() ? IR::U32{} : IR::U32{lod},
ms.IsEmpty() ? IR::U32{} : IR::U32{ms}, inst_info);
const auto swizzled = ApplySwizzle(ir, texel, image.DstSelect());
inst.ReplaceUsesWith(swizzled);
}
}
void PatchDataRingInstruction(IR::Block& block, IR::Inst& inst, Info& info,
Descriptors& descriptors) {
// Insert gds binding in the shader if it doesn't exist already.
@ -852,6 +855,19 @@ void ResourceTrackingPass(IR::Program& program) {
}
}
}
// Second pass to reinterpret format read/write where needed, since we now know
// the bindings and their properties.
for (IR::Block* const block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) {
if (IsTextureBufferInstruction(inst)) {
PatchTextureBufferInterpretation(*block, inst, info);
continue;
}
if (IsImageInstruction(inst)) {
PatchImageInterpretation(*block, inst, info);
}
}
}
}
} // namespace Shader::Optimization

View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "shader_recompiler/ir/ir_emitter.h"
#include "video_core/amdgpu/resource.h"
namespace Shader::IR {
/// Applies a component swizzle to a vec4.
inline Value ApplySwizzle(IREmitter& ir, const Value& vector, const AmdGpu::CompMapping& swizzle) {
// Constants are indexed as 0 and 1, and components are 4-7. Thus we can apply a swizzle
// using two vectors and a shuffle, using one vector of constants and one of the components.
const auto zero = ir.Imm32(0.f);
const auto one = ir.Imm32(1.f);
const auto constants_vec = ir.CompositeConstruct(zero, one, zero, zero);
const auto swizzled =
ir.CompositeShuffle(constants_vec, vector, size_t(swizzle.r), size_t(swizzle.g),
size_t(swizzle.b), size_t(swizzle.a));
return swizzled;
}
} // namespace Shader::IR

View File

@ -180,7 +180,7 @@ struct FragmentRuntimeInfo {
std::array<PsInput, 32> inputs;
struct PsColorBuffer {
AmdGpu::NumberFormat num_format;
MrtSwizzle mrt_swizzle;
AmdGpu::CompMapping swizzle;
auto operator<=>(const PsColorBuffer&) const noexcept = default;
};

View File

@ -31,7 +31,7 @@ struct BufferSpecialization {
struct TextureBufferSpecialization {
bool is_integer = false;
u32 dst_select = 0;
AmdGpu::CompMapping dst_select{};
auto operator<=>(const TextureBufferSpecialization&) const = default;
};
@ -40,13 +40,9 @@ struct ImageSpecialization {
AmdGpu::ImageType type = AmdGpu::ImageType::Color2D;
bool is_integer = false;
bool is_storage = false;
u32 dst_select = 0;
AmdGpu::CompMapping dst_select{};
bool operator==(const ImageSpecialization& other) const {
return type == other.type && is_integer == other.is_integer &&
is_storage == other.is_storage &&
(dst_select != 0 ? dst_select == other.dst_select : true);
}
auto operator<=>(const ImageSpecialization&) const = default;
};
struct FMaskSpecialization {

View File

@ -815,12 +815,30 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
}
if (rasterizer && (cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address));
rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:DispatchIndirect", vqid, cmd_address));
rasterizer->DispatchDirect();
rasterizer->ScopeMarkerEnd();
}
break;
}
case PM4ItOpcode::DispatchIndirect: {
const auto* dispatch_indirect = reinterpret_cast<const PM4CmdDispatchIndirect*>(header);
auto& cs_program = GetCsRegs();
const auto offset = dispatch_indirect->data_offset;
const auto ib_address = mapped_queues[vqid].indirect_args_addr;
const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions);
if (DebugState.DumpingCurrentReg()) {
DebugState.PushRegsDumpCompute(base_addr, reinterpret_cast<uintptr_t>(header),
cs_program);
}
if (rasterizer && (cs_program.dispatch_initiator & 1)) {
const auto cmd_address = reinterpret_cast<const void*>(header);
rasterizer->ScopeMarkerBegin(fmt::format("acb[{}]:{}:Dispatch", vqid, cmd_address));
rasterizer->DispatchIndirect(ib_address, offset, size);
rasterizer->ScopeMarkerEnd();
}
break;
}
case PM4ItOpcode::WriteData: {
const auto* write_data = reinterpret_cast<const PM4CmdWriteData*>(header);
ASSERT(write_data->dst_sel.Value() == 2 || write_data->dst_sel.Value() == 5);
@ -845,6 +863,10 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
release_mem->SignalFence(static_cast<Platform::InterruptId>(queue.pipe_id));
break;
}
case PM4ItOpcode::EventWrite: {
// const auto* event = reinterpret_cast<const PM4CmdEventWrite*>(header);
break;
}
default:
UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}",
static_cast<u32>(opcode), count);

View File

@ -889,10 +889,54 @@ struct Liverpool {
return !info.linear_general;
}
NumberFormat NumFormat() const {
[[nodiscard]] DataFormat DataFormat() const {
return RemapDataFormat(info.format);
}
[[nodiscard]] NumberFormat NumFormat() const {
// There is a small difference between T# and CB number types, account for it.
return info.number_type == AmdGpu::NumberFormat::SnormNz ? AmdGpu::NumberFormat::Srgb
: info.number_type.Value();
return RemapNumberFormat(info.number_type == NumberFormat::SnormNz
? NumberFormat::Srgb
: info.number_type.Value());
}
[[nodiscard]] CompMapping Swizzle() const {
// clang-format off
static constexpr std::array<std::array<CompMapping, 4>, 4> mrt_swizzles{{
// Standard
std::array<CompMapping, 4>{{
{.r = CompSwizzle::Red, .g = CompSwizzle::Zero, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Red, .g = CompSwizzle::Green, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Red, .g = CompSwizzle::Green, .b = CompSwizzle::Blue, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Red, .g = CompSwizzle::Green, .b = CompSwizzle::Blue, .a = CompSwizzle::Alpha},
}},
// Alternate
std::array<CompMapping, 4>{{
{.r = CompSwizzle::Green, .g = CompSwizzle::Zero, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Red, .g = CompSwizzle::Alpha, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Red, .g = CompSwizzle::Green, .b = CompSwizzle::Alpha, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Blue, .g = CompSwizzle::Green, .b = CompSwizzle::Red, .a = CompSwizzle::Alpha},
}},
// StandardReverse
std::array<CompMapping, 4>{{
{.r = CompSwizzle::Blue, .g = CompSwizzle::Zero, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Green, .g = CompSwizzle::Red, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Blue, .g = CompSwizzle::Green, .b = CompSwizzle::Red, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Alpha, .g = CompSwizzle::Blue, .b = CompSwizzle::Green, .a = CompSwizzle::Red},
}},
// AlternateReverse
std::array<CompMapping, 4>{{
{.r = CompSwizzle::Alpha, .g = CompSwizzle::Zero, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Alpha, .g = CompSwizzle::Red, .b = CompSwizzle::Zero, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Alpha, .g = CompSwizzle::Green, .b = CompSwizzle::Red, .a = CompSwizzle::Zero},
{.r = CompSwizzle::Alpha, .g = CompSwizzle::Red, .b = CompSwizzle::Green, .a = CompSwizzle::Blue},
}},
}};
// clang-format on
const auto swap_idx = static_cast<u32>(info.comp_swap.Value());
const auto components_idx = NumComponents(info.format) - 1;
const auto mrt_swizzle = mrt_swizzles[swap_idx][components_idx];
return RemapComponents(info.format, mrt_swizzle);
}
};

View File

@ -20,6 +20,85 @@ enum class CompSwizzle : u32 {
Alpha = 7,
};
struct CompMapping {
CompSwizzle r : 3;
CompSwizzle g : 3;
CompSwizzle b : 3;
CompSwizzle a : 3;
auto operator<=>(const CompMapping& other) const = default;
template <typename T>
[[nodiscard]] std::array<T, 4> Apply(const std::array<T, 4>& data) const {
return {
ApplySingle(data, r),
ApplySingle(data, g),
ApplySingle(data, b),
ApplySingle(data, a),
};
}
private:
template <typename T>
T ApplySingle(const std::array<T, 4>& data, const CompSwizzle swizzle) const {
switch (swizzle) {
case CompSwizzle::Zero:
return T(0);
case CompSwizzle::One:
return T(1);
case CompSwizzle::Red:
return data[0];
case CompSwizzle::Green:
return data[1];
case CompSwizzle::Blue:
return data[2];
case CompSwizzle::Alpha:
return data[3];
default:
UNREACHABLE();
}
}
};
inline DataFormat RemapDataFormat(const DataFormat format) {
switch (format) {
case DataFormat::Format11_11_10:
return DataFormat::Format10_11_11;
case DataFormat::Format10_10_10_2:
return DataFormat::Format2_10_10_10;
case DataFormat::Format5_5_5_1:
return DataFormat::Format1_5_5_5;
default:
return format;
}
}
inline NumberFormat RemapNumberFormat(const NumberFormat format) {
return format;
}
inline CompMapping RemapComponents(const DataFormat format, const CompMapping components) {
switch (format) {
case DataFormat::Format11_11_10:
return {
.r = components.b,
.g = components.g,
.b = components.r,
.a = components.a,
};
case DataFormat::Format10_10_10_2:
case DataFormat::Format5_5_5_1:
return {
.r = components.a,
.g = components.b,
.b = components.g,
.a = components.r,
};
default:
return components;
}
}
// Table 8.5 Buffer Resource Descriptor [Sea Islands Series Instruction Set Architecture]
struct Buffer {
u64 base_address : 44;
@ -52,21 +131,22 @@ struct Buffer {
return std::memcmp(this, &other, sizeof(Buffer)) == 0;
}
u32 DstSelect() const {
return dst_sel_x | (dst_sel_y << 3) | (dst_sel_z << 6) | (dst_sel_w << 9);
}
CompSwizzle GetSwizzle(u32 comp) const noexcept {
const std::array select{dst_sel_x, dst_sel_y, dst_sel_z, dst_sel_w};
return static_cast<CompSwizzle>(select[comp]);
CompMapping DstSelect() const {
const CompMapping dst_sel{
.r = CompSwizzle(dst_sel_x),
.g = CompSwizzle(dst_sel_y),
.b = CompSwizzle(dst_sel_z),
.a = CompSwizzle(dst_sel_w),
};
return RemapComponents(DataFormat(data_format), dst_sel);
}
NumberFormat GetNumberFmt() const noexcept {
return static_cast<NumberFormat>(num_format);
return RemapNumberFormat(NumberFormat(num_format));
}
DataFormat GetDataFmt() const noexcept {
return static_cast<DataFormat>(data_format);
return RemapDataFormat(DataFormat(data_format));
}
u32 GetStride() const noexcept {
@ -186,10 +266,11 @@ struct Image {
static constexpr Image Null() {
Image image{};
image.data_format = u64(DataFormat::Format8_8_8_8);
image.dst_sel_x = 4;
image.dst_sel_y = 5;
image.dst_sel_z = 6;
image.dst_sel_w = 7;
image.num_format = u64(NumberFormat::Unorm);
image.dst_sel_x = u64(CompSwizzle::Red);
image.dst_sel_y = u64(CompSwizzle::Green);
image.dst_sel_z = u64(CompSwizzle::Blue);
image.dst_sel_w = u64(CompSwizzle::Alpha);
image.tiling_index = u64(TilingMode::Texture_MicroTiled);
image.type = u64(ImageType::Color2D);
return image;
@ -207,43 +288,14 @@ struct Image {
return base_address != 0;
}
u32 DstSelect() const {
return dst_sel_x | (dst_sel_y << 3) | (dst_sel_z << 6) | (dst_sel_w << 9);
}
CompSwizzle GetSwizzle(u32 comp) const noexcept {
const std::array select{dst_sel_x, dst_sel_y, dst_sel_z, dst_sel_w};
return static_cast<CompSwizzle>(select[comp]);
}
static char SelectComp(u32 sel) {
switch (sel) {
case 0:
return '0';
case 1:
return '1';
case 4:
return 'R';
case 5:
return 'G';
case 6:
return 'B';
case 7:
return 'A';
default:
UNREACHABLE();
}
}
std::string DstSelectName() const {
std::string result = "[";
u32 dst_sel = DstSelect();
for (u32 i = 0; i < 4; i++) {
result += SelectComp(dst_sel & 7);
dst_sel >>= 3;
}
result += ']';
return result;
CompMapping DstSelect() const {
const CompMapping dst_sel{
.r = CompSwizzle(dst_sel_x),
.g = CompSwizzle(dst_sel_y),
.b = CompSwizzle(dst_sel_z),
.a = CompSwizzle(dst_sel_w),
};
return RemapComponents(DataFormat(data_format), dst_sel);
}
u32 Pitch() const {
@ -285,11 +337,11 @@ struct Image {
}
DataFormat GetDataFmt() const noexcept {
return static_cast<DataFormat>(data_format);
return RemapDataFormat(DataFormat(data_format));
}
NumberFormat GetNumberFmt() const noexcept {
return static_cast<NumberFormat>(num_format);
return RemapNumberFormat(NumberFormat(num_format));
}
TilingMode GetTilingMode() const {
@ -447,7 +499,7 @@ struct Sampler {
}
float MaxAniso() const {
switch (max_aniso) {
switch (max_aniso.Value()) {
case AnisoRatio::One:
return 1.0f;
case AnisoRatio::Two:

View File

@ -234,46 +234,22 @@ bool BufferCache::BindVertexBuffers(
return has_step_rate;
}
u32 BufferCache::BindIndexBuffer(bool& is_indexed, u32 index_offset) {
// Emulate QuadList and Polygon primitive types with CPU made index buffer.
void BufferCache::BindIndexBuffer(u32 index_offset) {
const auto& regs = liverpool->regs;
if (!is_indexed) {
if (regs.primitive_type != AmdGpu::PrimitiveType::Polygon) {
return regs.num_indices;
}
// Emit indices.
const u32 index_size = 3 * regs.num_indices;
const auto [data, offset] = stream_buffer.Map(index_size);
Vulkan::LiverpoolToVK::EmitPolygonToTriangleListIndices(data, regs.num_indices);
stream_buffer.Commit();
// Bind index buffer.
is_indexed = true;
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.bindIndexBuffer(stream_buffer.Handle(), offset, vk::IndexType::eUint16);
return index_size / sizeof(u16);
}
// Figure out index type and size.
const bool is_index16 =
regs.index_buffer_type.index_type == AmdGpu::Liverpool::IndexType::Index16;
const vk::IndexType index_type = is_index16 ? vk::IndexType::eUint16 : vk::IndexType::eUint32;
const u32 index_size = is_index16 ? sizeof(u16) : sizeof(u32);
VAddr index_address = regs.index_base_address.Address<VAddr>();
index_address += index_offset * index_size;
if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) {
UNREACHABLE();
}
const VAddr index_address =
regs.index_base_address.Address<VAddr>() + index_offset * index_size;
// Bind index buffer.
const u32 index_buffer_size = regs.num_indices * index_size;
const auto [vk_buffer, offset] = ObtainBuffer(index_address, index_buffer_size, false);
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.bindIndexBuffer(vk_buffer->Handle(), offset, index_type);
return regs.num_indices;
}
void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {

View File

@ -83,7 +83,7 @@ public:
const std::optional<Shader::Gcn::FetchShaderData>& fetch_shader);
/// Bind host index buffer for the current draw.
u32 BindIndexBuffer(bool& is_indexed, u32 index_offset);
void BindIndexBuffer(u32 index_offset);
/// Writes a value to GPU buffer.
void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds);

View File

@ -20,7 +20,7 @@ layout(push_constant) uniform image_info {
} info;
// Inverse morton LUT, small enough to fit into K$
uint rmort[16] = {
const uint rmort[16] = {
0x11011000, 0x31213020,
0x13031202, 0x33233222,
0x51415040, 0x71617060,

View File

@ -20,7 +20,7 @@ layout(push_constant) uniform image_info {
} info;
// Inverse morton LUT, small enough to fit into K$
uint rmort[16] = {
const uint rmort[16] = {
0x11011000, 0x31213020,
0x13031202, 0x33233222,
0x51415040, 0x71617060,

View File

@ -20,7 +20,7 @@ layout(push_constant) uniform image_info {
} info;
// Inverse morton LUT, small enough to fit into K$
uint rmort[16] = {
const uint rmort[16] = {
0x11011000, 0x31213020,
0x13031202, 0x33233222,
0x51415040, 0x71617060,

View File

@ -25,7 +25,7 @@ layout(push_constant) uniform image_info {
#define TEXELS_PER_ELEMENT 2
// Inverse morton LUT, small enough to fit into K$
uint rmort[16] = {
const uint rmort[16] = {
0x11011000, 0x31213020,
0x13031202, 0x33233222,
0x51415040, 0x71617060,

View File

@ -21,46 +21,46 @@ layout(push_constant) uniform image_info {
} info;
// Each LUT is 64 bytes, so should fit into K$ given tiled slices locality
const uint lut_32bpp[][64] = {
const uint lut_32bpp[][16] = {
{
0x00, 0x01, 0x04, 0x05, 0x40, 0x41, 0x44, 0x45,
0x02, 0x03, 0x06, 0x07, 0x42, 0x43, 0x46, 0x47,
0x10, 0x11, 0x14, 0x15, 0x50, 0x51, 0x54, 0x55,
0x12, 0x13, 0x16, 0x17, 0x52, 0x53, 0x56, 0x57,
0x80, 0x81, 0x84, 0x85, 0xc0, 0xc1, 0xc4, 0xc5,
0x82, 0x83, 0x86, 0x87, 0xc2, 0xc3, 0xc6, 0xc7,
0x90, 0x91, 0x94, 0x95, 0xd0, 0xd1, 0xd4, 0xd5,
0x92, 0x93, 0x96, 0x97, 0xd2, 0xd3, 0xd6, 0xd7,
0x05040100, 0x45444140,
0x07060302, 0x47464342,
0x15141110, 0x55545150,
0x17161312, 0x57565352,
0x85848180, 0xc5c4c1c0,
0x87868382, 0xc7c6c3c2,
0x95949190, 0xd5d4d1d0,
0x97969392, 0xd7d6d3d2,
},
{
0x08, 0x09, 0x0c, 0x0d, 0x48, 0x49, 0x4c, 0x4d,
0x0a, 0x0b, 0x0e, 0x0f, 0x4a, 0x4b, 0x4e, 0x4f,
0x18, 0x19, 0x1c, 0x1d, 0x58, 0x59, 0x5c, 0x5d,
0x1a, 0x1b, 0x1e, 0x1f, 0x5a, 0x5b, 0x5e, 0x5f,
0x88, 0x89, 0x8c, 0x8d, 0xc8, 0xc9, 0xcc, 0xcd,
0x8a, 0x8b, 0x8e, 0x8f, 0xca, 0xcb, 0xce, 0xcf,
0x98, 0x99, 0x9c, 0x9d, 0xd8, 0xd9, 0xdc, 0xdd,
0x9a, 0x9b, 0x9e, 0x9f, 0xda, 0xdb, 0xde, 0xdf,
0x0d0c0908, 0x4d4c4948,
0x0f0e0b0a, 0x4f4e4b4a,
0x1d1c1918, 0x5d5c5958,
0x1f1e1b1a, 0x5f5e5b5a,
0x8d8c8988, 0xcdccc9c8,
0x8f8e8b8a, 0xcfcecbca,
0x9d9c9998, 0xdddcd9d8,
0x9f9e9b9a, 0xdfdedbda,
},
{
0x20, 0x21, 0x24, 0x25, 0x60, 0x61, 0x64, 0x65,
0x22, 0x23, 0x26, 0x27, 0x62, 0x63, 0x66, 0x67,
0x30, 0x31, 0x34, 0x35, 0x70, 0x71, 0x74, 0x75,
0x32, 0x33, 0x36, 0x37, 0x72, 0x73, 0x76, 0x77,
0xa0, 0xa1, 0xa4, 0xa5, 0xe0, 0xe1, 0xe4, 0xe5,
0xa2, 0xa3, 0xa6, 0xa7, 0xe2, 0xe3, 0xe6, 0xe7,
0xb0, 0xb1, 0xb4, 0xb5, 0xf0, 0xf1, 0xf4, 0xf5,
0xb2, 0xb3, 0xb6, 0xb7, 0xf2, 0xf3, 0xf6, 0xf7,
0x25242120, 0x65646160,
0x27262322, 0x67666362,
0x35343130, 0x75747170,
0x37363332, 0x77767372,
0xa5a4a1a0, 0xe5e4e1e0,
0xa7a6a3a2, 0xe7e6e3e2,
0xb5b4b1b0, 0xf5f4f1f0,
0xb7b6b3b2, 0xf7f6f3f2,
},
{
0x28, 0x29, 0x2c, 0x2d, 0x68, 0x69, 0x6c, 0x6d,
0x2a, 0x2b, 0x2e, 0x2f, 0x6a, 0x6b, 0x6e, 0x6f,
0x38, 0x39, 0x3c, 0x3d, 0x78, 0x79, 0x7c, 0x7d,
0x3a, 0x3b, 0x3e, 0x3f, 0x7a, 0x7b, 0x7e, 0x7f,
0xa8, 0xa9, 0xac, 0xad, 0xe8, 0xe9, 0xec, 0xed,
0xaa, 0xab, 0xae, 0xaf, 0xea, 0xeb, 0xee, 0xef,
0xb8, 0xb9, 0xbc, 0xbd, 0xf8, 0xf9, 0xfc, 0xfd,
0xba, 0xbb, 0xbe, 0xbf, 0xfa, 0xfb, 0xfe, 0xff,
0x2d2c2928, 0x6d6c6968,
0x2f2e2b2a, 0x6f6e6b6a,
0x3d3c3938, 0x7d7c7978,
0x3f3e3b3a, 0x7f7e7b7a,
0xadaca9a8, 0xedece9e8,
0xafaeabaa, 0xefeeebea,
0xbdbcb9b8, 0xfdfcf9f8,
0xbfbebbba, 0xfffefbfa,
}
};
@ -77,7 +77,9 @@ void main() {
uint col = bitfieldExtract(x, 0, 3);
uint row = bitfieldExtract(y, 0, 3);
uint lut = bitfieldExtract(z, 0, 2);
uint idx = lut_32bpp[lut][col + row * MICRO_TILE_DIM];
uint idx_dw = lut_32bpp[lut][(col + row * MICRO_TILE_DIM) >> 2u];
uint byte_ofs = gl_LocalInvocationID.x & 3u;
uint idx = bitfieldExtract(idx_dw >> (8 * byte_ofs), 0, 8);
uint slice_offs = (z >> 2u) * info.c1 * MICRO_TILE_SZ;
uint tile_row = y / MICRO_TILE_DIM;

View File

@ -20,46 +20,46 @@ layout(push_constant) uniform image_info {
uint c1;
} info;
const uint lut_64bpp[][64] = {
const uint lut_64bpp[][16] = {
{
0x00, 0x01, 0x08, 0x09, 0x40, 0x41, 0x48, 0x49,
0x02, 0x03, 0x0a, 0x0b, 0x42, 0x43, 0x4a, 0x4b,
0x10, 0x11, 0x18, 0x19, 0x50, 0x51, 0x58, 0x59,
0x12, 0x13, 0x1a, 0x1b, 0x52, 0x53, 0x5a, 0x5b,
0x80, 0x81, 0x88, 0x89, 0xc0, 0xc1, 0xc8, 0xc9,
0x82, 0x83, 0x8a, 0x8b, 0xc2, 0xc3, 0xca, 0xcb,
0x90, 0x91, 0x98, 0x99, 0xd0, 0xd1, 0xd8, 0xd9,
0x92, 0x93, 0x9a, 0x9b, 0xd2, 0xd3, 0xda, 0xdb,
0x09080100, 0x49484140,
0x0b0a0302, 0x4a4b4342,
0x19181110, 0x59585150,
0x1b1a1312, 0x5a5b5352,
0x89888180, 0xc9c8c1c0,
0x8b8a8382, 0xcacbc3c2,
0x99989190, 0xd9d8d1d0,
0x9b9a9392, 0xdbdad3d2,
},
{
0x04, 0x05, 0x0c, 0x0d, 0x44, 0x45, 0x4c, 0x4d,
0x06, 0x07, 0x0e, 0x0f, 0x46, 0x47, 0x4e, 0x4f,
0x14, 0x15, 0x1c, 0x1d, 0x54, 0x55, 0x5c, 0x5d,
0x16, 0x17, 0x1e, 0x1f, 0x56, 0x57, 0x5e, 0x5f,
0x84, 0x85, 0x8c, 0x8d, 0xc4, 0xc5, 0xcc, 0xcd,
0x86, 0x87, 0x8e, 0x8f, 0xc6, 0xc7, 0xce, 0xcf,
0x94, 0x95, 0x9c, 0x9d, 0xd4, 0xd5, 0xdc, 0xdd,
0x96, 0x97, 0x9e, 0x9f, 0xd6, 0xd7, 0xde, 0xdf,
0x0d0c0504, 0x4d4c4544,
0x0f0e0706, 0x4f4e4746,
0x1d1c1514, 0x5d5c5554,
0x1f1e1716, 0x5f5e5756,
0x8d8c8584, 0xcdccc5c4,
0x8f8e8786, 0xcfcec7c6,
0x9d9c9594, 0xdddcd5d4,
0x9f9e9796, 0xdfded7d6,
},
{
0x20, 0x21, 0x28, 0x29, 0x60, 0x61, 0x68, 0x69,
0x22, 0x23, 0x2a, 0x2b, 0x62, 0x63, 0x6a, 0x6b,
0x30, 0x31, 0x38, 0x39, 0x70, 0x71, 0x78, 0x79,
0x32, 0x33, 0x3a, 0x3b, 0x72, 0x73, 0x7a, 0x7b,
0xa0, 0xa1, 0xa8, 0xa9, 0xe0, 0xe1, 0xe8, 0xe9,
0xa2, 0xa3, 0xaa, 0xab, 0xe2, 0xe3, 0xea, 0xeb,
0xb0, 0xb1, 0xb8, 0xb9, 0xf0, 0xf1, 0xf8, 0xf9,
0xb2, 0xb3, 0xba, 0xbb, 0xf2, 0xf3, 0xfa, 0xfb,
0x29282120, 0x69686160,
0x2b2a2322, 0x6b6a6362,
0x39383130, 0x79787170,
0x3b3a3332, 0x7b7a7372,
0xa9a8a1a0, 0xe9e8e1e0,
0xabaaa3a2, 0xebeae3e2,
0xb9b8b1b0, 0xf9f8f1f0,
0xbbbab3b2, 0xfbfaf3f2,
},
{
0x24, 0x25, 0x2c, 0x2d, 0x64, 0x65, 0x6c, 0x6d,
0x26, 0x27, 0x2e, 0x2f, 0x66, 0x67, 0x6e, 0x6f,
0x34, 0x35, 0x3c, 0x3d, 0x74, 0x75, 0x7c, 0x7d,
0x36, 0x37, 0x3e, 0x3f, 0x76, 0x77, 0x7e, 0x7f,
0xa4, 0xa5, 0xac, 0xad, 0xe4, 0xe5, 0xec, 0xed,
0xa6, 0xa7, 0xae, 0xaf, 0xe6, 0xe7, 0xee, 0xef,
0xb4, 0xb5, 0xbc, 0xbd, 0xf4, 0xf5, 0xfc, 0xfd,
0xb6, 0xb7, 0xbe, 0xbf, 0xf6, 0xf7, 0xfe, 0xff,
0x2d2c2524, 0x6d6c6564,
0x2f2e2726, 0x6f6e6766,
0x3d3c3534, 0x7d7c7574,
0x3f3e3736, 0x7f7e7776,
0xadaca5a4, 0xedece5e4,
0xafaea7a6, 0xefeee7e6,
0xbdbcb5b4, 0xfdfcf5f4,
0xbfbeb7b6, 0xfffef7f6,
},
};
@ -76,7 +76,9 @@ void main() {
uint col = bitfieldExtract(x, 0, 3);
uint row = bitfieldExtract(y, 0, 3);
uint lut = bitfieldExtract(z, 0, 2);
uint idx = lut_64bpp[lut][col + row * MICRO_TILE_DIM];
uint idx_dw = lut_64bpp[lut][(col + row * MICRO_TILE_DIM) >> 2u];
uint byte_ofs = gl_LocalInvocationID.x & 3u;
uint idx = bitfieldExtract(idx_dw >> (8 * byte_ofs), 0, 8);
uint slice_offs = (z >> 2u) * info.c1 * MICRO_TILE_SZ;
uint tile_row = y / MICRO_TILE_DIM;

View File

@ -65,11 +65,18 @@ void LoadRenderDoc() {
#else
static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
#endif
if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
const auto RENDERDOC_GetAPI =
reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
ASSERT(ret == 1);
// Check if we are running by RDoc GUI
void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD);
if (!mod && Config::isRdocEnabled()) {
// If enabled in config, try to load RDoc runtime in offline mode
if ((mod = dlopen(RENDERDOC_LIB, RTLD_NOW))) {
const auto RENDERDOC_GetAPI =
reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
ASSERT(ret == 1);
} else {
LOG_ERROR(Render, "Cannot load RenderDoc: {}", dlerror());
}
}
#endif
if (rdoc_api) {

View File

@ -103,6 +103,7 @@ vk::PrimitiveTopology PrimitiveType(AmdGpu::PrimitiveType type) {
case AmdGpu::PrimitiveType::TriangleList:
return vk::PrimitiveTopology::eTriangleList;
case AmdGpu::PrimitiveType::TriangleFan:
case AmdGpu::PrimitiveType::Polygon:
return vk::PrimitiveTopology::eTriangleFan;
case AmdGpu::PrimitiveType::TriangleStrip:
return vk::PrimitiveTopology::eTriangleStrip;
@ -116,9 +117,6 @@ vk::PrimitiveTopology PrimitiveType(AmdGpu::PrimitiveType type) {
return vk::PrimitiveTopology::eTriangleStripWithAdjacency;
case AmdGpu::PrimitiveType::PatchPrimitive:
return vk::PrimitiveTopology::ePatchList;
case AmdGpu::PrimitiveType::Polygon:
// Needs to generate index buffer on the fly.
return vk::PrimitiveTopology::eTriangleList;
case AmdGpu::PrimitiveType::QuadList:
case AmdGpu::PrimitiveType::RectList:
return vk::PrimitiveTopology::ePatchList;
@ -326,6 +324,34 @@ vk::BorderColor BorderColor(AmdGpu::BorderColor color) {
}
}
vk::ComponentSwizzle ComponentSwizzle(AmdGpu::CompSwizzle comp_swizzle) {
switch (comp_swizzle) {
case AmdGpu::CompSwizzle::Zero:
return vk::ComponentSwizzle::eZero;
case AmdGpu::CompSwizzle::One:
return vk::ComponentSwizzle::eOne;
case AmdGpu::CompSwizzle::Red:
return vk::ComponentSwizzle::eR;
case AmdGpu::CompSwizzle::Green:
return vk::ComponentSwizzle::eG;
case AmdGpu::CompSwizzle::Blue:
return vk::ComponentSwizzle::eB;
case AmdGpu::CompSwizzle::Alpha:
return vk::ComponentSwizzle::eA;
default:
UNREACHABLE();
}
}
vk::ComponentMapping ComponentMapping(AmdGpu::CompMapping comp_mapping) {
return vk::ComponentMapping{
.r = ComponentSwizzle(comp_mapping.r),
.g = ComponentSwizzle(comp_mapping.g),
.b = ComponentSwizzle(comp_mapping.b),
.a = ComponentSwizzle(comp_mapping.a),
};
}
static constexpr vk::FormatFeatureFlags2 BufferRead =
vk::FormatFeatureFlagBits2::eUniformTexelBuffer | vk::FormatFeatureFlagBits2::eVertexBuffer;
static constexpr vk::FormatFeatureFlags2 BufferWrite =
@ -540,10 +566,8 @@ std::span<const SurfaceFormatInfo> SurfaceFormats() {
// 10_11_11
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format10_11_11, AmdGpu::NumberFormat::Float,
vk::Format::eB10G11R11UfloatPack32),
// 11_11_10
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format11_11_10, AmdGpu::NumberFormat::Float,
vk::Format::eB10G11R11UfloatPack32),
// 10_10_10_2
// 11_11_10 - Remapped to 10_11_11.
// 10_10_10_2 - Remapped to 2_10_10_10.
// 2_10_10_10
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format2_10_10_10, AmdGpu::NumberFormat::Unorm,
vk::Format::eA2B10G10R10UnormPack32),
@ -616,7 +640,7 @@ std::span<const SurfaceFormatInfo> SurfaceFormats() {
// 1_5_5_5
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format1_5_5_5, AmdGpu::NumberFormat::Unorm,
vk::Format::eR5G5B5A1UnormPack16),
// 5_5_5_1
// 5_5_5_1 - Remapped to 1_5_5_5.
// 4_4_4_4
CreateSurfaceFormatInfo(AmdGpu::DataFormat::Format4_4_4_4, AmdGpu::NumberFormat::Unorm,
vk::Format::eR4G4B4A4UnormPack16),
@ -679,31 +703,6 @@ vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat nu
return format->vk_format;
}
vk::Format AdjustColorBufferFormat(vk::Format base_format,
Liverpool::ColorBuffer::SwapMode comp_swap) {
const bool comp_swap_alt = comp_swap == Liverpool::ColorBuffer::SwapMode::Alternate;
const bool comp_swap_reverse = comp_swap == Liverpool::ColorBuffer::SwapMode::StandardReverse;
const bool comp_swap_alt_reverse =
comp_swap == Liverpool::ColorBuffer::SwapMode::AlternateReverse;
if (comp_swap_alt) {
switch (base_format) {
case vk::Format::eR8G8B8A8Unorm:
return vk::Format::eB8G8R8A8Unorm;
case vk::Format::eB8G8R8A8Unorm:
return vk::Format::eR8G8B8A8Unorm;
case vk::Format::eR8G8B8A8Srgb:
return vk::Format::eB8G8R8A8Srgb;
case vk::Format::eB8G8R8A8Srgb:
return vk::Format::eR8G8B8A8Srgb;
case vk::Format::eA2B10G10R10UnormPack32:
return vk::Format::eA2R10G10B10UnormPack32;
default:
break;
}
}
return base_format;
}
static constexpr DepthFormatInfo CreateDepthFormatInfo(
const DepthBuffer::ZFormat z_format, const DepthBuffer::StencilFormat stencil_format,
const vk::Format vk_format) {
@ -746,21 +745,12 @@ vk::Format DepthFormat(DepthBuffer::ZFormat z_format, DepthBuffer::StencilFormat
}
vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color_buffer) {
const auto comp_swap = color_buffer.info.comp_swap.Value();
const auto format = color_buffer.info.format.Value();
const auto number_type = color_buffer.info.number_type.Value();
const auto comp_swizzle = color_buffer.Swizzle();
const auto format = color_buffer.DataFormat();
const auto number_type = color_buffer.NumFormat();
const auto& c0 = color_buffer.clear_word0;
const auto& c1 = color_buffer.clear_word1;
const auto num_bits = AmdGpu::NumBits(color_buffer.info.format);
const auto num_components = AmdGpu::NumComponents(format);
const bool comp_swap_alt =
comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::Alternate ||
comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::AlternateReverse;
const bool comp_swap_reverse =
comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::StandardReverse ||
comp_swap == AmdGpu::Liverpool::ColorBuffer::SwapMode::AlternateReverse;
vk::ClearColorValue color{};
@ -1081,26 +1071,7 @@ vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color
break;
}
if (num_components == 1) {
if (comp_swap != Liverpool::ColorBuffer::SwapMode::Standard) {
color.float32[static_cast<int>(comp_swap)] = color.float32[0];
color.float32[0] = 0.0f;
}
} else {
if (comp_swap_alt && num_components == 4) {
std::swap(color.float32[0], color.float32[2]);
}
if (comp_swap_reverse) {
std::reverse(std::begin(color.float32), std::begin(color.float32) + num_components);
}
if (comp_swap_alt && num_components != 4) {
color.float32[3] = color.float32[num_components - 1];
color.float32[num_components - 1] = 0.0f;
}
}
color.float32 = comp_swizzle.Apply(color.float32);
return {.color = color};
}

View File

@ -42,6 +42,10 @@ vk::SamplerMipmapMode MipFilter(AmdGpu::MipFilter filter);
vk::BorderColor BorderColor(AmdGpu::BorderColor color);
vk::ComponentSwizzle ComponentSwizzle(AmdGpu::CompSwizzle comp_swizzle);
vk::ComponentMapping ComponentMapping(AmdGpu::CompMapping comp_mapping);
struct SurfaceFormatInfo {
AmdGpu::DataFormat data_format;
AmdGpu::NumberFormat number_format;
@ -52,9 +56,6 @@ std::span<const SurfaceFormatInfo> SurfaceFormats();
vk::Format SurfaceFormat(AmdGpu::DataFormat data_format, AmdGpu::NumberFormat num_format);
vk::Format AdjustColorBufferFormat(vk::Format base_format,
Liverpool::ColorBuffer::SwapMode comp_swap);
struct DepthFormatInfo {
Liverpool::DepthBuffer::ZFormat z_format;
Liverpool::DepthBuffer::StencilFormat stencil_format;
@ -70,15 +71,6 @@ vk::ClearValue ColorBufferClearValue(const AmdGpu::Liverpool::ColorBuffer& color
vk::SampleCountFlagBits NumSamples(u32 num_samples, vk::SampleCountFlags supported_flags);
inline void EmitPolygonToTriangleListIndices(u8* out_ptr, u32 num_vertices) {
u16* out_data = reinterpret_cast<u16*>(out_ptr);
for (u16 i = 1; i < num_vertices - 1; i++) {
*out_data++ = 0;
*out_data++ = i;
*out_data++ = i + 1;
}
}
static inline vk::Format PromoteFormatToDepth(vk::Format fmt) {
if (fmt == vk::Format::eR32Sfloat) {
return vk::Format::eD32Sfloat;

View File

@ -32,7 +32,7 @@ struct GraphicsPipelineKey {
u32 num_color_attachments;
std::array<vk::Format, Liverpool::NumColorBuffers> color_formats;
std::array<AmdGpu::NumberFormat, Liverpool::NumColorBuffers> color_num_formats;
std::array<Liverpool::ColorBuffer::SwapMode, Liverpool::NumColorBuffers> mrt_swizzles;
std::array<AmdGpu::CompMapping, Liverpool::NumColorBuffers> color_swizzles;
vk::Format depth_format;
vk::Format stencil_format;

View File

@ -168,7 +168,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS
for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
info.fs_info.color_buffers[i] = {
.num_format = graphics_key.color_num_formats[i],
.mrt_swizzle = static_cast<Shader::MrtSwizzle>(graphics_key.mrt_swizzles[i]),
.swizzle = graphics_key.color_swizzles[i],
};
}
break;
@ -304,7 +304,7 @@ bool PipelineCache::RefreshGraphicsKey() {
key.color_num_formats.fill(AmdGpu::NumberFormat::Unorm);
key.blend_controls.fill({});
key.write_masks.fill({});
key.mrt_swizzles.fill(Liverpool::ColorBuffer::SwapMode::Standard);
key.color_swizzles.fill({});
key.vertex_buffer_formats.fill(vk::Format::eUndefined);
key.patch_control_points = 0;
@ -327,14 +327,10 @@ bool PipelineCache::RefreshGraphicsKey() {
continue;
}
const auto base_format =
LiverpoolToVK::SurfaceFormat(col_buf.info.format, col_buf.NumFormat());
key.color_formats[remapped_cb] =
LiverpoolToVK::AdjustColorBufferFormat(base_format, col_buf.info.comp_swap.Value());
LiverpoolToVK::SurfaceFormat(col_buf.DataFormat(), col_buf.NumFormat());
key.color_num_formats[remapped_cb] = col_buf.NumFormat();
if (base_format == key.color_formats[remapped_cb]) {
key.mrt_swizzles[remapped_cb] = col_buf.info.comp_swap.Value();
}
key.color_swizzles[remapped_cb] = col_buf.Swizzle();
}
fetch_shader = std::nullopt;
@ -450,7 +446,7 @@ bool PipelineCache::RefreshGraphicsKey() {
// of the latter we need to change format to undefined, and either way we need to
// increment the index for the null attachment binding.
key.color_formats[remapped_cb] = vk::Format::eUndefined;
key.mrt_swizzles[remapped_cb] = Liverpool::ColorBuffer::SwapMode::Standard;
key.color_swizzles[remapped_cb] = {};
++remapped_cb;
continue;
}

View File

@ -283,6 +283,9 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e
Common::FS::GetUserPathString(Common::FS::PathType::LogDir);
const char* log_path = crash_diagnostic_path.c_str();
vk::Bool32 enable_force_barriers = vk::True;
#ifdef __APPLE__
const vk::Bool32 mvk_debug_mode = enable_crash_diagnostic ? vk::True : vk::False;
#endif
const std::array layer_setings = {
vk::LayerSettingEXT{
@ -356,7 +359,7 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e
.pSettingName = "MVK_CONFIG_DEBUG",
.type = vk::LayerSettingTypeEXT::eBool32,
.valueCount = 1,
.pValues = &enable_crash_diagnostic,
.pValues = &mvk_debug_mode,
}
#endif
};

View File

@ -628,6 +628,13 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
}
void Presenter::Present(Frame* frame) {
// Free the frame for reuse
const auto free_frame = [&] {
std::scoped_lock fl{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
};
// Recreate the swapchain if the window was resized.
if (window.GetWidth() != swapchain.GetExtent().width ||
window.GetHeight() != swapchain.GetExtent().height) {
@ -636,8 +643,19 @@ void Presenter::Present(Frame* frame) {
if (!swapchain.AcquireNextImage()) {
swapchain.Recreate(window.GetWidth(), window.GetHeight());
if (!swapchain.AcquireNextImage()) {
// User resizes the window too fast and GPU can't keep up. Skip this frame.
LOG_WARNING(Render_Vulkan, "Skipping frame!");
free_frame();
return;
}
}
// Reset fence for queue submission. Do it here instead of GetRenderFrame() because we may
// skip frame because of slow swapchain recreation. If a frame skip occurs, we skip signal
// the frame's present fence and future GetRenderFrame() call will hang waiting for this frame.
instance.GetDevice().resetFences(frame->present_done);
ImGui::Core::NewFrame();
const vk::Image swapchain_image = swapchain.Image();
@ -737,11 +755,7 @@ void Presenter::Present(Frame* frame) {
swapchain.Recreate(window.GetWidth(), window.GetHeight());
}
// Free the frame for reuse
std::scoped_lock fl{free_mutex};
free_queue.push(frame);
free_cv.notify_one();
free_frame();
DebugState.IncFlipFrameNum();
}
@ -776,9 +790,6 @@ Frame* Presenter::GetRenderFrame() {
}
}
// Reset fence for next queue submission.
device.resetFences(frame->present_done);
// If the window dimensions changed, recreate this frame
if (frame->width != window.GetWidth() || frame->height != window.GetHeight()) {
RecreateFrame(frame, window.GetWidth(), window.GetHeight());

View File

@ -12,7 +12,6 @@
#include "video_core/renderer_vulkan/vk_shader_hle.h"
#include "video_core/texture_cache/image_view.h"
#include "video_core/texture_cache/texture_cache.h"
#include "vk_rasterizer.h"
#ifdef MemoryBarrier
#undef MemoryBarrier
@ -252,7 +251,9 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) {
const auto& vs_info = pipeline->GetStage(Shader::LogicalStage::Vertex);
const auto& fetch_shader = pipeline->GetFetchShader();
buffer_cache.BindVertexBuffers(vs_info, fetch_shader);
const u32 num_indices = buffer_cache.BindIndexBuffer(is_indexed, index_offset);
if (is_indexed) {
buffer_cache.BindIndexBuffer(index_offset);
}
BeginRendering(*pipeline, state);
UpdateDynamicState(*pipeline);
@ -263,10 +264,11 @@ void Rasterizer::Draw(bool is_indexed, u32 index_offset) {
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle());
if (is_indexed) {
cmdbuf.drawIndexed(num_indices, regs.num_instances.NumInstances(), 0, s32(vertex_offset),
instance_offset);
cmdbuf.drawIndexed(regs.num_indices, regs.num_instances.NumInstances(), 0,
s32(vertex_offset), instance_offset);
} else {
cmdbuf.draw(num_indices, regs.num_instances.NumInstances(), vertex_offset, instance_offset);
cmdbuf.draw(regs.num_indices, regs.num_instances.NumInstances(), vertex_offset,
instance_offset);
}
ResetBindings();
@ -280,22 +282,12 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3
return;
}
const auto& regs = liverpool->regs;
if (regs.primitive_type == AmdGpu::PrimitiveType::Polygon) {
// We use a generated index buffer to convert polygons to triangles. Since it
// changes type of the draw, arguments are not valid for this case. We need to run a
// conversion pass to repack the indirect arguments buffer first.
LOG_WARNING(Render_Vulkan, "Primitive type is not supported for indirect draw");
return;
}
const GraphicsPipeline* pipeline = pipeline_cache.GetGraphicsPipeline();
if (!pipeline) {
return;
}
auto state = PrepareRenderState(pipeline->GetMrtMask());
if (!BindResources(pipeline)) {
return;
}
@ -303,7 +295,9 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3
const auto& vs_info = pipeline->GetStage(Shader::LogicalStage::Vertex);
const auto& fetch_shader = pipeline->GetFetchShader();
buffer_cache.BindVertexBuffers(vs_info, fetch_shader);
buffer_cache.BindIndexBuffer(is_indexed, 0);
if (is_indexed) {
buffer_cache.BindIndexBuffer(0);
}
const auto& [buffer, base] =
buffer_cache.ObtainBuffer(arg_address + offset, stride * max_count, false);

Some files were not shown because too many files have changed in this diff Show More