Merge branch 'master' into vsh_fix

This commit is contained in:
Elad 2025-01-22 09:39:21 +02:00 committed by GitHub
commit c3b469a9ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
170 changed files with 3889 additions and 1616 deletions

View file

@ -44,7 +44,7 @@ add_subdirectory(libpng EXCLUDE_FROM_ALL)
# pugixml
if (USE_SYSTEM_PUGIXML)
pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml>=1.11)
pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml>=1.15)
add_library(pugixml INTERFACE)
target_link_libraries(pugixml INTERFACE PkgConfig::PUGIXML)
else()

@ -1 +1 @@
Subproject commit f5e92d76973a7a53f517579bc95d61483bf108c0
Subproject commit 51f5bd68b9b806d2c92b4318164d28b49357da31

2
3rdparty/pugixml vendored

@ -1 +1 @@
Subproject commit db78afc2b7d8f043b4bc6b185635d949ea2ed2a8
Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38

View file

@ -22,6 +22,5 @@ else()
add_subdirectory(wolfssl EXCLUDE_FROM_ALL)
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP)
target_compile_definitions(wolfssl PUBLIC FP_MAX_BITS=8192)
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_NO_OPTIONS_H)
endif()

View file

@ -21,7 +21,7 @@ using namespace std::literals::string_literals;
#include <cwchar>
#include <Windows.h>
static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
{
// String size + null terminator
const usz buf_size = source.size() + 1;
@ -44,7 +44,7 @@ static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
std::memcpy(buffer.get() + 32768 + 4, L"UNC\\", 4 * sizeof(wchar_t));
}
ensure(MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get() + 32768 + (unc ? 8 : 4), size)); // "to_wchar"
ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768 + (unc ? 8 : 4), size)); // "to_wchar"
// Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc)
ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar"
@ -2021,7 +2021,7 @@ std::string fs::get_executable_dir()
return s_exe_dir;
}
const std::string& fs::get_config_dir()
const std::string& fs::get_config_dir([[maybe_unused]] bool get_config_subdirectory)
{
// Use magic static
static const std::string s_dir = []
@ -2103,6 +2103,14 @@ const std::string& fs::get_config_dir()
return dir;
}();
#ifdef _WIN32
if (get_config_subdirectory)
{
static const std::string subdir = s_dir + "config/";
return subdir;
}
#endif
return s_dir;
}
@ -2144,6 +2152,16 @@ const std::string& fs::get_cache_dir()
return s_dir;
}
const std::string& fs::get_log_dir()
{
#ifdef _WIN32
static const std::string s_dir = fs::get_config_dir() + "log/";
return s_dir;
#else
return fs::get_cache_dir();
#endif
}
const std::string& fs::get_temp_dir()
{
static const std::string s_dir = []

View file

@ -599,12 +599,15 @@ namespace fs
// Get executable containing directory
std::string get_executable_dir();
// Get configuration directory
const std::string& get_config_dir();
// Get configuration directory. Set get_config_subdirectory to true to get the nested config dir on windows.
const std::string& get_config_dir(bool get_config_subdirectory = false);
// Get common cache directory
const std::string& get_cache_dir();
// Get common log directory
const std::string& get_log_dir();
// Temporary directory
const std::string& get_temp_dir();

View file

@ -33,7 +33,8 @@ enum class thread_state : u32
aborting = 1, // The thread has been joined in the destructor or explicitly aborted
errored = 2, // Set after the emergency_exit call
finished = 3, // Final state, always set at the end of thread execution
mask = 3
mask = 3,
destroying_context = 7, // Special value assigned to destroy data explicitly before the destructor
};
template <class Context>
@ -702,14 +703,17 @@ public:
thread::m_sync.notify_all();
}
if (s == thread_state::finished)
if (s == thread_state::finished || s == thread_state::destroying_context)
{
// This participates in emulation stopping, use destruction-alike semantics
thread::join(true);
}
if (s == thread_state::destroying_context)
{
if constexpr (std::is_assignable_v<Context&, thread_state>)
{
static_cast<Context&>(*this) = thread_state::finished;
static_cast<Context&>(*this) = thread_state::destroying_context;
}
}

View file

@ -125,19 +125,15 @@ patch_engine::patch_engine()
std::string patch_engine::get_patch_config_path()
{
#ifdef _WIN32
const std::string config_dir = fs::get_config_dir() + "config/";
const std::string config_dir = fs::get_config_dir(true);
const std::string patch_path = config_dir + "patch_config.yml";
#ifdef _WIN32
if (!fs::create_path(config_dir))
{
patch_log.error("Could not create path: %s (%s)", patch_path, fs::g_tls_error);
}
return patch_path;
#else
return fs::get_config_dir() + "patch_config.yml";
#endif
return patch_path;
}
std::string patch_engine::get_patches_path()
@ -1449,6 +1445,8 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
void patch_engine::apply(std::vector<u32>& applied_total, const std::string& name, std::function<u8*(u32, u32)> mem_translate, u32 filesz, u32 min_addr)
{
// applied_total may be non-empty, do not clear it
if (!m_map.contains(name))
{
return;
@ -1597,6 +1595,9 @@ void patch_engine::apply(std::vector<u32>& applied_total, const std::string& nam
}
}
}
// Ensure consistent order
std::sort(applied_total.begin(), applied_total.end());
}
void patch_engine::unload(const std::string& name)

View file

@ -14,6 +14,7 @@ enum class cheat_type : u8
signed_16_cheat,
signed_32_cheat,
signed_64_cheat,
float_32_cheat,
max
};

View file

@ -49,12 +49,11 @@ std::string rXmlNode::GetName()
return {};
}
std::string rXmlNode::GetAttribute(const std::string& name)
std::string rXmlNode::GetAttribute(std::string_view name)
{
if (handle)
{
const auto pred = [&name](const pugi::xml_attribute& attr) { return (name == attr.name()); };
if (const pugi::xml_attribute attr = handle.find_attribute(pred))
if (const pugi::xml_attribute attr = handle.attribute(name))
{
if (const pugi::char_t* value = attr.value())
{
@ -86,7 +85,7 @@ rXmlDocument::rXmlDocument()
{
}
pugi::xml_parse_result rXmlDocument::Read(const std::string& data)
pugi::xml_parse_result rXmlDocument::Read(std::string_view data)
{
if (handle)
{

View file

@ -23,7 +23,7 @@ struct rXmlNode
std::shared_ptr<rXmlNode> GetChildren();
std::shared_ptr<rXmlNode> GetNext();
std::string GetName();
std::string GetAttribute(const std::string& name);
std::string GetAttribute(std::string_view name);
std::string GetNodeContent();
pugi::xml_node handle{};
@ -34,7 +34,7 @@ struct rXmlDocument
rXmlDocument();
rXmlDocument(const rXmlDocument& other) = delete;
rXmlDocument &operator=(const rXmlDocument& other) = delete;
pugi::xml_parse_result Read(const std::string& data);
pugi::xml_parse_result Read(std::string_view data);
virtual std::shared_ptr<rXmlNode> GetRoot();
pugi::xml_document handle{};

View file

@ -164,7 +164,7 @@ elseif(WIN32)
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/GuiConfigs
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $<TARGET_FILE_DIR:rpcs3>/git
COMMAND "${WINDEPLOYQT_EXECUTABLE}" --no-compiler-runtime --no-opengl-sw --no-patchqt
--no-translations --no-quick --no-system-d3d-compiler --no-quick-import
--no-translations --no-system-d3d-compiler --no-quick-import
--plugindir "$<IF:$<CXX_COMPILER_ID:MSVC>,$<TARGET_FILE_DIR:rpcs3>/plugins,$<TARGET_FILE_DIR:rpcs3>/share/qt6/plugins>"
--verbose 0 "$<TARGET_FILE:rpcs3>")
endif()

View file

@ -172,6 +172,7 @@ target_link_libraries(rpcs3_emu
# Cell
target_sources(rpcs3_emu PRIVATE
Cell/ErrorCodes.cpp
Cell/MFC.cpp
Cell/PPUAnalyser.cpp
Cell/PPUDisAsm.cpp
@ -557,6 +558,7 @@ target_sources(rpcs3_emu PRIVATE
RSX/Program/CgBinaryFragmentProgram.cpp
RSX/Program/CgBinaryVertexProgram.cpp
RSX/Program/FragmentProgramDecompiler.cpp
RSX/Program/FragmentProgramRegister.cpp
RSX/Program/GLSLCommon.cpp
RSX/Program/ProgramStateCache.cpp
RSX/Program/program_util.cpp
@ -593,6 +595,7 @@ if(TARGET 3rdparty_vulkan)
RSX/VK/VKAsyncScheduler.cpp
RSX/VK/VKCommandStream.cpp
RSX/VK/VKCommonDecompiler.cpp
RSX/VK/VKCommonPipelineLayout.cpp
RSX/VK/VKCompute.cpp
RSX/VK/VKDMA.cpp
RSX/VK/VKDraw.cpp

View file

@ -0,0 +1,91 @@
#include "stdafx.h"
#include "ErrorCodes.h"
#include "PPUThread.h"
#include "SPUThread.h"
LOG_CHANNEL(sys_log, "SYS");
bool g_log_all_errors = false;
s32 error_code::error_report(s32 result, const logs::message* channel, const char* fmt, const fmt_type_info* sup, const u64* args)
{
static thread_local std::string g_tls_error_str;
static thread_local std::unordered_map<std::string, usz> g_tls_error_stats;
if (!channel)
{
channel = &sys_log.error;
}
if (!sup && !args)
{
if (!fmt)
{
// Report and clean error state
for (auto&& pair : g_tls_error_stats)
{
if (pair.second > 3)
{
channel->operator()("Stat: %s [x%u]", pair.first, pair.second);
}
}
g_tls_error_stats.clear();
return 0;
}
}
ensure(fmt);
const char* func = "Unknown function";
if (auto ppu = get_current_cpu_thread<ppu_thread>())
{
if (auto current = ppu->current_function)
{
func = current;
}
}
else if (auto spu = get_current_cpu_thread<spu_thread>())
{
if (auto current = spu->current_func; current && spu->start_time)
{
func = current;
}
}
// Format log message (use preallocated buffer)
g_tls_error_str.clear();
fmt::append(g_tls_error_str, "'%s' failed with 0x%08x", func, result);
// Add spacer between error and fmt if necessary
if (fmt[0] != ' ')
g_tls_error_str += " : ";
fmt::raw_append(g_tls_error_str, fmt, sup, args);
// Update stats and check log threshold
if (g_log_all_errors) [[unlikely]]
{
if (!g_tls_error_stats.empty())
{
// Report and clean error state
error_report(0, nullptr, nullptr, nullptr, nullptr);
}
channel->operator()("%s", g_tls_error_str);
}
else
{
const auto stat = ++g_tls_error_stats[g_tls_error_str];
if (stat <= 3)
{
channel->operator()("%s [%u]", g_tls_error_str, stat);
}
}
return result;
}

View file

@ -270,35 +270,75 @@ void AtracXdecContext::exec(ppu_thread& ppu)
decoder.init_avcodec();
}
switch (savestate)
{
case atracxdec_state::initial: break;
case atracxdec_state::waiting_for_cmd: goto label1_wait_for_cmd_state;
case atracxdec_state::checking_run_thread_1: goto label2_check_run_thread_1_state;
case atracxdec_state::executing_cmd: goto label3_execute_cmd_state;
case atracxdec_state::waiting_for_output: goto label4_wait_for_output_state;
case atracxdec_state::checking_run_thread_2: goto label5_check_run_thread_2_state;
case atracxdec_state::decoding: goto label6_decode_state;
}
for (;;cmd_counter++)
{
cellAtracXdec.trace("Command counter: %llu, waiting for next command...", cmd_counter);
if (!skip_getting_command)
for (;;)
{
lv2_obj::sleep(ppu);
std::lock_guard lock{queue_mutex};
savestate = atracxdec_state::initial;
while (cmd_queue.empty() && !ppu.is_stopped())
{
lv2_obj::sleep(ppu);
queue_not_empty.wait(queue_mutex, 20000);
}
ensure(sys_mutex_lock(ppu, queue_mutex, 0) == CELL_OK);
if (ppu.is_stopped())
{
ppu.state += cpu_flag::again;
return;
}
cmd_queue.pop(cmd);
if (!run_thread)
if (ppu.state & cpu_flag::again)
{
return;
}
if (!cmd_queue.empty())
{
break;
}
savestate = atracxdec_state::waiting_for_cmd;
label1_wait_for_cmd_state:
ensure(sys_cond_wait(ppu, queue_not_empty, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
return;
}
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
}
cmd_queue.pop(cmd);
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
savestate = atracxdec_state::checking_run_thread_1;
label2_check_run_thread_1_state:
ensure(sys_mutex_lock(ppu, run_thread_mutex, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
return;
}
if (!run_thread)
{
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
return;
}
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
savestate = atracxdec_state::executing_cmd;
label3_execute_cmd_state:
cellAtracXdec.trace("Command type: %d", static_cast<u32>(cmd.type.get()));
switch (cmd.type)
@ -327,8 +367,6 @@ void AtracXdecContext::exec(ppu_thread& ppu)
}
case AtracXdecCmdType::end_seq:
{
skip_getting_command = true;
// Block savestate creation during callbacks
std::unique_lock savestate_lock{g_fxo->get<hle_locks_t>(), std::try_to_lock};
@ -338,41 +376,59 @@ void AtracXdecContext::exec(ppu_thread& ppu)
return;
}
skip_getting_command = false;
// Doesn't do anything else
notify_seq_done.cbFunc(ppu, notify_seq_done.cbArg);
break;
}
case AtracXdecCmdType::decode_au:
{
skip_getting_command = true;
ensure(!!cmd.au_start_addr); // Not checked on LLE
cellAtracXdec.trace("Waiting for output to be consumed...");
lv2_obj::sleep(ppu);
std::unique_lock output_mutex_lock{output_mutex};
ensure(sys_mutex_lock(ppu, output_mutex, 0) == CELL_OK);
while (output_locked && !ppu.is_stopped())
if (ppu.state & cpu_flag::again)
{
lv2_obj::sleep(ppu);
output_consumed.wait(output_mutex, 20000);
return;
}
if (ppu.is_stopped())
while (output_locked)
{
savestate = atracxdec_state::waiting_for_output;
label4_wait_for_output_state:
ensure(sys_cond_wait(ppu, output_consumed, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
return;
}
}
cellAtracXdec.trace("Output consumed");
savestate = atracxdec_state::checking_run_thread_2;
label5_check_run_thread_2_state:
ensure(sys_mutex_lock(ppu, run_thread_mutex, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
ppu.state += cpu_flag::again;
return;
}
if (!run_thread)
{
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
ensure(sys_mutex_unlock(ppu, output_mutex) == CELL_OK);
return;
}
cellAtracXdec.trace("Output consumed");
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
savestate = atracxdec_state::decoding;
label6_decode_state:
u32 error = CELL_OK;
@ -578,14 +634,12 @@ void AtracXdecContext::exec(ppu_thread& ppu)
return;
}
skip_getting_command = false;
// au_done and pcm_out callbacks are always called after a decode command, even if an error occurred
// The output always has to be consumed as well
notify_au_done.cbFunc(ppu, cmd.pcm_handle, notify_au_done.cbArg);
output_locked = true;
output_mutex_lock.unlock();
ensure(sys_mutex_unlock(ppu, output_mutex) == CELL_OK);
const u32 output_size = decoded_samples_num * (decoder.bw_pcm & 0x7fu) * decoder.nch_out;
@ -614,29 +668,46 @@ void AtracXdecContext::exec(ppu_thread& ppu)
template <AtracXdecCmdType type>
error_code AtracXdecContext::send_command(ppu_thread& ppu, auto&&... args)
{
ppu.state += cpu_flag::wait;
auto& savestate = *ppu.optional_savestate_state;
const bool signal = savestate.try_read<bool>().second;
savestate.clear();
if (!signal)
{
std::lock_guard lock{queue_mutex};
ensure(sys_mutex_lock(ppu, queue_mutex, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
return {};
}
if constexpr (type == AtracXdecCmdType::close)
{
// Close command is only sent if the queue is empty on LLE
if (!cmd_queue.empty())
{
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
return {};
}
}
if (cmd_queue.full())
{
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
return CELL_ADEC_ERROR_ATX_BUSY;
}
cmd_queue.emplace(std::forward<AtracXdecCmdType>(type), std::forward<decltype(args)>(args)...);
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
}
queue_not_empty.notify_one();
ensure(sys_cond_signal(ppu, queue_not_empty) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
savestate(true);
}
return CELL_OK;
}
@ -699,6 +770,29 @@ error_code _CellAdecCoreOpOpenExt_atracx(ppu_thread& ppu, vm::ptr<AtracXdecConte
write_to_ptr(handle.get_ptr(), AtracXdecContext(notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg,
vm::bptr<u8>::make(handle.addr() + utils::align(static_cast<u32>(sizeof(AtracXdecContext)), 0x80) + ATXDEC_SPURS_STRUCTS_SIZE)));
const vm::var<sys_mutex_attribute_t> mutex_attr{{ SYS_SYNC_PRIORITY, SYS_SYNC_NOT_RECURSIVE, SYS_SYNC_NOT_PROCESS_SHARED, SYS_SYNC_NOT_ADAPTIVE, 0, 0, 0, { "_atd001"_u64 } }};
const vm::var<sys_cond_attribute_t> cond_attr{{ SYS_SYNC_NOT_PROCESS_SHARED, 0, 0, { "_atd002"_u64 } }};
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::queue_mutex), mutex_attr) == CELL_OK);
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::queue_not_empty), handle->queue_mutex, cond_attr) == CELL_OK);
mutex_attr->name_u64 = "_atd003"_u64;
cond_attr->name_u64 = "_atd004"_u64;
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::run_thread_mutex), mutex_attr) == CELL_OK);
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::run_thread_cond), handle->run_thread_mutex, cond_attr) == CELL_OK);
mutex_attr->name_u64 = "_atd005"_u64;
cond_attr->name_u64 = "_atd006"_u64;
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::output_mutex), mutex_attr) == CELL_OK);
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::output_consumed), handle->output_mutex, cond_attr) == CELL_OK);
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
handle->output_locked = false;
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
const vm::var<char[]> _name = vm::make_str("HLE ATRAC3plus decoder");
const auto entry = g_fxo->get<ppu_function_manager>().func_addr(FIND_FUNC(atracXdecEntry));
ppu_execute<&sys_ppu_thread_create>(ppu, handle.ptr(&AtracXdecContext::thread_id), entry, handle.addr(), +res->ppuThreadPriority, +res->ppuThreadStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, +_name);
@ -725,29 +819,32 @@ error_code _CellAdecCoreOpClose_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext
return {};
}
ppu.state += cpu_flag::wait;
cellAtracXdec.notice("_CellAdecCoreOpClose_atracx(handle=*0x%x)", handle);
ensure(!!handle); // Not checked on LLE
ensure(sys_mutex_lock(ppu, handle->run_thread_mutex, 0) == CELL_OK);
handle->run_thread = false;
ensure(sys_mutex_unlock(ppu, handle->run_thread_mutex) == CELL_OK);
handle->send_command<AtracXdecCmdType::close>(ppu);
{
std::lock_guard lock{handle->output_mutex};
handle->output_locked = false;
}
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
handle->output_locked = false;
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
handle->output_consumed.notify_one();
vm::var<u64> thread_ret;
ensure(sys_ppu_thread_join(ppu, static_cast<u32>(handle->thread_id), +thread_ret) == CELL_OK);
if (vm::var<u64> ret; sys_ppu_thread_join(ppu, static_cast<u32>(handle->thread_id), +ret) != CELL_OK)
{
// Other thread already closed the decoder
return CELL_ADEC_ERROR_FATAL;
}
error_code ret = sys_cond_destroy(ppu, handle->queue_not_empty);
ret = ret ? ret : sys_cond_destroy(ppu, handle->run_thread_cond);
ret = ret ? ret : sys_cond_destroy(ppu, handle->output_consumed);
ret = ret ? ret : sys_mutex_destroy(ppu, handle->queue_mutex);
ret = ret ? ret : sys_mutex_destroy(ppu, handle->run_thread_mutex);
ret = ret ? ret : sys_mutex_destroy(ppu, handle->output_mutex);
return CELL_OK;
return ret != CELL_OK ? static_cast<error_code>(CELL_ADEC_ERROR_FATAL) : CELL_OK;
}
error_code _CellAdecCoreOpStartSeq_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext> handle, vm::cptr<CellAdecParamAtracX> atracxParam)
@ -808,15 +905,35 @@ error_code _CellAdecCoreOpRealign_atracx(vm::ptr<AtracXdecContext> handle, vm::p
error_code _CellAdecCoreOpReleasePcm_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext> handle, s32 pcmHandle, vm::cptr<void> outBuffer)
{
ppu.state += cpu_flag::wait;
cellAtracXdec.trace("_CellAdecCoreOpReleasePcm_atracx(handle=*0x%x, pcmHandle=%d, outBuffer=*0x%x)", handle, pcmHandle, outBuffer);
ensure(!!handle); // Not checked on LLE
std::lock_guard lock{handle->output_mutex};
handle->output_locked = false;
handle->output_consumed.notify_one();
auto& savestate = *ppu.optional_savestate_state;
const bool signal = savestate.try_read<bool>().second;
savestate.clear();
if (!signal)
{
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
return {};
}
handle->output_locked = false;
}
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
if (ppu.state & cpu_flag::again)
{
savestate(true);
return {};
}
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
return CELL_OK;
}

View file

@ -20,7 +20,6 @@ constexpr int averror_eof = AVERROR_EOF; // Workaround for old-style-cast error
#pragma GCC diagnostic pop
#endif
#include "Utilities/cond.h"
#include "cellPamf.h"
#include "cellAdec.h"
@ -215,16 +214,28 @@ struct AtracXdecDecoder
CHECK_SIZE(AtracXdecDecoder, 0xa8);
// HLE exclusive, for savestates
enum class atracxdec_state : u8
{
initial,
waiting_for_cmd,
checking_run_thread_1,
executing_cmd,
waiting_for_output,
checking_run_thread_2,
decoding
};
struct AtracXdecContext
{
be_t<u64> thread_id; // sys_ppu_thread_t
shared_mutex queue_mutex; // sys_mutex_t
cond_variable queue_not_empty; // sys_cond_t
be_t<u32> queue_mutex; // sys_mutex_t
be_t<u32> queue_not_empty; // sys_cond_t
AdecCmdQueue<AtracXdecCmd> cmd_queue;
shared_mutex output_mutex; // sys_mutex_t
cond_variable output_consumed; // sys_cond_t
be_t<u32> output_mutex; // sys_mutex_t
be_t<u32> output_consumed; // sys_cond_t
be_t<u32> output_locked = false;
be_t<u32> run_thread_mutex; // sys_mutex_t
@ -239,10 +250,10 @@ struct AtracXdecContext
const vm::bptr<u8> work_mem;
// HLE exclusive
u64 cmd_counter = 0; // For debugging
AtracXdecCmd cmd; // For savestates; if savestate was created while processing a decode command, we need to save the current command
b8 skip_getting_command = false; // For savestates; skips getting a new command from the queue
b8 skip_next_frame; // Needed to emulate behavior of LLE SPU program, it doesn't output the first frame after a sequence reset or error
u64 cmd_counter = 0; // For debugging
AtracXdecCmd cmd; // For savestates; if savestate was created while processing a decode command, we need to save the current command
atracxdec_state savestate{}; // For savestates
b8 skip_next_frame; // Needed to emulate behavior of LLE SPU program, it doesn't output the first frame after a sequence reset or error
u8 spurs_stuff[58]; // 120 bytes on LLE, pointers to CellSpurs, CellSpursTaskset, etc.

View file

@ -148,9 +148,44 @@ void camera_context::save(utils::serial& ar)
return;
}
GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera);
const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera);
ar(notify_data_map, start_timestamp_us, read_mode, is_streaming, is_attached, is_open, info, attr, frame_num);
if (ar.is_writing() || version >= 2)
{
ar(is_attached_dirty);
}
if (!ar.is_writing())
{
if (is_open)
{
if (!open_camera())
{
cellCamera.error("Failed to open camera while loading savestate");
}
else if (is_streaming && !start_camera())
{
cellCamera.error("Failed to start camera while loading savestate");
}
}
}
}
gem_camera_shared::gem_camera_shared(utils::serial& ar)
{
save(ar);
}
void gem_camera_shared::save(utils::serial& ar)
{
const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera);
if (ar.is_writing() || version >= 2)
{
ar(frame_timestamp_us, width, height, size, format);
}
}
static bool check_dev_num(s32 dev_num)
@ -435,7 +470,6 @@ error_code cellCameraInit()
g_camera.attr[CELL_CAMERA_USBLOAD] = { 4 };
break;
}
case fake_camera_type::eyetoy2:
{
g_camera.attr[CELL_CAMERA_SATURATION] = { 64 };
@ -455,7 +489,6 @@ error_code cellCameraInit()
g_camera.attr[CELL_CAMERA_AGCHIGH] = { 64 };
break;
}
case fake_camera_type::uvc1_1:
{
g_camera.attr[CELL_CAMERA_DEVICEID] = { 0x5ca, 0x18d0 }; // KBCR-S01MU
@ -463,14 +496,14 @@ error_code cellCameraInit()
g_camera.attr[CELL_CAMERA_NUMFRAME] = { 1 }; // Amount of supported resolutions
break;
}
default:
cellCamera.todo("Trying to init cellCamera with un-researched camera type.");
break;
}
// TODO: Some other default attributes? Need to check the actual behaviour on a real PS3.
g_camera.is_attached = true;
g_camera.is_attached = g_cfg.io.camera != camera_handler::null;
g_camera.init = 1;
return CELL_OK;
}
@ -816,8 +849,8 @@ s32 cellCameraIsAttached(s32 dev_num)
// normally should be attached immediately after event queue is registered, but just to be sure
if (!is_attached)
{
g_camera.send_attach_state(true);
is_attached = g_camera.is_attached;
g_camera.is_attached = is_attached = true;
g_camera.is_attached_dirty = true;
}
}
@ -1606,9 +1639,15 @@ void camera_context::operator()()
{
while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped())
{
// send ATTACH event
if (init && is_attached_dirty && !Emu.IsPaused())
{
send_attach_state(is_attached);
}
const s32 fps = info.framerate;
if (!fps || Emu.IsPaused() || g_cfg.io.camera == camera_handler::null)
if (!init || !fps || Emu.IsPaused() || g_cfg.io.camera == camera_handler::null)
{
thread_ctrl::wait_for(1000); // hack
continue;
@ -1783,6 +1822,7 @@ void camera_context::reset_state()
read_mode = CELL_CAMERA_READ_FUNCCALL;
is_streaming = false;
is_attached = false;
is_attached_dirty = false;
is_open = false;
info.framerate = 0;
std::memset(&attr, 0, sizeof(attr));
@ -1828,6 +1868,7 @@ void camera_context::send_attach_state(bool attached)
// We're not expected to send any events for attaching/detaching
is_attached = attached;
is_attached_dirty = false;
}
void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2)
@ -1862,15 +1903,13 @@ void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2)
void camera_context::add_queue(u64 key, u64 source, u64 flag)
{
std::lock_guard lock(mutex);
{
std::lock_guard lock_data_map(mutex_notify_data_map);
notify_data_map[key] = { source, flag };
}
// send ATTACH event - HACKY
send_attach_state(is_attached);
is_attached_dirty = true;
}
void camera_context::remove_queue(u64 key)
@ -1897,7 +1936,8 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state
{
if (is_attached)
{
send_attach_state(false);
is_attached = false;
is_attached_dirty = true;
}
if (handler)
{
@ -1938,7 +1978,8 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state
if (!is_attached)
{
cellCamera.warning("Camera handler not attached. Sending attach event...", static_cast<int>(state));
send_attach_state(true);
is_attached = true;
is_attached_dirty = true;
}
break;
}

View file

@ -427,6 +427,7 @@ public:
atomic_t<u8> read_mode{CELL_CAMERA_READ_FUNCCALL};
atomic_t<bool> is_streaming{false};
atomic_t<bool> is_attached{false};
atomic_t<bool> is_attached_dirty{false};
atomic_t<bool> is_open{false};
CellCameraInfoEx info{};
@ -471,6 +472,13 @@ using camera_thread = named_thread<camera_context>;
/// Shared data between cellGem and cellCamera
struct gem_camera_shared
{
gem_camera_shared() {}
gem_camera_shared(utils::serial& ar);
void save(utils::serial& ar);
SAVESTATE_INIT_POS(7);
atomic_t<u64> frame_timestamp_us{}; // latest read timestamp from cellCamera (cellCameraRead(Ex))
atomic_t<s32> width{640};
atomic_t<s32> height{480};

File diff suppressed because it is too large Load diff

View file

@ -196,7 +196,7 @@ bool cellPad_NotifyStateChange(usz index, u64 /*state*/, bool locked, bool is_bl
return true;
}
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[index];
@ -268,7 +268,7 @@ error_code cellPadInit(ppu_thread& ppu, u32 max_connect)
config.port_setting.fill(CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF);
config.reported_info = {};
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
for (usz i = 0; i < config.get_max_connect(); ++i)
@ -336,7 +336,7 @@ error_code cellPadClearBuf(u32 port_no)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -351,7 +351,7 @@ error_code cellPadClearBuf(u32 port_no)
void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false)
{
auto& config = g_fxo->get<pad_info>();
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pad = handler->GetPads()[port_no];
const PadInfo& rinfo = handler->GetInfo();
@ -709,7 +709,7 @@ error_code cellPadGetData(u32 port_no, vm::ptr<CellPadData> data)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -740,7 +740,7 @@ error_code cellPadPeriphGetInfo(vm::ptr<CellPadPeriphInfo> info)
if (!info)
return CELL_PAD_ERROR_INVALID_PARAMETER;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const PadInfo& rinfo = handler->GetInfo();
std::memset(info.get_ptr(), 0, sizeof(CellPadPeriphInfo));
@ -795,7 +795,7 @@ error_code cellPadPeriphGetData(u32 port_no, vm::ptr<CellPadPeriphData> data)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -827,7 +827,7 @@ error_code cellPadGetRawData(u32 port_no, vm::ptr<CellPadData> data)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -891,7 +891,7 @@ error_code cellPadSetActDirect(u32 port_no, vm::ptr<CellPadActParam> param)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -923,7 +923,7 @@ error_code cellPadGetInfo(vm::ptr<CellPadInfo> info)
std::memset(info.get_ptr(), 0, sizeof(CellPadInfo));
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const PadInfo& rinfo = handler->GetInfo();
info->max_connect = config.max_connect;
info->system_info = rinfo.system_info;
@ -968,7 +968,7 @@ error_code cellPadGetInfo2(vm::ptr<CellPadInfo2> info)
std::memset(info.get_ptr(), 0, sizeof(CellPadInfo2));
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const PadInfo& rinfo = handler->GetInfo();
info->max_connect = config.get_max_connect(); // Here it is forcibly clamped
info->system_info = rinfo.system_info;
@ -1018,7 +1018,7 @@ error_code cellPadGetCapabilityInfo(u32 port_no, vm::ptr<CellPadCapabilityInfo>
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -1074,7 +1074,7 @@ error_code cellPadInfoPressMode(u32 port_no)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -1101,7 +1101,7 @@ error_code cellPadInfoSensorMode(u32 port_no)
if (port_no >= config.get_max_connect())
return CELL_PAD_ERROR_NO_DEVICE;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -1129,7 +1129,7 @@ error_code cellPadSetPressMode(u32 port_no, u32 mode)
if (port_no >= CELL_PAD_MAX_PORT_NUM)
return CELL_OK;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -1163,7 +1163,7 @@ error_code cellPadSetSensorMode(u32 port_no, u32 mode)
if (port_no >= CELL_PAD_MAX_PORT_NUM)
return CELL_OK;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = pads[port_no];
@ -1190,7 +1190,7 @@ error_code cellPadLddRegisterController()
if (!config.max_connect)
return CELL_PAD_ERROR_UNINITIALIZED;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const s32 handle = handler->AddLddPad();
@ -1215,7 +1215,7 @@ error_code cellPadLddDataInsert(s32 handle, vm::ptr<CellPadData> data)
if (!config.max_connect)
return CELL_PAD_ERROR_UNINITIALIZED;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
auto& pads = handler->GetPads();
if (handle < 0 || static_cast<u32>(handle) >= pads.size() || !data) // data == NULL stalls on decr
@ -1240,7 +1240,7 @@ error_code cellPadLddGetPortNo(s32 handle)
if (!config.max_connect)
return CELL_PAD_ERROR_UNINITIALIZED;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
auto& pads = handler->GetPads();
if (handle < 0 || static_cast<u32>(handle) >= pads.size())
@ -1264,7 +1264,7 @@ error_code cellPadLddUnregisterController(s32 handle)
if (!config.max_connect)
return CELL_PAD_ERROR_UNINITIALIZED;
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
if (handle < 0 || static_cast<u32>(handle) >= pads.size())

View file

@ -221,7 +221,7 @@ error_code sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<s
opt->info = info;
// Call the syscall
return _sys_prx_get_module_info(ppu, id, 0, opt);
return _sys_prx_get_module_info(ppu, id, flags, opt);
}
error_code sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr<char> name, u64 flags, vm::ptr<sys_prx_get_module_id_by_name_option_t> pOpt)

View file

@ -96,6 +96,7 @@ struct ppu_module : public Type
std::vector<ppu_segment> segs{};
std::vector<ppu_segment> secs{};
std::vector<ppu_function> funcs{};
std::vector<u32> applied_patches;
std::deque<std::shared_ptr<void>> allocations;
std::map<u32, u32> addr_to_seg_index;
@ -185,7 +186,6 @@ struct main_ppu_module : public ppu_module<T>
{
u32 elf_entry{};
u32 seg0_code_end{};
std::vector<u32> applied_patches;
// Disable inherited savestate ordering
void save(utils::serial&) = delete;

View file

@ -1816,6 +1816,9 @@ shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c
prx->module_info_version[1] = lib_info->version[1];
prx->module_info_attributes = lib_info->attributes;
prx->imports_start = lib_info->imports_start;
prx->imports_end = lib_info->imports_end;
prx->exports_start = lib_info->exports_start;
prx->exports_end = lib_info->exports_end;
@ -1947,6 +1950,7 @@ shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c
ppu_check_patch_spu_images(*prx, seg);
}
prx->applied_patches = applied;
prx->analyse(toc, 0, end, applied, exported_funcs);
if (!ar && !virtual_load)

View file

@ -4898,6 +4898,30 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
sha1_update(&ctx, ensure(info.get_ptr<const u8>(func.addr)), func.size);
}
if (!workload.empty() && fpos >= info.funcs.size())
{
// Hash the entire function grouped addresses for the integrity of the symbol resolver function
// Potentially occuring during patches
// Avoid doing it for files with a single module such as most PRX
std::vector<be_t<u32>> addrs;
for (const ppu_function& func : info.funcs)
{
if (func.size == 0)
{
continue;
}
addrs.emplace_back(func.addr - reloc);
}
// Hash its size too
addrs.emplace_back(::size32(addrs));
sha1_update(&ctx, reinterpret_cast<const u8*>(addrs.data()), addrs.size() * sizeof(be_t<u32>));
}
if (false)
{
const be_t<u64> forced_upd = 3;

View file

@ -5481,7 +5481,7 @@ bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t<u64, 64>* range_
usz spu_thread::register_cache_line_waiter(u32 addr)
{
const u64 value = u64{compute_rdata_hash32(rdata)} << 32 | raddr;
const u64 value = u64{compute_rdata_hash32(rdata)} << 32 | addr;
for (usz i = 0; i < std::size(g_spu_waiters_by_value); i++)
{

View file

@ -122,7 +122,7 @@ void lv2_config::remove_service_event(u32 id)
lv2_config_service_event& lv2_config_service_event::operator=(thread_state s) noexcept
{
if (s == thread_state::finished)
if (s == thread_state::destroying_context && !m_destroyed.exchange(true))
{
if (auto global = g_fxo->try_get<lv2_config>())
{
@ -133,6 +133,23 @@ lv2_config_service_event& lv2_config_service_event::operator=(thread_state s) no
return *this;
}
lv2_config_service_event::~lv2_config_service_event() noexcept
{
operator=(thread_state::destroying_context);
}
lv2_config::~lv2_config() noexcept
{
for (auto& [key, event] : events)
{
if (event)
{
// Avoid collision with lv2_config_service_event destructor
event->m_destroyed = true;
}
}
}
// LV2 Config Service Listener
bool lv2_config_service_listener::check_service(const lv2_config_service& service) const
{

View file

@ -161,6 +161,8 @@ public:
return null_ptr;
}
~lv2_config() noexcept;
};
/*
@ -276,7 +278,7 @@ public:
// Utilities
usz get_size() const { return sizeof(sys_config_service_event_t)-1 + data.size(); }
shared_ptr<lv2_config_service> get_shared_ptr () const { return idm::get_unlocked<lv2_config_service>(idm_id); }
shared_ptr<lv2_config_service> get_shared_ptr () const { return stx::make_shared_from_this<lv2_config_service>(this); }
u32 get_id() const { return idm_id; }
};
@ -342,7 +344,7 @@ public:
// Utilities
u32 get_id() const { return idm_id; }
shared_ptr<lv2_config_service_listener> get_shared_ptr() const { return idm::get_unlocked<lv2_config_service_listener>(idm_id); }
shared_ptr<lv2_config_service_listener> get_shared_ptr() const { return stx::make_shared_from_this<lv2_config_service_listener>(this); }
};
/*
@ -360,6 +362,10 @@ class lv2_config_service_event
return g_fxo->get<service_event_id>().next_id++;
}
atomic_t<bool> m_destroyed = false;
friend class lv2_config;
public:
const u32 id;
@ -391,8 +397,7 @@ public:
// Destructor
lv2_config_service_event& operator=(thread_state s) noexcept;
~lv2_config_service_event() noexcept = default;
~lv2_config_service_event() noexcept;
// Notify queue that this event exists
bool notify() const;

View file

@ -420,10 +420,8 @@ error_code sys_event_queue_tryreceive(ppu_thread& ppu, u32 equeue_id, vm::ptr<sy
while (count < size && !queue->events.empty())
{
auto& dest = events[count++];
const auto event = queue->events.front();
std::tie(dest.source, dest.data1, dest.data2, dest.data3) = queue->events.front();
queue->events.pop_front();
std::tie(dest.source, dest.data1, dest.data2, dest.data3) = event;
}
lock.unlock();

View file

@ -178,7 +178,7 @@ void lv2_socket::queue_wake(ppu_thread* ppu)
lv2_socket& lv2_socket::operator=(thread_state s) noexcept
{
if (s == thread_state::finished)
if (s == thread_state::destroying_context)
{
close();
}

View file

@ -11,7 +11,6 @@ struct lv2_overlay final : ppu_module<lv2_obj>
u32 entry{};
u32 seg0_code_end{};
std::vector<u32> applied_patches;
lv2_overlay() = default;
lv2_overlay(utils::serial&){}

View file

@ -271,7 +271,7 @@ error_code _sys_process_get_paramsfo(vm::ptr<char> buffer)
{
sys_process.warning("_sys_process_get_paramsfo(buffer=0x%x)", buffer);
if (!Emu.GetTitleID().length())
if (Emu.GetTitleID().empty())
{
return CELL_ENOENT;
}
@ -498,6 +498,9 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
};
signal_system_cache_can_stay();
// Make sure we keep the game window opened
Emu.SetContinuousMode(true);
Emu.Kill(false);
});

View file

@ -1034,7 +1034,7 @@ error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<
return CELL_EFAULT;
}
if (pOpt->info->size != pOpt->info.size())
if (pOpt->info->size != pOpt->info.size() && pOpt->info_v2->size != pOpt->info_v2.size())
{
return CELL_EINVAL;
}
@ -1072,6 +1072,14 @@ error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<
pOpt->info->segments_num = i;
}
if (pOpt->info_v2->size == pOpt->info_v2.size())
{
pOpt->info_v2->exports_addr = prx->exports_start;
pOpt->info_v2->exports_size = prx->exports_end - prx->exports_start;
pOpt->info_v2->imports_addr = prx->imports_start;
pOpt->info_v2->imports_size = prx->imports_end - prx->imports_start;
}
return CELL_OK;
}
@ -1079,11 +1087,30 @@ error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr<char> name,
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt);
sys_prx.warning("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt);
//if (realName == "?") ...
std::string module_name;
if (!vm::read_string(name.addr(), 28, module_name))
{
return CELL_EINVAL;
}
return not_an_error(CELL_PRX_ERROR_UNKNOWN_MODULE);
const auto [prx, id] = idm::select<lv2_obj, lv2_prx>([&](u32 id, lv2_prx& prx) -> u32
{
if (strncmp(module_name.c_str(), prx.module_info_name, sizeof(prx.module_info_name)) == 0)
{
return id;
}
return 0;
});
if (!id)
{
return CELL_PRX_ERROR_UNKNOWN_MODULE;
}
return not_an_error(id);
}
error_code _sys_prx_get_module_id_by_address(ppu_thread& ppu, u32 addr)

View file

@ -76,10 +76,22 @@ struct sys_prx_module_info_t
be_t<u32> segments_num; // 0x44
};
struct sys_prx_module_info_v2_t : sys_prx_module_info_t
{
be_t<u32> exports_addr; // 0x48
be_t<u32> exports_size; // 0x4C
be_t<u32> imports_addr; // 0x50
be_t<u32> imports_size; // 0x54
};
struct sys_prx_module_info_option_t
{
be_t<u64> size; // 0x10
vm::bptr<sys_prx_module_info_t> info;
union
{
vm::bptr<sys_prx_module_info_t> info;
vm::bptr<sys_prx_module_info_v2_t> info_v2;
};
};
struct sys_prx_start_module_option_t
@ -192,6 +204,9 @@ struct lv2_prx final : ppu_module<lv2_obj>
u8 module_info_version[2]{};
be_t<u16> module_info_attributes{};
u32 imports_start = umax;
u32 imports_end = 0;
u32 exports_start = umax;
u32 exports_end = 0;

View file

@ -25,7 +25,7 @@ void cfg_ipc::load()
void cfg_ipc::save() const
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
const std::string path_to_cfg = fs::get_config_dir(true);
if (!fs::create_path(path_to_cfg))
{
IPC.error("Could not create path: %s", path_to_cfg);
@ -42,11 +42,7 @@ void cfg_ipc::save() const
std::string cfg_ipc::get_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/ipc.yml";
#else
return fs::get_config_dir() + "ipc.yml";
#endif
return fs::get_config_dir(true) + "ipc.yml";
}
bool cfg_ipc::get_server_enabled() const

View file

@ -805,8 +805,8 @@ public:
{
if (ptr)
{
constexpr thread_state finished{3};
*static_cast<Get*>(ptr.get()) = finished;
constexpr thread_state destroying_context{7};
*static_cast<Get*>(ptr.get()) = destroying_context;
}
}
@ -837,8 +837,8 @@ public:
{
if (ptr)
{
constexpr thread_state finished{3};
*static_cast<Get*>(ptr.get()) = finished;
constexpr thread_state destroying_context{7};
*static_cast<Get*>(ptr.get()) = destroying_context;
}
}

View file

@ -146,7 +146,7 @@ void usb_device_buzz::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/
}
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
ensure(pads.size() > m_last_controller);
ensure(g_cfg_buzz.players.size() > m_last_controller);
@ -161,7 +161,7 @@ void usb_device_buzz::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/
}
const auto& cfg = g_cfg_buzz.players[i];
cfg->handle_input(pad, true, [&buf, &index](buzz_btn btn, u16 /*value*/, bool pressed)
cfg->handle_input(pad, true, [&buf, &index](buzz_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;

View file

@ -138,7 +138,7 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint
}
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pad = ::at32(handler->GetPads(), m_controller_index);
if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED))
@ -147,7 +147,7 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint
}
const auto& cfg = ::at32(g_cfg_ghltar.players, m_controller_index);
cfg->handle_input(pad, true, [&buf](ghltar_btn btn, u16 value, bool pressed)
cfg->handle_input(pad, true, [&buf](ghltar_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;

View file

@ -195,7 +195,7 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp
{
std::lock_guard lock(pad::g_pad_mutex);
const auto gamepad_handler = pad::get_current_handler();
const auto gamepad_handler = pad::get_pad_thread();
const auto& pads = gamepad_handler->GetPads();
const auto& pad = ::at32(pads, m_controller_index);
if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)

View file

@ -227,7 +227,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
return;
}
const auto input_callback = [&gc](guncon3_btn btn, u16 value, bool pressed)
const auto input_callback = [&gc](guncon3_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;
@ -255,7 +255,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
{
std::lock_guard lock(pad::g_pad_mutex);
const auto gamepad_handler = pad::get_current_handler();
const auto gamepad_handler = pad::get_pad_thread();
const auto& pads = gamepad_handler->GetPads();
const auto& pad = ::at32(pads, m_controller_index);
if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)

View file

@ -31,12 +31,12 @@ void MouseHandlerBase::save(utils::serial& ar)
ar(inited ? m_info.max_connect : 0);
}
bool MouseHandlerBase::is_time_for_update(double elapsed_time)
bool MouseHandlerBase::is_time_for_update(double elapsed_time_ms)
{
steady_clock::time_point now = steady_clock::now();
double elapsed = (now - last_update).count() / 1000'000.;
const double elapsed_ms = (now - last_update).count() / 1'000'000.;
if (elapsed > elapsed_time)
if (elapsed_ms > elapsed_time_ms)
{
last_update = now;
return true;

View file

@ -127,7 +127,7 @@ protected:
std::vector<Mouse> m_mice;
steady_clock::time_point last_update{};
bool is_time_for_update(double elapsed_time = 10.0); // 4-10 ms, let's use 10 for now
bool is_time_for_update(double elapsed_time_ms = 10.0); // 4-10 ms, let's use 10 for now
public:
shared_mutex mutex;

View file

@ -48,6 +48,7 @@ public:
cfg->pressure_intensity_button.def = "";
cfg->analog_limiter_button.def = "";
cfg->orientation_reset_button.def = "";
// Apply defaults
cfg->from_default();

View file

@ -2,6 +2,7 @@
#include "PadHandler.h"
#include "Emu/system_utils.hpp"
#include "Emu/system_config.h"
#include "Emu/Cell/timers.hpp"
#include "Input/pad_thread.h"
#include "Input/product_info.h"
@ -494,6 +495,12 @@ bool PadHandlerBase::bindPadToDevice(std::shared_ptr<Pad> pad)
pad->m_analog_limiter_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
}
if (b_has_orientation)
{
pad->m_buttons.emplace_back(special_button_offset, mapping[button::orientation_reset_button], special_button_value::orientation_reset);
pad->m_orientation_reset_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
}
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::up], CELL_PAD_CTRL_UP);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::down], CELL_PAD_CTRL_DOWN);
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::left], CELL_PAD_CTRL_LEFT);
@ -600,6 +607,11 @@ std::array<std::set<u32>, PadHandlerBase::button::button_count> PadHandlerBase::
mapping[button::analog_limiter_button] = FindKeyCodes<u32, u32>(button_list, cfg->analog_limiter_button);
}
if (b_has_orientation)
{
mapping[button::orientation_reset_button] = FindKeyCodes<u32, u32>(button_list, cfg->orientation_reset_button);
}
return mapping;
}
@ -739,6 +751,8 @@ void PadHandlerBase::process()
if (!device || !pad)
continue;
pad->move_data.orientation_enabled = b_has_orientation && device->config && device->config->orientation_enabled.get();
const connection status = update_connection(device);
switch (status)
@ -754,6 +768,11 @@ void PadHandlerBase::process()
last_connection_status[i] = true;
connected_devices++;
if (b_has_orientation)
{
device->reset_orientation();
}
}
if (status == connection::no_data)
@ -790,6 +809,11 @@ void PadHandlerBase::process()
last_connection_status[i] = false;
connected_devices--;
if (b_has_orientation)
{
device->reset_orientation();
}
}
continue;
}
@ -797,6 +821,142 @@ void PadHandlerBase::process()
get_mapping(m_bindings[i]);
get_extended_info(m_bindings[i]);
get_orientation(m_bindings[i]);
apply_pad_data(m_bindings[i]);
}
}
void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z)
{
if (!move_data.orientation_enabled)
{
move_data.reset_sensors();
return;
}
// This function expects DS3 sensor accel values in linear velocity (m/s²) and gyro values in angular velocity (degree/s)
// The default position is flat on the ground, pointing forward.
// The accelerometers constantly measure G forces.
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left
move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards
move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards
move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
}
void PadHandlerBase::set_raw_orientation(Pad& pad)
{
if (!pad.move_data.orientation_enabled)
{
pad.move_data.reset_sensors();
return;
}
// acceleration (linear velocity in m/s²)
const f32 accel_x = (pad.m_sensors[0].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
const f32 accel_y = (pad.m_sensors[1].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
const f32 accel_z = (pad.m_sensors[2].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
// gyro (angular velocity in degree/s)
constexpr f32 gyro_x = 0.0f;
const f32 gyro_y = (pad.m_sensors[3].m_value - 512) / (123.f / 90.f);
constexpr f32 gyro_z = 0.0f;
set_raw_orientation(pad.move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z);
}
void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
{
if (!b_has_orientation) return;
const auto& pad = binding.pad;
const auto& device = binding.device;
if (!pad || !device) return;
if (pad->move_data.calibration_requested)
{
device->reset_orientation();
pad->move_data.quaternion = ps_move_data::default_quaternion;
pad->move_data.calibration_succeeded = true;
return;
}
if (!pad->move_data.orientation_enabled || pad->get_orientation_reset_button_active())
{
// This can be called extensively in quick succession, so let's just reset the pointer instead of creating a new object.
device->ahrs.reset();
pad->move_data.quaternion = ps_move_data::default_quaternion;
return;
}
device->update_orientation(pad->move_data);
}
void PadDevice::reset_orientation()
{
// Initialize Fusion
ahrs = std::make_shared<FusionAhrs>();
FusionAhrsInitialise(ahrs.get());
ahrs->settings.convention = FusionConvention::FusionConventionEnu;
ahrs->settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
FusionAhrsSetSettings(ahrs.get(), &ahrs->settings);
FusionAhrsReset(ahrs.get());
}
void PadDevice::update_orientation(ps_move_data& move_data)
{
if (!ahrs)
{
reset_orientation();
}
// Get elapsed time since last update
const u64 now_us = get_system_time();
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
last_ahrs_update_time_us = now_us;
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
// Don't ask how the axis work. It's basically been trial and error.
ensure(ahrs->settings.convention == FusionConvention::FusionConventionEnu); // East-North-Up
const FusionVector accelerometer{
.axis {
.x = -move_data.accelerometer_x,
.y = +move_data.accelerometer_y,
.z = +move_data.accelerometer_z
}
};
const FusionVector gyroscope{
.axis {
.x = +PadHandlerBase::rad_to_degree(move_data.gyro_x),
.y = +PadHandlerBase::rad_to_degree(move_data.gyro_z),
.z = -PadHandlerBase::rad_to_degree(move_data.gyro_y)
}
};
FusionVector magnetometer {};
if (move_data.magnetometer_enabled)
{
magnetometer = FusionVector{
.axis {
.x = move_data.magnetometer_x,
.y = move_data.magnetometer_y,
.z = move_data.magnetometer_z
}
};
}
// Update Fusion
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, magnetometer, elapsed_sec);
// Get quaternion
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(ahrs.get());
move_data.quaternion[0] = quaternion.array[1];
move_data.quaternion[1] = quaternion.array[2];
move_data.quaternion[2] = quaternion.array[3];
move_data.quaternion[3] = quaternion.array[0];
}

View file

@ -5,6 +5,15 @@
#include "pad_config_types.h"
#include "util/types.hpp"
#ifndef _MSC_VER
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include "3rdparty/fusion/fusion/Fusion/Fusion.h"
#ifndef _MSC_VER
#pragma GCC diagnostic pop
#endif
#include <cmath>
#include <functional>
#include <string>
@ -38,6 +47,12 @@ public:
};
color color_override{};
bool color_override_active{};
std::shared_ptr<FusionAhrs> ahrs; // Used to calculate quaternions from sensor data
u64 last_ahrs_update_time_us = 0; // Last ahrs update
void update_orientation(ps_move_data& move_data);
void reset_orientation();
};
struct pad_ensemble
@ -125,6 +140,7 @@ protected:
pressure_intensity_button,
analog_limiter_button,
orientation_reset_button,
button_count
};
@ -153,6 +169,7 @@ protected:
bool b_has_config = false;
bool b_has_pressure_intensity_button = true;
bool b_has_analog_limiter_button = true;
bool b_has_orientation = false;
std::array<cfg_pad, MAX_GAMEPADS> m_pad_configs;
std::vector<pad_ensemble> m_bindings;
@ -301,6 +318,7 @@ public:
bool has_battery_led() const { return b_has_battery_led; }
bool has_pressure_intensity_button() const { return b_has_pressure_intensity_button; }
bool has_analog_limiter_button() const { return b_has_analog_limiter_button; }
bool has_orientation() const { return b_has_orientation; }
u16 NormalizeStickInput(u16 raw_value, s32 threshold, s32 multiplier, bool ignore_threshold = false) const;
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const;
@ -323,6 +341,18 @@ public:
virtual void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array<AnalogSensor, 4>& sensors);
virtual std::unordered_map<u32, std::string> get_motion_axis_list() const { return {}; }
static constexpr f32 PI = 3.14159265f;
static f32 degree_to_rad(f32 degree)
{
return degree * PI / 180.0f;
}
static f32 rad_to_degree(f32 radians)
{
return radians * 180.0f / PI;
};
private:
virtual std::shared_ptr<PadDevice> get_device(const std::string& /*device*/) { return nullptr; }
virtual bool get_is_left_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 /*keyCode*/) { return false; }
@ -336,10 +366,15 @@ private:
virtual std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& /*device*/) { return {}; }
virtual pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& /*data*/) { return {}; }
void get_orientation(const pad_ensemble& binding) const;
protected:
virtual std::array<std::set<u32>, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const cfg_pad* cfg);
virtual void get_mapping(const pad_ensemble& binding);
void TranslateButtonPress(const std::shared_ptr<PadDevice>& device, u64 keyCode, bool& pressed, u16& val, bool use_stick_multipliers, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false);
void init_configs();
cfg_pad* get_config(const std::string& pad_id);
static void set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z);
static void set_raw_orientation(Pad& pad);
};

View file

@ -280,7 +280,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
}
bool up = false, right = false, down = false, left = false;
const auto input_callback = [&ts, &up, &down, &left, &right](topshotelite_btn btn, u16 value, bool pressed)
const auto input_callback = [&ts, &up, &down, &left, &right](topshotelite_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;
@ -315,7 +315,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
{
std::lock_guard lock(pad::g_pad_mutex);
const auto gamepad_handler = pad::get_current_handler();
const auto gamepad_handler = pad::get_pad_thread();
const auto& pads = gamepad_handler->GetPads();
const auto& pad = ::at32(pads, m_controller_index);
if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)

View file

@ -308,7 +308,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
}
bool up = false, right = false, down = false, left = false;
const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, u16 value, bool pressed)
const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;
@ -339,7 +339,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
{
std::lock_guard lock(pad::g_pad_mutex);
const auto gamepad_handler = pad::get_current_handler();
const auto gamepad_handler = pad::get_pad_thread();
const auto& pads = gamepad_handler->GetPads();
const auto& pad = ::at32(pads, m_controller_index);
if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)

View file

@ -151,7 +151,7 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo
// All other bufs are always 0x00
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const auto& pads = handler->GetPads();
const auto& pad = ::at32(pads, m_controller_index);
@ -159,7 +159,7 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo
return;
const auto& cfg = ::at32(g_cfg_turntable.players, m_controller_index);
cfg->handle_input(pad, true, [&buf](turntable_btn btn, u16 value, bool pressed)
cfg->handle_input(pad, true, [&buf](turntable_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/)
{
if (!pressed)
return;

View file

@ -8,11 +8,7 @@ cfg_camera g_cfg_camera;
cfg_camera::cfg_camera()
: cfg::node()
#ifdef _WIN32
, path(fs::get_config_dir() + "config/camera.yml")
#else
, path(fs::get_config_dir() + "camera.yml")
#endif
, path(fs::get_config_dir(true) + "camera.yml")
{
}

View file

@ -88,7 +88,7 @@ public:
button_map.clear();
}
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(T, u16, bool)>& func) const
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(T, pad_button, u16, bool, bool&)>& func) const
{
if (!pad)
return;
@ -97,19 +97,25 @@ public:
{
if (button.m_pressed || !press_only)
{
handle_input(func, button.m_offset, button.m_outKeyCode, button.m_value, button.m_pressed, true);
if (handle_input(func, button.m_offset, button.m_outKeyCode, button.m_value, button.m_pressed, true))
{
return;
}
}
}
for (const AnalogStick& stick : pad->m_sticks)
{
handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true);
if (handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true))
{
return;
}
}
}
void handle_input(const Mouse& mouse, const std::function<void(T, u16, bool)>& func) const
void handle_input(const Mouse& mouse, const std::function<void(T, pad_button, u16, bool, bool&)>& func) const
{
for (int i = 0; i < 7; i++)
for (int i = 0; i < 8; i++)
{
const MouseButtonCodes cell_code = get_mouse_button_code(i);
if ((mouse.buttons & cell_code))
@ -117,7 +123,11 @@ public:
const pad_button button = static_cast<pad_button>(static_cast<int>(pad_button::mouse_button_1) + i);
const u32 offset = pad_button_offset(button);
const u32 keycode = pad_button_keycode(button);
handle_input(func, offset, keycode, 255, true, true);
if (handle_input(func, offset, keycode, 255, true, true))
{
return;
}
}
}
}
@ -163,10 +173,12 @@ protected:
return empty_set;
}
void handle_input(const std::function<void(T, u16, bool)>& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const
bool handle_input(const std::function<void(T, pad_button, u16, bool, bool&)>& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const
{
m_mutex.lock();
bool abort = false;
const auto& btns = find_button(offset, keycode);
if (btns.empty())
{
@ -180,24 +192,26 @@ protected:
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y:
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X:
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y:
handle_input(func, offset, static_cast<u32>(axis_direction::both), value, pressed, false);
abort = handle_input(func, offset, static_cast<u32>(axis_direction::both), value, pressed, false);
break;
default:
break;
}
}
return;
return abort;
}
for (const auto& btn : btns)
{
if (btn && func)
{
func(btn->btn_id(), value, pressed);
func(btn->btn_id(), btn->get(), value, pressed, abort);
if (abort) break;
}
}
m_mutex.unlock();
return abort;
}
};
@ -221,7 +235,7 @@ struct emulated_pads_config : cfg::node
m_mutex.lock();
bool result = false;
const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id);
const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id);
cfg_log.notice("Loading %s config: %s", cfg_id, cfg_name);
from_default();
@ -258,7 +272,7 @@ struct emulated_pads_config : cfg::node
{
std::lock_guard lock(m_mutex);
const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id);
const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id);
cfg_log.notice("Saving %s config to '%s'", cfg_id, cfg_name);
if (!fs::create_path(fs::get_parent_dir(cfg_name)))

View file

@ -4,7 +4,7 @@
#include <array>
enum class gem_btn
enum class gem_btn : u32
{
start,
select,
@ -17,6 +17,18 @@ enum class gem_btn
x_axis,
y_axis,
combo_begin,
combo = combo_begin,
combo_start,
combo_select,
combo_triangle,
combo_circle,
combo_cross,
combo_square,
combo_move,
combo_t,
combo_end = combo_t,
count
};
@ -41,6 +53,34 @@ struct cfg_fake_gems final : public emulated_pads_config<cfg_fake_gem, 4>
cfg_fake_gems() : emulated_pads_config<cfg_fake_gem, 4>("gem") {};
};
struct cfg_mouse_gem final : public emulated_pad_config<gem_btn>
{
cfg_mouse_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
cfg_pad_btn<gem_btn> start{ this, "Start", gem_btn::start, pad_button::mouse_button_6 };
cfg_pad_btn<gem_btn> select{ this, "Select", gem_btn::select, pad_button::mouse_button_7 };
cfg_pad_btn<gem_btn> triangle{ this, "Triangle", gem_btn::triangle, pad_button::mouse_button_8 };
cfg_pad_btn<gem_btn> circle{ this, "Circle", gem_btn::circle, pad_button::mouse_button_4 };
cfg_pad_btn<gem_btn> cross{ this, "Cross", gem_btn::cross, pad_button::mouse_button_5 };
cfg_pad_btn<gem_btn> square{ this, "Square", gem_btn::square, pad_button::mouse_button_3 };
cfg_pad_btn<gem_btn> move{ this, "Move", gem_btn::move, pad_button::mouse_button_2 };
cfg_pad_btn<gem_btn> t{ this, "T", gem_btn::t, pad_button::mouse_button_1 };
cfg_pad_btn<gem_btn> combo{ this, "Combo", gem_btn::combo, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_start{ this, "Combo Start", gem_btn::combo_start, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_select{ this, "Combo Select", gem_btn::combo_select, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_triangle{ this, "Combo Triangle", gem_btn::combo_triangle, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_circle{ this, "Combo Circle", gem_btn::combo_circle, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_cross{ this, "Combo Cross", gem_btn::combo_cross, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_square{ this, "Combo Square", gem_btn::combo_square, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_move{ this, "Combo Move", gem_btn::combo_move, pad_button::pad_button_max_enum };
cfg_pad_btn<gem_btn> combo_t{ this, "Combo T", gem_btn::combo_t, pad_button::pad_button_max_enum };
};
struct cfg_mouse_gems final : public emulated_pads_config<cfg_mouse_gem, 4>
{
cfg_mouse_gems() : emulated_pads_config<cfg_mouse_gem, 4>("gem_mouse") {};
};
struct cfg_gem final : public emulated_pad_config<gem_btn>
{
cfg_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
@ -62,3 +102,4 @@ struct cfg_gems final : public emulated_pads_config<cfg_gem, 4>
extern cfg_gems g_cfg_gem_real;
extern cfg_fake_gems g_cfg_gem_fake;
extern cfg_mouse_gems g_cfg_gem_mouse;

View file

@ -4,11 +4,7 @@
#include "Utilities/File.h"
mouse_config::mouse_config()
#ifdef _WIN32
: cfg_name(fs::get_config_dir() + "config/config_mouse.yml")
#else
: cfg_name(fs::get_config_dir() + "config_mouse.yml")
#endif
: cfg_name(fs::get_config_dir(true) + "config_mouse.yml")
{
}

View file

@ -32,6 +32,20 @@ std::string cfg_pad::get_buttons(std::vector<std::string> vec)
return fmt::merge(vec, ",");
}
u8 cfg_pad::get_large_motor_speed(const std::array<VibrateMotor, 2>& motor_speed) const
{
const u8 idx = switch_vibration_motors ? 1 : 0;
const f32 multiplier = multiplier_vibration_motor_large / 100.0f;
return static_cast<u8>(std::clamp(motor_speed[idx].m_value * multiplier, 0.0f, 255.0f));
}
u8 cfg_pad::get_small_motor_speed(const std::array<VibrateMotor, 2>& motor_speed) const
{
const u8 idx = switch_vibration_motors ? 0 : 1;
const f32 multiplier = multiplier_vibration_motor_small / 100.0f;
return static_cast<u8>(std::clamp(motor_speed[idx].m_value * multiplier, 0.0f, 255.0f));
}
bool cfg_input::load(const std::string& title_id, const std::string& config_file, bool strict)
{
input_log.notice("Loading pad config (title_id='%s', config_file='%s', strict=%d)", title_id, config_file, strict);

View file

@ -28,6 +28,9 @@ struct cfg_pad final : cfg::node
static std::vector<std::string> get_buttons(const std::string& str);
static std::string get_buttons(std::vector<std::string> vec);
u8 get_large_motor_speed(const std::array<VibrateMotor, 2>& motor_speed) const;
u8 get_small_motor_speed(const std::array<VibrateMotor, 2>& motor_speed) const;
cfg::string ls_left{ this, "Left Stick Left", "" };
cfg::string ls_down{ this, "Left Stick Down", "" };
cfg::string ls_right{ this, "Left Stick Right", "" };
@ -66,6 +69,9 @@ struct cfg_pad final : cfg::node
cfg_sensor motion_sensor_z{ this, "Motion Sensor Z" };
cfg_sensor motion_sensor_g{ this, "Motion Sensor G" };
cfg::string orientation_reset_button{ this, "Orientation Reset Button", "" };
cfg::_bool orientation_enabled{ this, "Orientation Enabled", false };
cfg::string pressure_intensity_button{ this, "Pressure Intensity Button", "" };
cfg::uint<0, 100> pressure_intensity{ this, "Pressure Intensity Percent", 50 };
cfg::_bool pressure_intensity_toggle_mode{ this, "Pressure Intensity Toggle Mode", false };
@ -93,8 +99,8 @@ struct cfg_pad final : cfg::node
cfg::uint<0, 100> led_battery_indicator_brightness{ this, "LED battery indicator brightness", 50 };
cfg::_bool player_led_enabled{ this, "Player LED enabled", true };
cfg::_bool enable_vibration_motor_large{ this, "Enable Large Vibration Motor", true };
cfg::_bool enable_vibration_motor_small{ this, "Enable Small Vibration Motor", true };
cfg::uint<0, 200> multiplier_vibration_motor_large{ this, "Large Vibration Motor Multiplier", 100 };
cfg::uint<0, 200> multiplier_vibration_motor_small{ this, "Small Vibration Motor Multiplier", 100 };
cfg::_bool switch_vibration_motors{ this, "Switch Vibration Motors", false };
cfg::_enum<mouse_movement_mode> mouse_move_mode{ this, "Mouse Movement Mode", mouse_movement_mode::relative };

View file

@ -39,7 +39,7 @@ void fmt_class_string<pad_button>::format(std::string& out, u64 arg)
case pad_button::rs_right: return "Right Stick Right";
case pad_button::rs_x: return "Right Stick X-Axis";
case pad_button::rs_y: return "Right Stick Y-Axis";
case pad_button::pad_button_max_enum: return "MAX_ENUM";
case pad_button::pad_button_max_enum: return "";
case pad_button::mouse_button_1: return "Mouse Button 1";
case pad_button::mouse_button_2: return "Mouse Button 2";
case pad_button::mouse_button_3: return "Mouse Button 3";
@ -159,6 +159,20 @@ u32 get_axis_keycode(u32 offset, u16 value)
}
}
void ps_move_data::reset_sensors()
{
quaternion = default_quaternion;
accelerometer_x = 0.0f;
accelerometer_y = 0.0f;
accelerometer_z = 0.0f;
gyro_x = 0.0f;
gyro_y = 0.0f;
gyro_z = 0.0f;
magnetometer_x = 0.0f;
magnetometer_y = 0.0f;
magnetometer_z = 0.0f;
}
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
{
if (m_pressure_intensity_button_index < 0)
@ -238,3 +252,13 @@ bool Pad::get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id)
return analog_limiter_button.m_pressed;
}
bool Pad::get_orientation_reset_button_active()
{
if (m_orientation_reset_button_index < 0)
{
return false;
}
return m_buttons[m_orientation_reset_button_index].m_pressed;
}

View file

@ -365,7 +365,8 @@ constexpr u32 special_button_offset = 666; // Must not conflict with other CELL
enum special_button_value
{
pressure_intensity,
analog_limiter
analog_limiter,
orientation_reset
};
struct Button
@ -470,8 +471,10 @@ struct ps_move_data
bool calibration_succeeded = false;
bool magnetometer_enabled = false;
bool orientation_enabled = false;
std::array<f32, 4> quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
static constexpr std::array<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
std::array<f32, 4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
f32 accelerometer_x = 0.0f; // linear velocity in m/s²
f32 accelerometer_y = 0.0f; // linear velocity in m/s²
f32 accelerometer_z = 0.0f; // linear velocity in m/s²
@ -482,6 +485,8 @@ struct ps_move_data
f32 magnetometer_y = 0.0f;
f32 magnetometer_z = 0.0f;
s16 temperature = 0;
void reset_sensors();
};
struct Pad
@ -512,6 +517,9 @@ struct Pad
bool m_analog_limiter_enabled_last{}; // only used in keyboard_pad_handler
bool get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id);
s32 m_orientation_reset_button_index{-1}; // Special button index. -1 if not set.
bool get_orientation_reset_button_active();
// Cable State: 0 - 1 plugged in ?
u8 m_cable_state{0};

View file

@ -8,13 +8,7 @@ cfg_rb3drums g_cfg_rb3drums;
cfg_rb3drums::cfg_rb3drums()
: cfg::node()
#ifdef _WIN32
,
path(fs::get_config_dir() + "config/rb3drums.yml")
#else
,
path(fs::get_config_dir() + "rb3drums.yml")
#endif
, path(fs::get_config_dir(true) + "rb3drums.yml")
{
}

View file

@ -8,11 +8,7 @@ cfg_recording g_cfg_recording;
cfg_recording::cfg_recording()
: cfg::node()
#ifdef _WIN32
, path(fs::get_config_dir() + "config/recording.yml")
#else
, path(fs::get_config_dir() + "recording.yml")
#endif
, path(fs::get_config_dir(true) + "recording.yml")
{
}

View file

@ -189,7 +189,7 @@ void usb_device_usio::save_backup()
void usb_device_usio::translate_input_taiko()
{
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
std::vector<u8> input_buf(0x60);
constexpr le_t<u16> c_hit = 0x1800;
@ -203,7 +203,7 @@ void usb_device_usio::translate_input_taiko()
if (const auto& pad = ::at32(handler->GetPads(), pad_number); (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && is_input_allowed())
{
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
cfg->handle_input(pad, false, [&](usio_btn btn, u16 /*value*/, bool pressed)
cfg->handle_input(pad, false, [&](usio_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
{
switch (btn)
{
@ -273,7 +273,7 @@ void usb_device_usio::translate_input_taiko()
void usb_device_usio::translate_input_tekken()
{
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
std::vector<u8> input_buf(0x180);
le_t<u64> digital_input[2]{};
@ -288,7 +288,7 @@ void usb_device_usio::translate_input_tekken()
if (const auto& pad = ::at32(handler->GetPads(), pad_number); (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && is_input_allowed())
{
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
cfg->handle_input(pad, false, [&](usio_btn btn, u16 /*value*/, bool pressed)
cfg->handle_input(pad, false, [&](usio_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
{
switch (btn)
{

View file

@ -59,11 +59,7 @@ namespace np
{
std::string get_players_history_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/players_history.yml";
#else
return fs::get_config_dir() + "players_history.yml";
#endif
return fs::get_config_dir(true) + "players_history.yml";
}
std::map<std::string, player_history> load_players_history()
@ -1440,7 +1436,7 @@ namespace np
void np_handler::save_players_history()
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
const std::string path_to_cfg = fs::get_config_dir(true);
if (!fs::create_path(path_to_cfg))
{
nph_log.error("Could not create path: %s", path_to_cfg);

View file

@ -34,7 +34,7 @@ void cfg_rpcn::load()
void cfg_rpcn::save() const
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
const std::string path_to_cfg = fs::get_config_dir(true);
if (!fs::create_path(path_to_cfg))
{
rpcn_log.error("Could not create path: %s", path_to_cfg);
@ -51,11 +51,7 @@ void cfg_rpcn::save() const
std::string cfg_rpcn::get_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/rpcn.yml";
#else
return fs::get_config_dir() + "rpcn.yml";
#endif
return fs::get_config_dir(true) + "rpcn.yml";
}
std::string cfg_rpcn::generate_npid()

View file

@ -24,7 +24,7 @@ void cfg_upnp::load()
void cfg_upnp::save() const
{
#ifdef _WIN32
const std::string path_to_cfg = fs::get_config_dir() + "config/";
const std::string path_to_cfg = fs::get_config_dir(true);
if (!fs::create_path(path_to_cfg))
{
upnp_cfg_log.error("Could not create path: %s", path_to_cfg);
@ -51,9 +51,5 @@ void cfg_upnp::set_device_url(std::string_view url)
std::string cfg_upnp::get_path()
{
#ifdef _WIN32
return fs::get_config_dir() + "config/upnp.yml";
#else
return fs::get_config_dir() + "upnp.yml";
#endif
return fs::get_config_dir(true) + "upnp.yml";
}

View file

@ -427,5 +427,17 @@ namespace rsx
}
return result;
}
template <typename F, typename U>
requires std::is_invocable_r_v<U, F, const U&, const Ty&>
U reduce(U initial_value, F&& reducer) const
{
U accumulate = initial_value;
for (auto it = begin(); it != end(); ++it)
{
accumulate = reducer(accumulate, *it);
}
return accumulate;
}
};
}

View file

@ -18,7 +18,21 @@ namespace rsx
case surface_target::surfaces_a_b_c: return{ 0, 1, 2 };
case surface_target::surfaces_a_b_c_d: return{ 0, 1, 2, 3 };
}
fmt::throw_exception("Wrong color_target");
fmt::throw_exception("Invalid color target %d", static_cast<int>(color_target));
}
u8 get_mrt_buffers_count(surface_target color_target)
{
switch (color_target)
{
case surface_target::none: return 0;
case surface_target::surface_a: return 1;
case surface_target::surface_b: return 1;
case surface_target::surfaces_a_b: return 2;
case surface_target::surfaces_a_b_c: return 3;
case surface_target::surfaces_a_b_c_d: return 4;
}
fmt::throw_exception("Invalid color target %d", static_cast<int>(color_target));
}
usz get_aligned_pitch(surface_color_format format, u32 width)

View file

@ -17,6 +17,7 @@ namespace rsx
namespace utility
{
std::vector<u8> get_rtt_indexes(surface_target color_target);
u8 get_mrt_buffers_count(surface_target color_target);
usz get_aligned_pitch(surface_color_format format, u32 width);
usz get_packed_pitch(surface_color_format format, u32 width);
}

View file

@ -735,7 +735,7 @@ namespace rsx
utils::stream_vector(dst + 4, 0u, fog_mode, std::bit_cast<u32>(wpos_scale), std::bit_cast<u32>(wpos_bias));
}
void draw_command_processor::fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase& prog) const
void draw_command_processor::fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase* prog) const
{
auto& draw_call = REGS(m_ctx)->current_draw_clause;
@ -745,8 +745,9 @@ namespace rsx
// Temp indirection table. Used to track "running" updates.
rsx::simple_array<u32> instancing_indirection_table;
// indirection table size
const auto reloc_table = prog.has_indexed_constants ? decltype(prog.constant_ids){} : prog.constant_ids;
const auto redirection_table_size = prog.has_indexed_constants ? 468u : ::size32(prog.constant_ids);
const auto full_reupload = !prog || prog->has_indexed_constants;
const auto reloc_table = full_reupload ? decltype(prog->constant_ids){} : prog->constant_ids;
const auto redirection_table_size = full_reupload ? 468u : ::size32(prog->constant_ids);
instancing_indirection_table.resize(redirection_table_size);
// Temp constants data
@ -787,9 +788,9 @@ namespace rsx
continue;
}
const int translated_offset = prog.has_indexed_constants
const int translated_offset = full_reupload
? instance_config.patch_load_offset
: prog.TranslateConstantsRange(instance_config.patch_load_offset, instance_config.patch_load_count);
: prog->translate_constants_range(instance_config.patch_load_offset, instance_config.patch_load_count);
if (translated_offset >= 0)
{
@ -809,14 +810,14 @@ namespace rsx
continue;
}
ensure(!prog.has_indexed_constants);
ensure(!full_reupload);
// Sparse update. Update records individually instead of bulk
// FIXME: Range batching optimization
const auto load_end = instance_config.patch_load_offset + instance_config.patch_load_count;
for (u32 i = 0; i < redirection_table_size; ++i)
{
const auto read_index = prog.constant_ids[i];
const auto read_index = prog->constant_ids[i];
if (read_index < instance_config.patch_load_offset || read_index >= load_end)
{
// Reading outside "hot" range.

View file

@ -105,6 +105,6 @@ namespace rsx
// Fill instancing buffers. A single iobuf is used for both. 256byte alignment enforced to allow global bind
// Returns offsets to the index redirection lookup table and constants field array
void fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase& prog) const;
void fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase* prog) const;
};
}

View file

@ -39,7 +39,7 @@ u64 GLGSRender::get_cycles()
GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar)
{
m_shaders_cache = std::make_unique<gl::shader_cache>(m_prog_buffer, "opengl", "v1.94");
m_shaders_cache = std::make_unique<gl::shader_cache>(m_prog_buffer, "opengl", "v1.95");
if (g_cfg.video.disable_vertex_cache)
m_vertex_cache = std::make_unique<gl::null_vertex_cache>();
@ -52,6 +52,14 @@ GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar)
backend_config.supports_normalized_barycentrics = true;
}
GLGSRender::~GLGSRender()
{
if (m_frame)
{
m_frame->reset();
}
}
extern CellGcmContextData current_context;
void GLGSRender::set_viewport()
@ -870,7 +878,7 @@ void GLGSRender::load_program_env()
}
}
if (update_fragment_constants && !update_instruction_buffers)
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
{
// Fragment constants
auto mapping = m_fragment_constants_buffer->alloc_from_heap(fragment_constants_size, m_uniform_buffer_offset_align);
@ -970,12 +978,23 @@ void GLGSRender::load_program_env()
}
}
m_graphics_state.clear(
rsx::flags32_t handled_flags =
rsx::pipeline_state::fragment_state_dirty |
rsx::pipeline_state::vertex_state_dirty |
rsx::pipeline_state::transform_constants_dirty |
rsx::pipeline_state::fragment_constants_dirty |
rsx::pipeline_state::fragment_texture_state_dirty);
rsx::pipeline_state::fragment_texture_state_dirty;
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
{
handled_flags |= rsx::pipeline_state::fragment_constants_dirty;
}
m_graphics_state.clear(handled_flags);
}
bool GLGSRender::is_current_program_interpreted() const
{
return m_program && m_shader_interpreter.is_interpreter(m_program);
}
void GLGSRender::upload_transform_constants(const rsx::io_buffer& buffer)
@ -1026,13 +1045,19 @@ void GLGSRender::update_vertex_env(const gl::vertex_upload_info& upload_info)
void GLGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 count)
{
if (!m_vertex_prog)
if (!m_program || !m_vertex_prog)
{
// Shouldn't be reachable, but handle it correctly anyway
m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
return;
}
if (!m_vertex_prog->overlaps_constants_range(index, count))
{
// Nothing meaningful to us
return;
}
std::pair<u32, u32> data_range {};
void* data_source = nullptr;
const auto bound_range = m_transform_constants_buffer->bound_range();
@ -1046,7 +1071,7 @@ void GLGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou
data_range = { bound_range.first + byte_offset, byte_count};
data_source = &REGS(ctx)->transform_constants[index];
}
else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0)
else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0)
{
const auto write_offset = xform_id * 16;
const auto byte_count = count * 16;

View file

@ -159,6 +159,7 @@ public:
GLGSRender(utils::serial* ar) noexcept;
GLGSRender() noexcept : GLGSRender(nullptr) {}
virtual ~GLGSRender();
private:
@ -205,6 +206,9 @@ public:
// GRAPH backend
void patch_transform_constants(rsx::context* ctx, u32 index, u32 count) override;
// Misc
bool is_current_program_interpreted() const override;
protected:
void clear_surface(u32 arg) override;
void begin() override;

View file

@ -347,7 +347,7 @@ namespace gl
return data;
}
bool shader_interpreter::is_interpreter(const glsl::program* program)
bool shader_interpreter::is_interpreter(const glsl::program* program) const
{
return (program == &m_current_interpreter->prog);
}

View file

@ -84,6 +84,6 @@ namespace gl
void update_fragment_textures(const std::array<std::unique_ptr<rsx::sampled_image_descriptor_base>, 16>& descriptors, u16 reference_mask, u32* out);
glsl::program* get(const interpreter::program_metadata& fp_metadata);
bool is_interpreter(const glsl::program* program);
bool is_interpreter(const glsl::program* program) const;
};
}

View file

@ -14,6 +14,7 @@ public:
virtual ~GSFrameBase() = default;
virtual void close() = 0;
virtual void reset() = 0;
virtual bool shown() = 0;
virtual void hide() = 0;
virtual void show() = 0;

View file

@ -18,7 +18,7 @@ GSRender::~GSRender()
{
m_context = nullptr;
if (m_frame)
if (m_frame && !m_continuous_mode)
{
m_frame->close();
}
@ -39,7 +39,10 @@ void GSRender::on_exit()
if (m_frame)
{
m_frame->hide();
if (!m_continuous_mode)
{
m_frame->hide();
}
m_frame->delete_context(m_context);
m_context = nullptr;
}

View file

@ -21,12 +21,15 @@ class GSRender : public rsx::thread
protected:
GSFrameBase* m_frame;
draw_context_t m_context = nullptr;
bool m_continuous_mode = false;
public:
~GSRender() override;
GSRender(utils::serial* ar) noexcept;
void set_continuous_mode(bool continuous_mode) { m_continuous_mode = continuous_mode; }
void on_init_thread() override;
void on_exit() override;

View file

@ -114,6 +114,8 @@ namespace rsx
Emu.CallFromMainThread([]()
{
// Make sure we keep the game window opened
Emu.SetContinuousMode(true);
Emu.Restart(false);
});
return page_navigation::exit;

View file

@ -26,6 +26,9 @@ namespace rsx
if (!suspend_mode)
{
Emu.after_kill_callback = []() { Emu.Restart(); };
// Make sure we keep the game window opened
Emu.SetContinuousMode(true);
}
Emu.Kill(false, true);
});

View file

@ -265,7 +265,6 @@ namespace rsx
fade_animation.end = color4f(1.f);
fade_animation.active = true;
this->on_close = std::move(on_close);
visible = true;
const auto notify = std::make_shared<atomic_t<u32>>(0);

View file

@ -193,7 +193,7 @@ namespace rsx
// Get gamepad input
std::lock_guard lock(pad::g_pad_mutex);
const auto handler = pad::get_current_handler();
const auto handler = pad::get_pad_thread();
const PadInfo& rinfo = handler->GetInfo();
const bool ignore_gamepad_input = (!rinfo.now_connect || !input::g_pads_intercepted);

View file

@ -22,6 +22,15 @@ namespace rsx
using namespace rsx::fragment_program;
// SIMD vector lanes
enum VectorLane : u8
{
X = 0,
Y = 1,
Z = 2,
W = 3,
};
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
: m_size(size)
, m_prog(prog)
@ -141,8 +150,7 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
AddCode(m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "cc" + std::to_string(src0.cond_mod_reg_index)) + "$m = " + dest + ";");
}
u32 reg_index = dst.fp16 ? dst.dest_reg >> 1 : dst.dest_reg;
const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg;
ensure(reg_index < temp_registers.size());
if (dst.opcode == RSX_FP_OPCODE_MOV &&
@ -754,14 +762,26 @@ std::string FragmentProgramDecompiler::BuildCode()
const std::string init_value = float4_type + "(0.)";
std::array<std::string, 4> output_register_names;
std::array<u32, 4> ouput_register_indices = { 0, 2, 3, 4 };
bool shader_is_valid = false;
// Holder for any "cleanup" before exiting main
std::stringstream main_epilogue;
// Check depth export
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
{
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
shader_is_valid = (!!temp_registers[1].h1_writes);
auto& r1 = temp_registers[1];
if (r1.requires_gather(VectorLane::Z))
{
// r1.zw was not written to
properties.has_gather_op = true;
main_epilogue << " r1.z = " << float4_type << r1.gather_r() << ".z;\n";
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from shader depth without writing to it. Final value will be gathered.");
}
}
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
@ -775,28 +795,49 @@ std::string FragmentProgramDecompiler::BuildCode()
output_register_names = { "h0", "h4", "h6", "h8" };
}
for (int n = 0; n < 4; ++n)
for (u32 n = 0; n < 4; ++n)
{
if (!m_parr.HasParam(PF_PARAM_NONE, float4_type, output_register_names[n]))
const auto& reg_name = output_register_names[n];
if (!m_parr.HasParam(PF_PARAM_NONE, float4_type, reg_name))
{
m_parr.AddParam(PF_PARAM_NONE, float4_type, output_register_names[n], init_value);
m_parr.AddParam(PF_PARAM_NONE, float4_type, reg_name, init_value);
}
if (n >= m_prog.mrt_buffers_count)
{
// Skip gather
continue;
}
const auto block_index = ouput_register_indices[n];
shader_is_valid |= (!!temp_registers[block_index].h0_writes);
}
auto& r = temp_registers[block_index];
if (!shader_is_valid)
{
properties.has_no_output = true;
if (!properties.has_discard_op)
if (fp16_out)
{
// NOTE: Discard operation overrides output
rsx_log.warning("Shader does not write to any output register and will be NOPed");
main = "/*" + main + "*/";
// Check if we need a split/extract op
if (r.requires_split(0))
{
main_epilogue << " " << reg_name << " = " << float4_type << r.split_h0() << ";\n";
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from %s without writing to it. Final value will be extracted from the 32-bit register.", reg_name);
}
continue;
}
if (!r.requires_gather128())
{
// Nothing to do
continue;
}
// We need to gather the data from existing registers
main_epilogue << " " << reg_name << " = " << float4_type << r.gather_r() << ";\n";
properties.has_gather_op = true;
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name);
}
if (properties.has_dynamic_register_load)
@ -822,6 +863,9 @@ std::string FragmentProgramDecompiler::BuildCode()
OS << "#endif\n";
OS << " discard;\n";
OS << "}\n";
// Don't consume any args
m_parr.Clear();
return OS.str();
}
@ -1019,6 +1063,12 @@ std::string FragmentProgramDecompiler::BuildCode()
insertMainStart(OS);
OS << main << std::endl;
if (const auto epilogue = main_epilogue.str(); !epilogue.empty())
{
OS << " // Epilogue\n";
OS << epilogue << std::endl;
}
insertMainEnd(OS);
return OS.str();
@ -1360,12 +1410,12 @@ std::string FragmentProgramDecompiler::Decompile()
switch (opcode)
{
case RSX_FP_OPCODE_NOP: break;
case RSX_FP_OPCODE_NOP:
break;
case RSX_FP_OPCODE_KIL:
properties.has_discard_op = true;
AddFlowOp("_kill()");
break;
default:
int prev_force_unit = forced_unit;

View file

@ -1,116 +1,10 @@
#pragma once
#include "ShaderParam.h"
#include "FragmentProgramRegister.h"
#include "RSXFragmentProgram.h"
#include <sstream>
// Helper for GPR occupancy tracking
struct temp_register
{
bool aliased_r0 = false;
bool aliased_h0 = false;
bool aliased_h1 = false;
bool last_write_half[4] = { false, false, false, false };
u32 real_index = -1;
u32 h0_writes = 0u; // Number of writes to the first 64-bits of the register
u32 h1_writes = 0u; // Number of writes to the last 64-bits of the register
void tag(u32 index, bool half_register, bool x, bool y, bool z, bool w)
{
if (half_register)
{
if (index & 1)
{
if (x) last_write_half[2] = true;
if (y) last_write_half[2] = true;
if (z) last_write_half[3] = true;
if (w) last_write_half[3] = true;
aliased_h1 = true;
h1_writes++;
}
else
{
if (x) last_write_half[0] = true;
if (y) last_write_half[0] = true;
if (z) last_write_half[1] = true;
if (w) last_write_half[1] = true;
aliased_h0 = true;
h0_writes++;
}
}
else
{
if (x) last_write_half[0] = false;
if (y) last_write_half[1] = false;
if (z) last_write_half[2] = false;
if (w) last_write_half[3] = false;
aliased_r0 = true;
h0_writes++;
h1_writes++;
}
if (real_index == umax)
{
if (half_register)
real_index = index >> 1;
else
real_index = index;
}
}
bool requires_gather(u8 channel) const
{
//Data fetched from the single precision register requires merging of the two half registers
ensure(channel < 4);
if (aliased_h0 && channel < 2)
{
return last_write_half[channel];
}
if (aliased_h1 && channel > 1)
{
return last_write_half[channel];
}
return false;
}
bool requires_split(u32 /*index*/) const
{
//Data fetched from any of the two half registers requires sync with the full register
if (!(last_write_half[0] || last_write_half[1]) && aliased_r0)
{
//r0 has been written to
//TODO: Check for specific elements in real32 register
return true;
}
return false;
}
std::string gather_r() const
{
std::string h0 = "h" + std::to_string(real_index << 1);
std::string h1 = "h" + std::to_string(real_index << 1 | 1);
std::string reg = "r" + std::to_string(real_index);
std::string ret = "//Invalid gather";
if (aliased_h0 && aliased_h1)
ret = "(gather(" + h0 + ", " + h1 + "))";
else if (aliased_h0)
ret = "(gather(" + h0 + "), " + reg + ".zw)";
else if (aliased_h1)
ret = "(" + reg + ".xy, gather(" + h1 + "))";
return ret;
}
};
/**
* This class is used to translate RSX Fragment program to GLSL/HLSL code
* Backend with text based shader can subclass this class and implement :
@ -157,7 +51,7 @@ class FragmentProgramDecompiler
bool m_is_valid_ucode = true;
std::array<temp_register, 64> temp_registers;
std::array<rsx::MixedPrecisionRegister, 64> temp_registers;
std::string GetMask() const;

View file

@ -0,0 +1,196 @@
#include "stdafx.h"
#include "FragmentProgramRegister.h"
namespace rsx
{
MixedPrecisionRegister::MixedPrecisionRegister()
{
std::fill(content_mask.begin(), content_mask.end(), data_type_bits::undefined);
}
void MixedPrecisionRegister::tag_h0(bool x, bool y, bool z, bool w)
{
if (x) content_mask[0] = data_type_bits::f16;
if (y) content_mask[1] = data_type_bits::f16;
if (z) content_mask[2] = data_type_bits::f16;
if (w) content_mask[3] = data_type_bits::f16;
}
void MixedPrecisionRegister::tag_h1(bool x, bool y, bool z, bool w)
{
if (x) content_mask[4] = data_type_bits::f16;
if (y) content_mask[5] = data_type_bits::f16;
if (z) content_mask[6] = data_type_bits::f16;
if (w) content_mask[7] = data_type_bits::f16;
}
void MixedPrecisionRegister::tag_r(bool x, bool y, bool z, bool w)
{
if (x) content_mask[0] = content_mask[1] = data_type_bits::f32;
if (y) content_mask[2] = content_mask[3] = data_type_bits::f32;
if (z) content_mask[4] = content_mask[5] = data_type_bits::f32;
if (w) content_mask[6] = content_mask[7] = data_type_bits::f32;
}
void MixedPrecisionRegister::tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w)
{
if (file_index == umax)
{
// First-time use. Initialize...
const u32 real_index = is_fp16 ? (index >> 1) : index;
file_index = real_index;
}
if (is_fp16)
{
ensure((index / 2) == file_index);
if (index & 1)
{
tag_h1(x, y, z, w);
return;
}
tag_h0(x, y, z, w);
return;
}
tag_r(x, y, z, w);
}
std::string MixedPrecisionRegister::gather_r() const
{
const auto half_index = file_index << 1;
const std::string reg = "r" + std::to_string(file_index);
const std::string gather_half_regs[] = {
"gather(h" + std::to_string(half_index) + ")",
"gather(h" + std::to_string(half_index + 1) + ")"
};
std::string outputs[4];
for (int ch = 0; ch < 4; ++ch)
{
// FIXME: This approach ignores mixed register bits. Not ideal!!!!
const auto channel0 = content_mask[ch * 2];
const auto is_fp16_ch = channel0 == content_mask[ch * 2 + 1] && channel0 == data_type_bits::f16;
outputs[ch] = is_fp16_ch ? gather_half_regs[ch / 2] : reg;
}
// Grouping. Only replace relevant bits...
if (outputs[0] == outputs[1]) outputs[0] = "";
if (outputs[2] == outputs[3]) outputs[2] = "";
// Assemble
bool group = false;
std::string result = "";
constexpr std::string_view swz_mask = "xyzw";
for (int ch = 0; ch < 4; ++ch)
{
if (outputs[ch].empty())
{
group = true;
continue;
}
if (!result.empty())
{
result += ", ";
}
if (group)
{
ensure(ch > 0);
group = false;
if (outputs[ch] == reg)
{
result += reg + "." + swz_mask[ch - 1] + swz_mask[ch];
continue;
}
result += outputs[ch];
continue;
}
const int subch = outputs[ch] == reg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
result += outputs[ch] + "." + swz_mask[subch];
}
// Optimize dual-gather (128-bit gather) to use special function
const std::string double_gather = gather_half_regs[0] + ", " + gather_half_regs[1];
if (result == double_gather)
{
result = "gather(h" + std::to_string(half_index) + ", h" + std::to_string(half_index + 1) + ")";
}
return "(" + result + ")";
}
std::string MixedPrecisionRegister::fetch_halfreg(u32 word_index) const
{
// Reads half-word 0 (H16x4) from a full real (R32x4) register
constexpr std::string_view swz_mask = "xyzw";
const std::string reg = "r" + std::to_string(file_index);
const std::string hreg = "h" + std::to_string(file_index * 2 + word_index);
const std::string word0_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2] + ")";
const std::string word1_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2 + 1] + ")";
const std::string words[] = {
"unpackHalf2x16(" + word0_bits + ")",
"unpackHalf2x16(" + word1_bits + ")"
};
// Assemble
std::string outputs[4];
ensure(word_index <= 1);
const int word_offset = word_index * 4;
for (int ch = 0; ch < 4; ++ch)
{
outputs[ch] = content_mask[ch + word_offset] == data_type_bits::f32
? words[ch / 2]
: hreg;
}
// Grouping. Only replace relevant bits...
if (outputs[0] == outputs[1]) outputs[0] = "";
if (outputs[2] == outputs[3]) outputs[2] = "";
// Assemble
bool group = false;
std::string result = "";
for (int ch = 0; ch < 4; ++ch)
{
if (outputs[ch].empty())
{
group = true;
continue;
}
if (!result.empty())
{
result += ", ";
}
if (group)
{
ensure(ch > 0);
group = false;
result += outputs[ch];
if (outputs[ch] == hreg)
{
result += std::string(".") + swz_mask[ch - 1] + swz_mask[ch];
}
continue;
}
const int subch = outputs[ch] == hreg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
result += outputs[ch] + "." + swz_mask[subch];
}
return "(" + result + ")";
}
}

View file

@ -0,0 +1,111 @@
#pragma once
#include <util/types.hpp>
namespace rsx
{
class MixedPrecisionRegister
{
enum data_type_bits
{
undefined = 0,
f16 = 1,
f32 = 2
};
std::array<data_type_bits, 8> content_mask; // Content details for each half-word
u32 file_index = umax;
void tag_h0(bool x, bool y, bool z, bool w);
void tag_h1(bool x, bool y, bool z, bool w);
void tag_r(bool x, bool y, bool z, bool w);
std::string fetch_halfreg(u32 word_index) const;
public:
MixedPrecisionRegister();
void tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w);
std::string gather_r() const;
std::string split_h0() const
{
return fetch_halfreg(0);
}
std::string split_h1() const
{
return fetch_halfreg(1);
}
// Getters
// Return true if all values are unwritten to (undefined)
bool floating() const
{
return file_index == umax;
}
// Return true if the first half register is all undefined
bool floating_h0() const
{
return content_mask[0] == content_mask[1] &&
content_mask[1] == content_mask[2] &&
content_mask[2] == content_mask[3] &&
content_mask[3] == data_type_bits::undefined;
}
// Return true if the second half register is all undefined
bool floating_h1() const
{
return content_mask[4] == content_mask[5] &&
content_mask[5] == content_mask[6] &&
content_mask[6] == content_mask[7] &&
content_mask[7] == data_type_bits::undefined;
}
// Return true if any of the half-words are 16-bit
bool requires_gather(u8 channel) const
{
// Data fetched from the single precision register requires merging of the two half registers
const auto channel_offset = channel * 2;
ensure(channel_offset <= 6);
return (content_mask[channel_offset] == data_type_bits::f16 || content_mask[channel_offset + 1] == data_type_bits::f16);
}
// Return true if the entire 128-bit register is filled with 2xfp16x4 data words
bool requires_gather128() const
{
// Full 128-bit check
for (const auto& ch : content_mask)
{
if (ch == data_type_bits::f16)
{
return true;
}
}
return false;
}
// Return true if the half-register is polluted with fp32 data
bool requires_split(u32 word_index) const
{
const u32 content_offset = word_index * 4;
for (u32 i = 0; i < 4; ++i)
{
if (content_mask[content_offset + i] == data_type_bits::f32)
{
return true;
}
}
return false;
}
};
}

View file

@ -86,8 +86,11 @@ layout(location=0) in vec4 in_regs[16];
#define CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT 0xe
#define CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS 0x40
#define GET_BITS(word, offset, count) bitfieldExtract(inst.words[word], offset, count)
#define TEST_BIT(word, offset) (GET_BITS(word, offset, 1) > 0)
#define GET_BITS(bitfield, offset, count) bitfieldExtract(bitfield, offset, count)
#define TEST_BIT(bitfield, offset) (GET_BITS(bitfield, offset, 1) > 0)
#define GET_INST_BITS(word, offset, count) GET_BITS(inst.words[word], offset, count)
#define TEST_INST_BIT(word, offset) (GET_INST_BITS(word, offset, 1) > 0)
#define select mix
#define reg_mov(d, s, m) d = select(d, s, m)
@ -174,7 +177,7 @@ int counter = 0;
vec4 read_src(const in int index)
{
ur0 = GET_BITS(index + 1, 0, 2);
ur0 = GET_INST_BITS(index + 1, 0, 2);
switch (ur0)
{
@ -183,14 +186,14 @@ vec4 read_src(const in int index)
switch(index)
{
case 0:
ur1 = GET_BITS(1, 2, 6); break;
ur1 = GET_INST_BITS(1, 2, 6); break;
case 1:
ur1 = GET_BITS(2, 2, 6); break;
ur1 = GET_INST_BITS(2, 2, 6); break;
case 2:
ur1 = GET_BITS(3, 2, 6); break;
ur1 = GET_INST_BITS(3, 2, 6); break;
}
if (TEST_BIT(index + 1, 8))
if (TEST_INST_BIT(index + 1, 8))
{
vr0 = regs16[ur1];
}
@ -202,7 +205,7 @@ vec4 read_src(const in int index)
}
case RSX_FP_REGISTER_TYPE_INPUT:
{
ur1 = GET_BITS(0, 13, 4);
ur1 = GET_INST_BITS(0, 13, 4);
switch (ur1)
{
case 0:
@ -235,27 +238,27 @@ vec4 read_src(const in int index)
}
}
ur1 = GET_BITS(index + 1, 9, 8);
ur1 = GET_INST_BITS(index + 1, 9, 8);
vr0 = shuffle(vr0, ur1);
// abs
if (index == 0)
{
if (TEST_BIT(1, 29)) vr0 = abs(vr0);
if (TEST_INST_BIT(1, 29)) vr0 = abs(vr0);
}
else
{
ur1 = index + 1;
if (TEST_BIT(ur1, 18)) vr0 = abs(vr0);
if (TEST_INST_BIT(ur1, 18)) vr0 = abs(vr0);
}
// neg
return (TEST_BIT(index + 1, 17))? -vr0 : vr0;
return (TEST_INST_BIT(index + 1, 17))? -vr0 : vr0;
}
vec4 read_cond()
{
return shuffle(cc[GET_BITS(1, 31, 1)], GET_BITS(1, 21, 8));
return shuffle(cc[GET_INST_BITS(1, 31, 1)], GET_INST_BITS(1, 21, 8));
}
bvec4 decode_cond(const in uint mode, const in vec4 cond)
@ -283,7 +286,7 @@ bvec4 decode_cond(const in uint mode, const in vec4 cond)
bool check_cond()
{
ur0 = GET_BITS(1, 18, 3);
ur0 = GET_INST_BITS(1, 18, 3);
if (ur0 == 0x7)
{
return true;
@ -351,14 +354,14 @@ vec3 _texcoord_xform(const in vec3 coord, const in sampler_info params)
vec4 _texture(in vec4 coord, float bias)
{
ur0 = GET_BITS(0, 17, 4);
ur0 = GET_INST_BITS(0, 17, 4);
if (!IS_TEXTURE_RESIDENT(ur0))
{
return vr_zero;
}
ur1 = ur0 + ur0;
const uint type = bitfieldExtract(texture_control, int(ur1), 2);
const uint type = GET_BITS(texture_control, int(ur1), 2);
switch (type)
{
@ -380,7 +383,7 @@ vec4 _texture(in vec4 coord, float bias)
break;
}
if (TEST_BIT(0, 21))
if (TEST_INST_BIT(0, 21))
{
vr0 = vr0 * 2. - 1.;
}
@ -390,14 +393,14 @@ vec4 _texture(in vec4 coord, float bias)
vec4 _textureLod(in vec4 coord, float lod)
{
ur0 = GET_BITS(0, 17, 4);
ur0 = GET_INST_BITS(0, 17, 4);
if (!IS_TEXTURE_RESIDENT(ur0))
{
return vr_zero;
}
ur1 = ur0 + ur0;
const uint type = bitfieldExtract(texture_control, int(ur1), 2);
const uint type = GET_BITS(texture_control, int(ur1), 2);
switch (type)
{
@ -419,7 +422,7 @@ vec4 _textureLod(in vec4 coord, float lod)
break;
}
if (TEST_BIT(0, 21))
if (TEST_INST_BIT(0, 21))
{
// Normal-expand, v = 2v - 1
vr0 += vr0;
@ -436,27 +439,27 @@ void write_dst(const in vec4 value)
uvr0 = uvec4(uint(1 << 9), uint(1 << 10), uint(1 << 11), uint(1 << 12));
bvr0 = bvec4(uvr0 & inst.words.xxxx);
if (TEST_BIT(0, 8)) // SET COND
if (TEST_INST_BIT(0, 8)) // SET COND
{
ur0 = GET_BITS(1, 30, 1);
ur0 = GET_INST_BITS(1, 30, 1);
reg_mov(cc[ur0], value, bvr0);
}
if (TEST_BIT(0, 30)) // NO DEST
if (TEST_INST_BIT(0, 30)) // NO DEST
{
return;
}
ur1 = GET_BITS(2, 28, 3);
ur1 = GET_INST_BITS(2, 28, 3);
sr0 = modifier_scale[ur1];
vr0 = value * sr0;
if (TEST_BIT(0, 31)) // SAT
if (TEST_INST_BIT(0, 31)) // SAT
{
vr0 = clamp(vr0, 0, 1);
}
ur0 = GET_BITS(1, 18, 3);
ur0 = GET_INST_BITS(1, 18, 3);
if (ur0 != 0x7)
{
vr1 = read_cond();
@ -464,8 +467,8 @@ void write_dst(const in vec4 value)
bvr0 = bvec4(uvec4(bvr0) & uvec4(bvr1));
}
ur1 = GET_BITS(0, 1, 6);
if (TEST_BIT(0, 7))
ur1 = GET_INST_BITS(0, 1, 6);
if (TEST_INST_BIT(0, 7))
{
reg_mov(regs16[ur1], vr0, bvr0);
}
@ -481,7 +484,7 @@ void initialize()
// NOTE: Register count is the number of 'full' registers that will be consumed. Hardware seems to do some renaming.
// NOTE: Attempting to zero-initialize all the registers will slow things to a crawl!
uint register_count = bitfieldExtract(shader_control, 24, 6);
uint register_count = GET_BITS(shader_control, 24, 6);
ur0 = 0, ur1 = 0;
while (register_count > 0)
{
@ -587,11 +590,11 @@ void main()
((fp_instructions[ip] << 8) & uvec4(0xFF00FF00)) |
((fp_instructions[ip] >> 8) & uvec4(0x00FF00FF));
inst.opcode = GET_BITS(0, 24, 6);
inst.end = TEST_BIT(0, 0);
inst.opcode = GET_INST_BITS(0, 24, 6);
inst.end = TEST_INST_BIT(0, 0);
#ifdef WITH_FLOW_CTRL
if (TEST_BIT(2, 31))
if (TEST_INST_BIT(2, 31))
{
// Flow control
switch (inst.opcode | (1 << 6))
@ -623,8 +626,8 @@ void main()
case RSX_FP_OPCODE_REP:
if (check_cond())
{
counter = int(GET_BITS(2, 2, 8) - GET_BITS(2, 10, 8));
counter /= int(GET_BITS(2, 19, 8));
counter = int(GET_INST_BITS(2, 2, 8) - GET_INST_BITS(2, 10, 8));
counter /= int(GET_INST_BITS(2, 19, 8));
loop_start_addr = ip + 1;
loop_end_addr = int(inst.words.w >> 2);
}

View file

@ -34,7 +34,8 @@ vec2 texture2DMSCoord(const in vec2 coords, const in uint flags)
return coords;
}
const vec2 wrapped_coords = mod(coords, vec2(1.0));
const vec2 wrapped_coords_raw = mod(coords, vec2(1.0));
const vec2 wrapped_coords = mod(wrapped_coords_raw + vec2(1.0), vec2(1.0));
const bvec2 wrap_control_mask = bvec2(uvec2(flags) & uvec2(WRAP_S_MASK, WRAP_T_MASK));
return _select(coords, wrapped_coords, wrap_control_mask);
}

View file

@ -340,12 +340,16 @@ vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vert
usz vertex_program_storage_hash::operator()(const RSXVertexProgram &program) const
{
usz hash = vertex_program_utils::get_vertex_program_ucode_hash(program);
hash ^= program.ctrl;
hash ^= program.output_mask;
hash ^= program.texture_state.texture_dimensions;
hash ^= program.texture_state.multisampled_textures;
return hash;
const usz ucode_hash = vertex_program_utils::get_vertex_program_ucode_hash(program);
const u32 state_params[] =
{
program.ctrl,
program.output_mask,
program.texture_state.texture_dimensions,
program.texture_state.multisampled_textures,
};
const usz metadata_hash = rpcs3::hash_array(state_params);
return rpcs3::hash64(ucode_hash, metadata_hash);
}
bool vertex_program_compare::operator()(const RSXVertexProgram &binary1, const RSXVertexProgram &binary2) const
@ -541,24 +545,33 @@ usz fragment_program_utils::get_fragment_program_ucode_hash(const RSXFragmentPro
usz fragment_program_storage_hash::operator()(const RSXFragmentProgram& program) const
{
usz hash = fragment_program_utils::get_fragment_program_ucode_hash(program);
hash ^= program.ctrl;
hash ^= +program.two_sided_lighting;
hash ^= program.texture_state.texture_dimensions;
hash ^= program.texture_state.shadow_textures;
hash ^= program.texture_state.redirected_textures;
hash ^= program.texture_state.multisampled_textures;
hash ^= program.texcoord_control_mask;
return hash;
const usz ucode_hash = fragment_program_utils::get_fragment_program_ucode_hash(program);
const u32 state_params[] =
{
program.ctrl,
program.two_sided_lighting ? 1u : 0u,
program.texture_state.texture_dimensions,
program.texture_state.shadow_textures,
program.texture_state.redirected_textures,
program.texture_state.multisampled_textures,
program.texcoord_control_mask,
program.mrt_buffers_count
};
const usz metadata_hash = rpcs3::hash_array(state_params);
return rpcs3::hash64(ucode_hash, metadata_hash);
}
bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, const RSXFragmentProgram& binary2) const
{
if (binary1.ctrl != binary2.ctrl || binary1.texture_state != binary2.texture_state ||
if (binary1.ucode_length != binary2.ucode_length ||
binary1.ctrl != binary2.ctrl ||
binary1.texture_state != binary2.texture_state ||
binary1.texcoord_control_mask != binary2.texcoord_control_mask ||
binary1.two_sided_lighting != binary2.two_sided_lighting)
binary1.two_sided_lighting != binary2.two_sided_lighting ||
binary1.mrt_buffers_count != binary2.mrt_buffers_count)
{
return false;
}
const void* instBuffer1 = binary1.get_data();
const void* instBuffer2 = binary2.get_data();
@ -569,7 +582,9 @@ bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, con
const auto inst2 = v128::loadu(instBuffer2, instIndex);
if (inst1._u ^ inst2._u)
{
return false;
}
instIndex++;
// Skip constants
@ -578,9 +593,11 @@ bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, con
fragment_program_utils::is_constant(inst1._u32[3]))
instIndex++;
bool end = ((inst1._u32[0] >> 8) & 0x1) && ((inst2._u32[0] >> 8) & 0x1);
const bool end = ((inst1._u32[0] >> 8) & 0x1) && ((inst2._u32[0] >> 8) & 0x1);
if (end)
{
return true;
}
}
}

View file

@ -293,10 +293,10 @@ public:
bool compile_async,
bool allow_notification,
Args&& ...args
)
)
{
const auto &vp_search = search_vertex_program(vertexShader);
const auto &fp_search = search_fragment_program(fragmentShader);
const auto& vp_search = search_vertex_program(vertexShader);
const auto& fp_search = search_fragment_program(fragmentShader);
const bool already_existing_fragment_program = std::get<1>(fp_search);
const bool already_existing_vertex_program = std::get<1>(vp_search);
@ -385,7 +385,13 @@ public:
void fill_fragment_constants_buffer(std::span<f32> dst_buffer, const fragment_program_type& fragment_program, const RSXFragmentProgram& rsx_prog, bool sanitize = false) const
{
ensure((dst_buffer.size_bytes() >= ::narrow<int>(fragment_program.FragmentConstantOffsetCache.size()) * 16u));
if (dst_buffer.size_bytes() < (fragment_program.FragmentConstantOffsetCache.size() * 16))
{
// This can happen if CELL alters the shader after it has been loaded by RSX.
rsx_log.error("Insufficient constants buffer size passed to fragment program! Corrupt shader?");
return;
}
rsx::write_fragment_constants_to_buffer(dst_buffer, rsx_prog, fragment_program.FragmentConstantOffsetCache, sanitize);
}

View file

@ -300,8 +300,10 @@ struct RSXFragmentProgram
u32 ucode_length = 0;
u32 total_length = 0;
u32 ctrl = 0;
bool two_sided_lighting = false;
u32 texcoord_control_mask = 0;
u32 mrt_buffers_count = 0;
bool two_sided_lighting = false;
rsx::fragment_program_texture_state texture_state;
rsx::fragment_program_texture_config texture_params;

View file

@ -223,10 +223,10 @@ struct RSXVertexProgram
{
std::vector<u32> data;
rsx::vertex_program_texture_state texture_state;
u32 ctrl;
u32 output_mask;
u32 base_address;
u32 entry;
u32 ctrl = 0;
u32 output_mask = 0;
u32 base_address = 0;
u32 entry = 0;
std::bitset<rsx::max_vertex_program_instructions> instruction_mask;
std::set<u32> jump_table;

View file

@ -227,6 +227,14 @@ struct ParamArray
return name;
}
void Clear()
{
for (auto& param : params)
{
param.clear();
}
}
};
class ShaderVariable

View file

@ -110,7 +110,7 @@ namespace rsx
multisampled_textures == other.multisampled_textures;
}
int VertexProgramBase::TranslateConstantsRange(int first_index, int count) const
int VertexProgramBase::translate_constants_range(int first_index, int count) const
{
// The constant ids should be sorted, so just find the first one and check for continuity
int index = -1;
@ -157,4 +157,31 @@ namespace rsx
// OOB or partial match
return -1;
}
bool VertexProgramBase::overlaps_constants_range(int first_index, int count) const
{
if (has_indexed_constants)
{
return true;
}
const int last_index = first_index + count - 1;
// Early rejection test
if (constant_ids.empty() || first_index > constant_ids.back() || last_index < first_index)
{
return false;
}
// Check for any hits
for (auto& idx : constant_ids)
{
if (idx >= first_index && idx <= last_index)
{
return true;
}
}
return false;
}
}

View file

@ -67,6 +67,9 @@ namespace rsx
// Translates an incoming range of constants against our mapping.
// If there is no linear mapping available, return -1, otherwise returns the translated index of the first slot
// TODO: Move this somewhere else during refactor
int TranslateConstantsRange(int first_index, int count) const;
int translate_constants_range(int first_index, int count) const;
// Returns true if this program consumes any constants in the range [first, first + count - 1]
bool overlaps_constants_range(int first_index, int count) const;
};
}

View file

@ -1719,7 +1719,7 @@ namespace rsx
for (uint i = 0; i < mrt_buffers.size(); ++i)
{
if (rsx::method_registers.color_write_enabled(i))
if (m_ctx->register_state->color_write_enabled(i))
{
const auto real_index = mrt_buffers[i];
m_framebuffer_layout.color_write_enabled[real_index] = true;
@ -1727,6 +1727,14 @@ namespace rsx
}
}
if (::size32(mrt_buffers) != current_fragment_program.mrt_buffers_count &&
!m_graphics_state.test(rsx::pipeline_state::fragment_program_dirty) &&
!is_current_program_interpreted())
{
// Notify that we should recompile the FS
m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty;
}
return any_found;
};
@ -2038,9 +2046,10 @@ namespace rsx
m_graphics_state.clear(rsx::pipeline_state::fragment_program_dirty);
current_fragment_program.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
current_fragment_program.texcoord_control_mask = rsx::method_registers.texcoord_control_mask();
current_fragment_program.two_sided_lighting = rsx::method_registers.two_side_light_en();
current_fragment_program.ctrl = m_ctx->register_state->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
current_fragment_program.texcoord_control_mask = m_ctx->register_state->texcoord_control_mask();
current_fragment_program.two_sided_lighting = m_ctx->register_state->two_side_light_en();
current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(m_ctx->register_state->surface_color_target());
if (method_registers.current_draw_clause.classify_mode() == primitive_class::polygon)
{

View file

@ -436,6 +436,8 @@ namespace rsx
bool is_current_vertex_program_instanced() const { return !!(current_vertex_program.ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS); }
virtual bool is_current_program_interpreted() const { return false; }
public:
void reset();
void init(u32 ctrlAddress);

View file

@ -0,0 +1,160 @@
#include "stdafx.h"
#include "vkutils/device.h"
#include "vkutils/descriptors.h"
#include "VKCommonPipelineLayout.h"
#include "VKHelpers.h"
#include "Emu/RSX/Common/simple_array.hpp"
namespace vk
{
rsx::simple_array<VkDescriptorSetLayoutBinding> get_common_binding_table()
{
const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table();
rsx::simple_array<VkDescriptorSetLayoutBinding> bindings(binding_table.instancing_constants_buffer_slot + 1);
u32 idx = 0;
// Vertex stream, one stream for cacheable data, one stream for transient data
for (int i = 0; i < 3; i++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_state_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_texture_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
bindings[idx].binding = binding_table.vertex_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.conditional_render_predicate_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.rasterizer_env_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.instancing_lookup_table_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.instancing_constants_buffer_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
return bindings;
}
std::tuple<VkPipelineLayout, VkDescriptorSetLayout> get_common_pipeline_layout(VkDevice dev)
{
const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table();
auto bindings = get_common_binding_table();
u32 idx = ::size32(bindings);
bindings.resize(binding_table.total_descriptor_bindings);
for (auto binding = binding_table.textures_first_bind_slot;
binding < binding_table.vertex_textures_first_bind_slot;
binding++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
for (int i = 0; i < rsx::limits::vertex_textures_count; i++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_textures_first_bind_slot + i;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
ensure(idx == binding_table.total_descriptor_bindings);
std::array<VkPushConstantRange, 1> push_constants;
push_constants[0].offset = 0;
push_constants[0].size = 16;
push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
if (vk::emulate_conditional_rendering())
{
// Conditional render toggle
push_constants[0].size = 20;
}
const auto set_layout = vk::descriptors::create_layout(bindings);
VkPipelineLayoutCreateInfo layout_info = {};
layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &set_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = push_constants.data();
VkPipelineLayout result;
CHECK_RESULT(vkCreatePipelineLayout(dev, &layout_info, nullptr, &result));
return std::make_tuple(result, set_layout);
}
}

View file

@ -0,0 +1,14 @@
#pragma once
#include "vkutils/shared.h"
#include "Emu/RSX/Common/simple_array.hpp"
namespace vk
{
// Grab standard layout for decompiled RSX programs. Also used by the interpreter.
// FIXME: This generates a bloated monstrosity that needs to die.
std::tuple<VkPipelineLayout, VkDescriptorSetLayout> get_common_pipeline_layout(VkDevice dev);
// Returns the standard binding layout without texture slots. Those have special handling depending on the consumer.
rsx::simple_array<VkDescriptorSetLayoutBinding> get_common_binding_table();
}

View file

@ -5,6 +5,7 @@
#include "VKAsyncScheduler.h"
#include "VKCommandStream.h"
#include "VKCommonDecompiler.h"
#include "VKCommonPipelineLayout.h"
#include "VKCompute.h"
#include "VKGSRender.h"
#include "VKHelpers.h"
@ -401,148 +402,6 @@ namespace vk
}
}
namespace
{
std::tuple<VkPipelineLayout, VkDescriptorSetLayout> get_shared_pipeline_layout(VkDevice dev)
{
const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table();
rsx::simple_array<VkDescriptorSetLayoutBinding> bindings(binding_table.total_descriptor_bindings);
u32 idx = 0;
// Vertex stream, one stream for cacheable data, one stream for transient data
for (int i = 0; i < 3; i++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_state_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_texture_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
bindings[idx].binding = binding_table.vertex_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.conditional_render_predicate_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.rasterizer_env_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.instancing_lookup_table_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.instancing_constants_buffer_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
for (auto binding = binding_table.textures_first_bind_slot;
binding < binding_table.vertex_textures_first_bind_slot;
binding++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
for (int i = 0; i < rsx::limits::vertex_textures_count; i++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_textures_first_bind_slot + i;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
ensure(idx == binding_table.total_descriptor_bindings);
std::array<VkPushConstantRange, 1> push_constants;
push_constants[0].offset = 0;
push_constants[0].size = 16;
push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
if (vk::emulate_conditional_rendering())
{
// Conditional render toggle
push_constants[0].size = 20;
}
const auto set_layout = vk::descriptors::create_layout(bindings);
VkPipelineLayoutCreateInfo layout_info = {};
layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
layout_info.setLayoutCount = 1;
layout_info.pSetLayouts = &set_layout;
layout_info.pushConstantRangeCount = 1;
layout_info.pPushConstantRanges = push_constants.data();
VkPipelineLayout result;
CHECK_RESULT(vkCreatePipelineLayout(dev, &layout_info, nullptr, &result));
return std::make_tuple(result, set_layout);
}
}
u64 VKGSRender::get_cycles()
{
return thread_ctrl::get_cycles(static_cast<named_thread<VKGSRender>&>(*this));
@ -633,7 +492,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
m_secondary_cb_list.create(m_secondary_command_buffer_pool, vk::command_buffer::access_type_hint::all);
//Precalculated stuff
std::tie(m_pipeline_layout, m_descriptor_layouts) = get_shared_pipeline_layout(*m_device);
std::tie(m_pipeline_layout, m_descriptor_layouts) = vk::get_common_pipeline_layout(*m_device);
//Occlusion
m_occlusion_query_manager = std::make_unique<vk::query_pool_manager>(*m_device, VK_QUERY_TYPE_OCCLUSION, OCCLUSION_MAX_POOL_SIZE);
@ -688,10 +547,10 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
}
// Initialize optional allocation information with placeholders
m_vertex_env_buffer_info = { m_vertex_env_ring_info.heap->value, 0, 32 };
m_vertex_constants_buffer_info = { m_transform_constants_ring_info.heap->value, 0, 32 };
m_fragment_env_buffer_info = { m_fragment_env_ring_info.heap->value, 0, 32 };
m_fragment_texture_params_buffer_info = { m_fragment_texture_params_ring_info.heap->value, 0, 32 };
m_vertex_env_buffer_info = { m_vertex_env_ring_info.heap->value, 0, 16 };
m_vertex_constants_buffer_info = { m_transform_constants_ring_info.heap->value, 0, 16 };
m_fragment_env_buffer_info = { m_fragment_env_ring_info.heap->value, 0, 16 };
m_fragment_texture_params_buffer_info = { m_fragment_texture_params_ring_info.heap->value, 0, 16 };
m_raster_env_buffer_info = { m_raster_env_ring_info.heap->value, 0, 128 };
const auto limits = m_device->gpu().get_limits();
@ -730,7 +589,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
else
m_vertex_cache = std::make_unique<vk::weak_vertex_cache>();
m_shaders_cache = std::make_unique<vk::shader_cache>(*m_prog_buffer, "vulkan", "v1.94");
m_shaders_cache = std::make_unique<vk::shader_cache>(*m_prog_buffer, "vulkan", "v1.95");
for (u32 i = 0; i < m_swapchain->get_swap_image_count(); ++i)
{
@ -2192,7 +2051,7 @@ void VKGSRender::load_program_env()
return std::make_pair(m_instancing_buffer_ring_info.map(constants_data_table_offset, size), size);
});
m_draw_processor.fill_constants_instancing_buffer(indirection_table_buf, constants_array_buf, *m_vertex_prog);
m_draw_processor.fill_constants_instancing_buffer(indirection_table_buf, constants_array_buf, m_vertex_prog);
m_instancing_buffer_ring_info.unmap();
m_instancing_indirection_buffer_info = { m_instancing_buffer_ring_info.heap->value, indirection_table_offset, indirection_table_buf.size() };
@ -2219,7 +2078,7 @@ void VKGSRender::load_program_env()
}
}
if (update_fragment_constants && !update_instruction_buffers)
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
{
check_heap_status(VK_HEAP_CHECK_FRAGMENT_CONSTANTS_STORAGE);
@ -2350,9 +2209,9 @@ void VKGSRender::load_program_env()
}
// Clear flags
u32 handled_flags = rsx::pipeline_state::fragment_state_dirty |
rsx::flags32_t handled_flags =
rsx::pipeline_state::fragment_state_dirty |
rsx::pipeline_state::vertex_state_dirty |
rsx::pipeline_state::fragment_constants_dirty |
rsx::pipeline_state::fragment_texture_state_dirty;
if (!update_instancing_data)
@ -2360,9 +2219,19 @@ void VKGSRender::load_program_env()
handled_flags |= rsx::pipeline_state::transform_constants_dirty;
}
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
{
handled_flags |= rsx::pipeline_state::fragment_constants_dirty;
}
m_graphics_state.clear(handled_flags);
}
bool VKGSRender::is_current_program_interpreted() const
{
return m_program && m_shader_interpreter.is_interpreter(m_program);
}
void VKGSRender::upload_transform_constants(const rsx::io_buffer& buffer)
{
const usz transform_constants_size = (!m_vertex_prog || m_vertex_prog->has_indexed_constants) ? 8192 : m_vertex_prog->constant_ids.size() * 16;
@ -2433,13 +2302,19 @@ void VKGSRender::update_vertex_env(u32 id, const vk::vertex_upload_info& vertex_
void VKGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 count)
{
if (!m_vertex_prog)
if (!m_program || !m_vertex_prog)
{
// Shouldn't be reachable, but handle it correctly anyway
m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
return;
}
if (!m_vertex_prog->overlaps_constants_range(index, count))
{
// Nothing meaningful to us
return;
}
// Hot-patching transform constants mid-draw (instanced draw)
std::pair<VkDeviceSize, VkDeviceSize> data_range;
void* data_source = nullptr;
@ -2453,7 +2328,7 @@ void VKGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou
data_range = { m_vertex_constants_buffer_info.offset + byte_offset, byte_count };
data_source = &REGS(ctx)->transform_constants[index];
}
else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0)
else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0)
{
const auto write_offset = xform_id * 16;
const auto byte_count = count * 16;

View file

@ -288,6 +288,9 @@ public:
// GRAPH backend
void patch_transform_constants(rsx::context* ctx, u32 index, u32 count) override;
// Misc
bool is_current_program_interpreted() const override;
protected:
void clear_surface(u32 mask) override;
void begin() override;

View file

@ -63,7 +63,7 @@ namespace vk
const std::vector<glsl::program_input>& vs_inputs, const std::vector<glsl::program_input>& fs_inputs)
{
VkPipeline pipeline;
CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, nullptr, 1, &create_info, NULL, &pipeline));
CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, VK_NULL_HANDLE, 1, &create_info, nullptr, &pipeline));
auto result = std::make_unique<vk::glsl::program>(*m_device, pipeline, pipe_layout, vs_inputs, fs_inputs);
result->link();
return result;

View file

@ -387,7 +387,7 @@ namespace vk
struct stencilonly_unresolve : depth_resolve_base
{
VkClearRect region{};
VkClearRect clear_region{};
VkClearAttachment clear_info{};
stencilonly_unresolve()
@ -402,8 +402,8 @@ namespace vk
renderpass_config.set_depth_mask(false);
clear_info.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
region.baseArrayLayer = 0;
region.layerCount = 1;
clear_region.baseArrayLayer = 0;
clear_region.layerCount = 1;
static_parameters_width = 3;
@ -425,7 +425,7 @@ namespace vk
void emit_geometry(vk::command_buffer& cmd) override
{
vkCmdClearAttachments(cmd, 1, &clear_info, 1, &region);
vkCmdClearAttachments(cmd, 1, &clear_info, 1, &clear_region);
for (s32 write_mask = 0x1; write_mask <= 0x80; write_mask <<= 1)
{
@ -444,8 +444,8 @@ namespace vk
auto stencil_view = resolve_image->get_view(rsx::default_remap_vector.with_encoding(VK_REMAP_IDENTITY), VK_IMAGE_ASPECT_STENCIL_BIT);
region.rect.extent.width = resolve_image->width();
region.rect.extent.height = resolve_image->height();
clear_region.rect.extent.width = msaa_image->width();
clear_region.rect.extent.height = msaa_image->height();
overlay_pass::run(
cmd,

View file

@ -1,5 +1,6 @@
#include "stdafx.h"
#include "VKShaderInterpreter.h"
#include "VKCommonPipelineLayout.h"
#include "VKVertexProgram.h"
#include "VKFragmentProgram.h"
#include "VKGSRender.h"
@ -233,77 +234,12 @@ namespace vk
std::pair<VkDescriptorSetLayout, VkPipelineLayout> shader_interpreter::create_layout(VkDevice dev)
{
const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table();
rsx::simple_array<VkDescriptorSetLayoutBinding> bindings(binding_table.total_descriptor_bindings);
auto bindings = get_common_binding_table();
u32 idx = ::size32(bindings);
u32 idx = 0;
// Vertex stream, one stream for cacheable data, one stream for transient data. Third stream contains vertex layout info
for (int i = 0; i < 3; i++)
{
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
}
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_state_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.fragment_texture_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS;
bindings[idx].binding = binding_table.vertex_params_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
bindings[idx].binding = binding_table.conditional_render_predicate_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
bindings[idx].binding = binding_table.rasterizer_env_bind_slot;
bindings[idx].pImmutableSamplers = nullptr;
idx++;
bindings.resize(binding_table.total_descriptor_bindings);
// Texture 1D array
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 16;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
@ -313,6 +249,7 @@ namespace vk
m_fragment_textures_start = bindings[idx].binding;
idx++;
// Texture 2D array
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 16;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
@ -321,6 +258,7 @@ namespace vk
idx++;
// Texture 3D array
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 16;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
@ -329,6 +267,7 @@ namespace vk
idx++;
// Texture CUBE array
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 16;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
@ -337,6 +276,7 @@ namespace vk
idx++;
// Vertex texture array (2D only)
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
bindings[idx].descriptorCount = 4;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
@ -345,6 +285,7 @@ namespace vk
idx++;
// Vertex program ucode block
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
@ -354,6 +295,7 @@ namespace vk
m_vertex_instruction_start = bindings[idx].binding;
idx++;
// Fragment program ucode block
bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
bindings[idx].descriptorCount = 1;
bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
@ -364,6 +306,22 @@ namespace vk
idx++;
bindings.resize(idx);
// Compile descriptor pool sizes
const u32 num_ubo = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ? y.descriptorCount : 0)));
const u32 num_texel_buffers = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ? y.descriptorCount : 0)));
const u32 num_combined_image_sampler = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ? y.descriptorCount : 0)));
const u32 num_ssbo = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? y.descriptorCount : 0)));
ensure(num_ubo > 0 && num_texel_buffers > 0 && num_combined_image_sampler > 0 && num_ssbo > 0);
m_descriptor_pool_sizes =
{
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , num_ubo },
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER , num_texel_buffers },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , num_combined_image_sampler },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, num_ssbo }
};
std::array<VkPushConstantRange, 1> push_constants;
push_constants[0].offset = 0;
push_constants[0].size = 16;
@ -392,16 +350,7 @@ namespace vk
void shader_interpreter::create_descriptor_pools(const vk::render_device& dev)
{
const auto max_draw_calls = dev.get_descriptor_max_draw_calls();
rsx::simple_array<VkDescriptorPoolSize> sizes =
{
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 6 },
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER , 3 },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 68 },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 }
};
m_descriptor_pool.create(dev, sizes, max_draw_calls);
m_descriptor_pool.create(dev, m_descriptor_pool_sizes, max_draw_calls);
}
void shader_interpreter::init(const vk::render_device& dev)
@ -410,6 +359,7 @@ namespace vk
std::tie(m_shared_descriptor_layout, m_shared_pipeline_layout) = create_layout(dev);
create_descriptor_pools(dev);
rsx_log.notice("Building global vertex program interpreter...");
build_vs();
// TODO: Seed the cache
}
@ -449,6 +399,7 @@ namespace vk
}
else
{
rsx_log.notice("Compiling FS...");
fs = build_fs(compiler_opt);
}
@ -463,15 +414,17 @@ namespace vk
shader_stages[1].module = fs->get_handle();
shader_stages[1].pName = "main";
std::vector<VkDynamicState> dynamic_state_descriptors;
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_VIEWPORT);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_SCISSOR);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_LINE_WIDTH);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
std::vector<VkDynamicState> dynamic_state_descriptors =
{
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_LINE_WIDTH,
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
VK_DYNAMIC_STATE_DEPTH_BIAS
};
if (vk::get_current_renderer()->get_depth_bounds_support())
{
@ -502,6 +455,9 @@ namespace vk
VkPipelineColorBlendStateCreateInfo cs = properties.state.cs;
cs.pAttachments = properties.state.att_state;
VkPipelineTessellationStateCreateInfo ts = {};
ts.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
VkGraphicsPipelineCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
info.pVertexInputState = &vi;
@ -511,6 +467,7 @@ namespace vk
info.pMultisampleState = &ms;
info.pViewportState = &vp;
info.pDepthStencilState = &properties.state.ds;
info.pTessellationState = &ts;
info.stageCount = 2;
info.pStages = shader_stages;
info.pDynamicState = &dynamic_state_info;

View file

@ -41,6 +41,7 @@ namespace vk
std::unordered_map<pipeline_key, std::unique_ptr<glsl::program>, key_hasher> m_program_cache;
std::unordered_map<u64, std::unique_ptr<glsl::shader>> m_fs_cache;
rsx::simple_array<VkDescriptorPoolSize> m_descriptor_pool_sizes;
vk::descriptor_pool m_descriptor_pool;
u32 m_vertex_instruction_start = 0;

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