mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 04:16:57 +02:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e930657bf6 | |||
| 0ad9149c90 | |||
| 25f2699523 | |||
| a849f25376 | |||
| fa51da4d76 | |||
| 6b79b7a003 | |||
| 102a10f0fe | |||
| 8d66c4ef6d | |||
| 8cecaf4e30 | |||
| 82b2711cdf | |||
| 35a275e97d | |||
| 303a0203b6 | |||
| cfbaee07f0 | |||
| fe45265fcd | |||
| 8b294eb5b8 | |||
| 07c9978ac6 | |||
| b61b3f2a40 | |||
| 001dfea501 | |||
| f100f785da | |||
| ba1854bbdc | |||
| ea6aae8577 | |||
| 5b7d3343cf | |||
| 394e638905 | |||
| c2b7b0b58c | |||
| 1d2f59dfa4 | |||
| 007d7ea2eb | |||
| 95d3d9d3ad | |||
| bfc8cd0e0b | |||
| 165234b159 | |||
| 6493674726 | |||
| cbedf5707f | |||
| 57c5b206ca | |||
| 87b4417aca | |||
| 104530eafc |
Vendored
+3
@@ -86,6 +86,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
|
||||
@@ -450,6 +450,12 @@ add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(audio_out2_port_tests common fmt::fmt)
|
||||
target_include_directories(audio_out2_port_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
@@ -534,6 +540,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME kernel_file_system COMMAND $<TARGET_FILE:kernel_file_system_tests>)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
|
||||
@@ -135,6 +135,10 @@ bool ReadbackLinearImagesEnabled() {
|
||||
return g_config->readback_linear_images;
|
||||
}
|
||||
|
||||
bool TessellationEnabled() {
|
||||
return g_config->tessellation_enabled;
|
||||
}
|
||||
|
||||
bool PlayGoHackEnabled() {
|
||||
return g_config->playgo_hack_enabled;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ struct ConfigOptions {
|
||||
bool gpu_assisted_validation_enabled = false;
|
||||
bool renderdoc_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool tessellation_enabled = false;
|
||||
bool playgo_hack_enabled = false;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool red_zone_protection_enabled = false;
|
||||
@@ -110,6 +111,7 @@ bool GpuAssistedValidationEnabled();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool ReadbackLinearImagesEnabled();
|
||||
bool TessellationEnabled();
|
||||
bool PlayGoHackEnabled();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool RedZoneProtectionEnabled();
|
||||
|
||||
+49
-65
@@ -245,88 +245,78 @@ void File::Printf(const char* format, ...) {
|
||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||
}
|
||||
|
||||
static std::filesystem::path WithoutTrailingSeparator(
|
||||
const std::filesystem::path& path) {
|
||||
if (!path.empty() && !path.has_filename() &&
|
||||
path != path.root_path()) {
|
||||
return path.parent_path();
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
bool File::CreateDirectory(const std::filesystem::path& path) {
|
||||
return SysFileCreateDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
return SysFileDeleteDirectory(
|
||||
WithoutTrailingSeparator(path));
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
if (IsDirectoryExisting(current_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
if (!CreateDirectory(current_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
std::vector<std::filesystem::path> directories;
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
directories.push_back(current_path);
|
||||
}
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
for (auto it = directories.rbegin();
|
||||
it != directories.rend(); ++it) {
|
||||
if (!DeleteDirectory(*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
@@ -460,13 +450,7 @@ std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!path_str.ends_with("/") && !path_str.ends_with("\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
const auto normalized_path = path.lexically_normal();
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
@@ -474,7 +458,7 @@ std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.rel_path_with_name = f.path_with_name.lexically_normal().lexically_relative(normalized_path);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
@@ -47,7 +46,7 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
|
||||
@@ -477,18 +477,10 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -496,18 +488,19 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
if (file_name == L"." || file_name == L"..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto full_path = path / file_name;
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
SysFileFindFiles(full_path, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.path_with_name = full_path;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
@@ -515,7 +508,7 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
@@ -524,18 +517,10 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -549,7 +534,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::filesystem::path PathFromUtf8(std::string_view text) {
|
||||
#if defined(__cpp_char8_t)
|
||||
return std::filesystem::path(std::u8string(text.begin(), text.end()));
|
||||
#else
|
||||
return std::filesystem::u8path(text.begin(), text.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
|
||||
@@ -26,6 +26,10 @@ enum class PrimitiveType : uint32_t {
|
||||
kPolygon = 21,
|
||||
};
|
||||
|
||||
constexpr bool IsRectList(PrimitiveType type) {
|
||||
return type == PrimitiveType::kRectList || type == PrimitiveType::kRectListLegacy;
|
||||
}
|
||||
|
||||
enum class IndexType : uint32_t {
|
||||
kIndex16 = 0,
|
||||
kIndex32 = 1,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#undef MemoryBarrier
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#elif defined(__linux__)
|
||||
|
||||
@@ -593,6 +593,21 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
|
||||
ShaderParams pixel_params;
|
||||
if (pixel_active) {
|
||||
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
|
||||
const auto& blend = context.GetBlendControl(0);
|
||||
const auto is_dual_source = [](uint8_t factor) {
|
||||
return factor >= static_cast<uint8_t>(Prospero::BlendFactor::kSrc1Color) &&
|
||||
factor <= static_cast<uint8_t>(Prospero::BlendFactor::kOneMinusSrc1Alpha);
|
||||
};
|
||||
pixel_info.dual_source_blending =
|
||||
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
|
||||
(is_dual_source(blend.color_srcblend) || is_dual_source(blend.color_destblend) ||
|
||||
(blend.separate_alpha_blend &&
|
||||
(is_dual_source(blend.alpha_srcblend) || is_dual_source(blend.alpha_destblend))));
|
||||
if (pixel_info.dual_source_blending) {
|
||||
// MRT1 supplies a second blend source for the same render target as MRT0.
|
||||
pixel_info.target_output_mode[1] = pixel_info.target_output_mode[0];
|
||||
pixel_info.target_export_mapping[1] = pixel_info.target_export_mapping[0];
|
||||
}
|
||||
}
|
||||
if (context.GetClipControl().clip_disable) {
|
||||
const auto& viewport = context.GetScreenViewport().viewports[0];
|
||||
@@ -740,8 +755,7 @@ PipelineCache::Pipeline& PipelineCache::GetGraphicsPipeline(
|
||||
static_params.depth_bounds_test_enable = depth.depth_bounds_test_enable;
|
||||
static_params.depth_min_bounds = depth.depth_min_bounds;
|
||||
static_params.depth_max_bounds = depth.depth_max_bounds;
|
||||
const bool rect_list =
|
||||
command.GetUserConfig().GetPrimType() == Prospero::PrimitiveType::kRectList;
|
||||
const bool rect_list = Prospero::IsRectList(command.GetUserConfig().GetPrimType());
|
||||
static_params.cull_back = !rect_list && mc.cull_back;
|
||||
static_params.cull_front = !rect_list && mc.cull_front;
|
||||
static_params.face = mc.face;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
@@ -789,8 +790,7 @@ static void SetDrawDebugPhase(CommandBuffer& buffer, uint64_t submit_id, const D
|
||||
draw.instance_count, draw.first_instance);
|
||||
}
|
||||
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
vk::PrimitiveTopology& topology) {
|
||||
static bool GetDrawTopology(const HW::UserConfig& ucfg, vk::PrimitiveTopology& topology) {
|
||||
|
||||
topology = vk::PrimitiveTopology::ePointList;
|
||||
|
||||
@@ -813,14 +813,11 @@ static bool GetDrawTopology(const HW::UserConfig& ucfg, bool auto_draw,
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (!Config::TessellationEnabled()) return false;
|
||||
[[fallthrough]];
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (!auto_draw) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
topology = vk::PrimitiveTopology::eTriangleStrip;
|
||||
topology = vk::PrimitiveTopology::ePatchList;
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
topology = vk::PrimitiveTopology::eTriangleFan;
|
||||
@@ -999,8 +996,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed() &&
|
||||
buffer.GetUserConfig().GetPrimType() != Prospero::PrimitiveType::kRectListLegacy) {
|
||||
if (!draw.IsIndexed() && !Prospero::IsRectList(buffer.GetUserConfig().GetPrimType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1013,8 +1009,7 @@ static void LogDrawStateIfNeeded(const CommandBuffer& buffer, const DrawCallInfo
|
||||
}
|
||||
|
||||
static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_buffer,
|
||||
const ShaderVertexInputInfo& vs_input_info, const DrawCallInfo& draw,
|
||||
const DrawEmitInfo& emit) {
|
||||
const DrawCallInfo& draw, const DrawEmitInfo& emit) {
|
||||
switch (ucfg.GetPrimType()) {
|
||||
case Prospero::PrimitiveType::kPointList:
|
||||
case Prospero::PrimitiveType::kLineList:
|
||||
@@ -1023,6 +1018,7 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
case Prospero::PrimitiveType::kTriFan:
|
||||
case Prospero::PrimitiveType::kTriStrip:
|
||||
case Prospero::PrimitiveType::kRectList:
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
case Prospero::PrimitiveType::kPatch:
|
||||
if (draw.IsIndexed()) {
|
||||
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
|
||||
@@ -1032,14 +1028,6 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
|
||||
emit.first_instance);
|
||||
}
|
||||
break;
|
||||
case Prospero::PrimitiveType::kRectListLegacy:
|
||||
if (draw.IsIndexed()) {
|
||||
EXIT("unknown primitive type: %u\n", static_cast<uint32_t>(ucfg.GetPrimType()));
|
||||
}
|
||||
// Sarah
|
||||
EXIT_NOT_IMPLEMENTED(!(draw.index_count == 3 && vs_input_info.buffers_num == 0));
|
||||
vk_buffer.draw(4, draw.instance_count, emit.first_vertex, emit.first_instance);
|
||||
break;
|
||||
case Prospero::PrimitiveType::kQuadListLegacy:
|
||||
EXIT_NOT_IMPLEMENTED((draw.index_count & 0x3u) != 0);
|
||||
for (uint32_t i = 0; i < draw.index_count; i += 4) {
|
||||
@@ -1182,7 +1170,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
|
||||
if (mesh_active) {
|
||||
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
|
||||
} else {
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, state.vertex_info[0], draw, emit);
|
||||
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
|
||||
}
|
||||
|
||||
if (!draw.IsIndexed()) {
|
||||
@@ -1258,7 +1246,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
|
||||
hw_check(buffer);
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, false, topology)) {
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1368,7 +1356,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
args.vertex_count, args.instance_count, args.first_instance};
|
||||
|
||||
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
|
||||
if (!GetDrawTopology(ucfg, true, topology)) {
|
||||
if (!GetDrawTopology(ucfg, topology)) {
|
||||
ResetBindings();
|
||||
return;
|
||||
}
|
||||
@@ -1378,7 +1366,7 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
|
||||
return;
|
||||
}
|
||||
|
||||
const bool rect_list = ucfg.GetPrimType() == Prospero::PrimitiveType::kRectList;
|
||||
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
|
||||
if (rect_list && state.vertex_info[0].buffers_num == 0 &&
|
||||
state.vertex_info[0].stage.program->param_export_mask == 0 &&
|
||||
state.ps_input_info.input_num != 0) {
|
||||
|
||||
@@ -610,10 +610,11 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
graphics.mesh_shader_properties.maxMeshOutputPrimitives,
|
||||
graphics.mesh_shader_properties.maxMeshSharedMemorySize);
|
||||
}
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These two
|
||||
// VulkanFindPhysicalDevice already checked the required creation features. These
|
||||
// requirements are specific to this creation path and are not part of device selection.
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.shaderInt64 != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.vertexPipelineStoresAndAtomics != VK_TRUE);
|
||||
EXIT_NOT_IMPLEMENTED(supported_features2.features.dualSrcBlend != VK_TRUE);
|
||||
vk::PhysicalDeviceFeatures device_features {};
|
||||
device_features.fragmentStoresAndAtomics = VK_TRUE;
|
||||
device_features.samplerAnisotropy = VK_TRUE;
|
||||
@@ -624,6 +625,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
|
||||
device_features.shaderStorageImageWriteWithoutFormat = VK_TRUE;
|
||||
device_features.shaderImageGatherExtended = VK_TRUE;
|
||||
device_features.independentBlend = VK_TRUE;
|
||||
device_features.dualSrcBlend = VK_TRUE;
|
||||
device_features.tessellationShader = VK_TRUE;
|
||||
device_features.sampleRateShading = VK_TRUE;
|
||||
device_features.depthBiasClamp = VK_TRUE;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
#include <filesystem>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
@@ -945,10 +947,10 @@ struct WindowIcon {
|
||||
}
|
||||
};
|
||||
|
||||
static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* icon) {
|
||||
Common::File f;
|
||||
if (!f.Open(path, Common::File::Mode::Read)) {
|
||||
EXIT("Can't open icon file %s\n", path.c_str());
|
||||
EXIT("Can't open icon file %s\n", Common::PathToString(path).c_str());
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
@@ -974,7 +976,7 @@ void WindowContext::UpdateIcon() {
|
||||
static bool icon_loaded = false;
|
||||
|
||||
if (!icon_loaded) {
|
||||
std::string icon_path;
|
||||
std::filesystem::path icon_path;
|
||||
if (Loader::SystemContentGetIconPath(&icon_path)) {
|
||||
WindowLoadPngIcon(icon_path, &icon);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ uint32_t EmitDppWriteCondition(ValueEmitContext& ctx, const IR::DppMoveFlags& fl
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), masks_ok, bank_ok, row_ok);
|
||||
uint32_t writable = masks_ok;
|
||||
if (!flags.bound_control) {
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto bounded = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpLogicalAnd, TypeBool(state), bounded, writable,
|
||||
target.valid);
|
||||
@@ -611,7 +611,7 @@ uint32_t EmitUndefU1(EmitterState& state, const IR::Inst& inst) {
|
||||
uint32_t EmitDppMoveU32(ValueEmitContext& ctx, const IR::Inst& inst) {
|
||||
auto& state = ctx.state;
|
||||
const auto flags = inst.Flags<IR::DppMoveFlags>();
|
||||
const auto target = EmitDppTargetLane(state, flags.control);
|
||||
const auto target = EmitDppTargetLane(state, flags);
|
||||
const auto shuffled = ctx.Shuffle(inst, 0, target.lane);
|
||||
if (flags.fetch_inactive) {
|
||||
return shuffled;
|
||||
|
||||
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Spirv::Emitter {
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control) {
|
||||
const auto quad_base = state.builder.AllocateId();
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits) {
|
||||
const auto lane_mask = (1u << lane_bits) - 1u;
|
||||
const auto group_base = state.builder.AllocateId();
|
||||
const auto lane = state.builder.AllocateId();
|
||||
const auto shift = state.builder.AllocateId();
|
||||
const auto selected0 = state.builder.AllocateId();
|
||||
const auto selected = state.builder.AllocateId();
|
||||
const auto target = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), quad_base, subid,
|
||||
ConstantU32(state, 0xfffffffcu));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), group_base, subid,
|
||||
ConstantU32(state, ~lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), lane, subid,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpShiftLeftLogical, TypeU32(state), shift, lane,
|
||||
ConstantU32(state, 1));
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpIMul, TypeU32(state), shift, lane,
|
||||
ConstantU32(state, lane_bits));
|
||||
state.builder.AddFunction(spv::OpShiftRightLogical, TypeU32(state), selected0,
|
||||
ConstantU32(state, control), shift);
|
||||
state.builder.AddFunction(spv::OpBitwiseAnd, TypeU32(state), selected, selected0,
|
||||
ConstantU32(state, 3));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, quad_base, selected);
|
||||
ConstantU32(state, lane_mask));
|
||||
state.builder.AddFunction(spv::OpBitwiseOr, TypeU32(state), target, group_base, selected);
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
@@ -89,10 +91,14 @@ DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool
|
||||
return {target, ConstantBool(state, true)};
|
||||
}
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control) {
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags) {
|
||||
const auto subid = EmitSubgroupLocalInvocationId(state);
|
||||
const auto control = flags.control;
|
||||
if (flags.dpp8) {
|
||||
return EmitDppPermTargetLane(state, subid, control, 3u);
|
||||
}
|
||||
if (control <= 0xffu) {
|
||||
return EmitDppQuadPermTargetLane(state, subid, control);
|
||||
return EmitDppPermTargetLane(state, subid, control, 2u);
|
||||
}
|
||||
if (control >= 0x101u && control <= 0x10fu) {
|
||||
return EmitDppRowShiftTargetLane(state, subid, control & 0xfu, true);
|
||||
|
||||
@@ -327,7 +327,8 @@ void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst);
|
||||
uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0);
|
||||
uint32_t MeshPrimitivePointer(EmitterState& state);
|
||||
|
||||
DppTargetLane EmitDppQuadPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control);
|
||||
DppTargetLane EmitDppPermTargetLane(EmitterState& state, uint32_t subid, uint32_t control,
|
||||
uint32_t lane_bits);
|
||||
|
||||
DppTargetLane EmitDppRowShiftTargetLane(EmitterState& state, uint32_t subid, uint32_t amount,
|
||||
bool left);
|
||||
@@ -336,7 +337,7 @@ DppTargetLane EmitDppRowRotateRightTargetLane(EmitterState& state, uint32_t subi
|
||||
|
||||
DppTargetLane EmitDppMirrorTargetLane(EmitterState& state, uint32_t subid, bool half_row);
|
||||
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, uint32_t control);
|
||||
DppTargetLane EmitDppTargetLane(EmitterState& state, const IR::DppMoveFlags& flags);
|
||||
|
||||
uint32_t EmitSubgroupLocalInvocationId(EmitterState& state);
|
||||
|
||||
|
||||
@@ -553,8 +553,17 @@ void DefineOutputs(EmitterState& state) {
|
||||
const auto type = uint_output ? TypeU32Vector(state, 4) : TypeF32Vector(state, 4);
|
||||
binding.variable_id = DefineInterfaceVariable(state, type, spv::StorageClassOutput,
|
||||
binding.debug_name.c_str());
|
||||
const bool dual_source = binding.kind == IR::StageOutputKind::Mrt &&
|
||||
state.program.stage == ShaderType::Pixel &&
|
||||
state.input_info.pixel->dual_source_blending;
|
||||
EXIT_NOT_IMPLEMENTED(dual_source && binding.index > 1);
|
||||
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
|
||||
spv::DecorationLocation, binding.location);
|
||||
spv::DecorationLocation,
|
||||
dual_source ? 0u : binding.location);
|
||||
if (dual_source) {
|
||||
state.builder.AddAnnotation(spv::OpDecorate, binding.variable_id,
|
||||
spv::DecorationIndex, binding.index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,8 +472,9 @@ std::string OperandToString(const Operand& operand) {
|
||||
text += ".clamp";
|
||||
}
|
||||
if (operand.dpp) {
|
||||
text += fmt::format(".dpp(ctrl=0x{:x},fi={},bc={})", operand.dpp_ctrl,
|
||||
operand.dpp_fetch_inactive ? 1u : 0u, operand.dpp_bound_ctrl ? 1u : 0u);
|
||||
text += fmt::format(".{}(ctrl=0x{:x},fi={},bc={})", operand.dpp8 ? "dpp8" : "dpp",
|
||||
operand.dpp_ctrl, operand.dpp_fetch_inactive ? 1u : 0u,
|
||||
operand.dpp_bound_ctrl ? 1u : 0u);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ enum class Opcode {
|
||||
V_CVT_PKRTZ_F16_F32,
|
||||
V_CVT_PK_U8_F32,
|
||||
V_MAD_F32,
|
||||
V_MAD_I16,
|
||||
V_MAD_I32_I24,
|
||||
V_MAD_U32_U24,
|
||||
V_MAD_U64_U32,
|
||||
@@ -300,6 +301,7 @@ enum class Opcode {
|
||||
V_SUBREV_NC_U32,
|
||||
V_ADD_NC_U16,
|
||||
V_SUB_NC_U16,
|
||||
V_MUL_LO_U16,
|
||||
V_MAX_U16,
|
||||
V_MAX_I16,
|
||||
V_MIN_U16,
|
||||
@@ -380,6 +382,7 @@ enum class Opcode {
|
||||
V_CMP_GE_F16,
|
||||
V_CMP_NGT_F16,
|
||||
V_CMP_NEQ_F16,
|
||||
V_CMP_NLT_F16,
|
||||
V_CMPX_LT_F16,
|
||||
V_CMPX_EQ_F16,
|
||||
V_CMPX_LE_F16,
|
||||
@@ -667,6 +670,7 @@ struct Operand {
|
||||
bool absolute = false;
|
||||
bool clamp = false;
|
||||
bool dpp = false;
|
||||
bool dpp8 = false;
|
||||
};
|
||||
|
||||
struct Instruction {
|
||||
|
||||
@@ -247,7 +247,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0xcau, Opcode::V_CMP_EQ_F16}, {0xcbu, Opcode::V_CMP_LE_F16},
|
||||
{0xccu, Opcode::V_CMP_GT_F16}, {0xcdu, Opcode::V_CMP_LG_F16},
|
||||
{0xceu, Opcode::V_CMP_GE_F16}, {0xebu, Opcode::V_CMP_NGT_F16},
|
||||
{0xedu, Opcode::V_CMP_NEQ_F16},
|
||||
{0xedu, Opcode::V_CMP_NEQ_F16}, {0xeeu, Opcode::V_CMP_NLT_F16},
|
||||
{0xd9u, Opcode::V_CMPX_LT_F16}, {0xdau, Opcode::V_CMPX_EQ_F16},
|
||||
{0xdbu, Opcode::V_CMPX_LE_F16}, {0xdcu, Opcode::V_CMPX_GT_F16},
|
||||
{0xdeu, Opcode::V_CMPX_GE_F16}, {0xfbu, Opcode::V_CMPX_NGT_F16},
|
||||
@@ -295,6 +295,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x300u, Opcode::V_LSHRREV_B64},
|
||||
{0x303u, Opcode::V_ADD_NC_U16},
|
||||
{0x304u, Opcode::V_SUB_NC_U16},
|
||||
{0x305u, Opcode::V_MUL_LO_U16},
|
||||
{0x307u, Opcode::V_LSHRREV_B16},
|
||||
{0x308u, Opcode::V_ASHRREV_I16},
|
||||
{0x309u, Opcode::V_MAX_U16},
|
||||
@@ -311,6 +312,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
|
||||
{0x345u, Opcode::V_XAD_U32},
|
||||
{0x346u, Opcode::V_LSHL_ADD_U32},
|
||||
{0x347u, Opcode::V_ADD_LSHL_U32},
|
||||
{0x35eu, Opcode::V_MAD_I16},
|
||||
{0x360u, Opcode::V_READLANE_B32},
|
||||
{0x361u, Opcode::V_WRITELANE_B32},
|
||||
{0x362u, Opcode::V_LDEXP_F32},
|
||||
@@ -423,13 +425,14 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool IsNativeVop3I16TernaryOpcode(Opcode opcode) {
|
||||
return opcode == Opcode::V_MED3_I16;
|
||||
return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
|
||||
switch (opcode) {
|
||||
case Opcode::V_ADD_NC_U16:
|
||||
case Opcode::V_SUB_NC_U16:
|
||||
case Opcode::V_MUL_LO_U16:
|
||||
case Opcode::V_MAX_U16:
|
||||
case Opcode::V_MAX_I16:
|
||||
case Opcode::V_MIN_U16:
|
||||
@@ -682,10 +685,15 @@ void DecodeVop1Sdwa(uint32_t pc, std::span<const uint32_t> code, uint32_t word_i
|
||||
ReadLiteralOperands(code, word_index, inst);
|
||||
}
|
||||
|
||||
void ApplyDppModifier(Operand& operand, uint32_t modifier) {
|
||||
void ApplyDppModifier(Operand& operand, uint32_t modifier, uint32_t encoding) {
|
||||
operand.dpp = true;
|
||||
if (encoding == 233u) {
|
||||
operand.dpp8 = true;
|
||||
operand.dpp_ctrl = modifier >> 8u;
|
||||
return;
|
||||
}
|
||||
operand.negate = ((modifier >> 20u) & 0x1u) != 0u;
|
||||
operand.absolute = ((modifier >> 21u) & 0x1u) != 0u;
|
||||
operand.dpp = true;
|
||||
operand.dpp_ctrl = (modifier >> 8u) & 0x1ffu;
|
||||
operand.dpp_fetch_inactive = ((modifier >> 18u) & 0x1u) != 0u;
|
||||
operand.dpp_bound_ctrl = ((modifier >> 19u) & 0x1u) != 0u;
|
||||
@@ -706,7 +714,7 @@ void DecodeVop1Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
}
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src_count = 1;
|
||||
|
||||
if (!IsVop1FloatSourceOpcode(inst.opcode) && (inst.src0.negate || inst.src0.absolute)) {
|
||||
@@ -823,6 +831,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
|
||||
case Opcode::V_CMP_GE_F16:
|
||||
case Opcode::V_CMP_NGT_F16:
|
||||
case Opcode::V_CMP_NEQ_F16:
|
||||
case Opcode::V_CMP_NLT_F16:
|
||||
case Opcode::V_CMPX_LT_F16:
|
||||
case Opcode::V_CMPX_EQ_F16:
|
||||
case Opcode::V_CMPX_LE_F16:
|
||||
@@ -1044,7 +1053,7 @@ void DecodeVop2Dpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vdst, inst.dst);
|
||||
DecodeVectorGpr(vsrc1, inst.src1);
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u;
|
||||
inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u;
|
||||
const bool packed_fmac = inst.opcode == Opcode::V_PK_FMAC_F16;
|
||||
@@ -1153,7 +1162,7 @@ void DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
|
||||
DecodeVectorGpr(vsrc1, inst.src1);
|
||||
DecodeScalarSource(src0 + 256u, pc, inst.src0);
|
||||
inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
|
||||
ApplyDppModifier(inst.src0, modifier);
|
||||
ApplyDppModifier(inst.src0, modifier, code[word_index] & 0x1ffu);
|
||||
inst.src1.negate = ((modifier >> 22u) & 0x1u) != 0u;
|
||||
inst.src1.absolute = ((modifier >> 23u) & 0x1u) != 0u;
|
||||
inst.src_count = 2;
|
||||
@@ -1169,6 +1178,7 @@ uint32_t NativeVop3SourceCount(Opcode opcode) {
|
||||
case Opcode::V_MUL_I32_I24:
|
||||
case Opcode::V_ADD_NC_U16:
|
||||
case Opcode::V_SUB_NC_U16:
|
||||
case Opcode::V_MUL_LO_U16:
|
||||
case Opcode::V_MAX_U16:
|
||||
case Opcode::V_MAX_I16:
|
||||
case Opcode::V_MIN_U16:
|
||||
@@ -1353,7 +1363,8 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
|
||||
}
|
||||
|
||||
bool SupportsNativeVop3Clamp(Opcode opcode) {
|
||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode);
|
||||
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
|
||||
opcode == Opcode::V_MAD_I16;
|
||||
}
|
||||
|
||||
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
|
||||
@@ -1374,7 +1385,7 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
|
||||
return opcode != Opcode::V_FMA_F16 && (clamp != 0u || omod != 0u);
|
||||
}
|
||||
if (IsNativeVop3I16TernaryOpcode(opcode)) {
|
||||
return abs != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u;
|
||||
}
|
||||
if (IsNativeVop3B16BinaryOpcode(opcode)) {
|
||||
return abs != 0u || clamp != 0u || omod != 0u || neg != 0u;
|
||||
@@ -1508,6 +1519,7 @@ void DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
|
||||
return;
|
||||
}
|
||||
switch (src0) {
|
||||
case 233u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 249u: DecodeVop1Sdwa(pc, code, word_index, opcode, vdst, inst); return;
|
||||
case 250u: DecodeVop1Dpp(pc, code, word_index, opcode, vdst, inst); return;
|
||||
default: break;
|
||||
|
||||
@@ -166,9 +166,9 @@ void Translator::V_CVT_PK_U8_F32(const Decoder::Instruction& inst) {
|
||||
}
|
||||
|
||||
void Translator::V_PACK_B32_F16(const Decoder::Instruction& inst) {
|
||||
const auto low = ReadF16LaneBits(inst.src0, false);
|
||||
const auto low = Read16LaneBits(inst.src0, false);
|
||||
const auto high =
|
||||
ir.ShiftLeftLogical(ReadF16LaneBits(inst.src1, false), IR::U32(IR::Value(16u)));
|
||||
ir.ShiftLeftLogical(Read16LaneBits(inst.src1, false), IR::U32(IR::Value(16u)));
|
||||
WriteOperand(DestinationOperand(inst), ir.BitwiseOr(low, high));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,18 @@ bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MAD_I16(const Decoder::Instruction& inst) {
|
||||
const auto lhs = ReadU16AsU32(inst.src0, true);
|
||||
const auto rhs = ReadU16AsU32(inst.src1, true);
|
||||
auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true));
|
||||
if (inst.dst.clamp) {
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {result, IR::Value(0xffff8000u)}));
|
||||
result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)}));
|
||||
}
|
||||
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Translator::V_MED3_I16(const Decoder::Instruction& inst) {
|
||||
const auto result = IR::U32(ir.Emit(
|
||||
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
|
||||
|
||||
@@ -34,6 +34,7 @@ Decoder::Operand OffsetDecodedRegister(const Decoder::Operand& operand, uint32_t
|
||||
result.negate_hi = false;
|
||||
result.absolute = false;
|
||||
result.dpp_ctrl = 0;
|
||||
result.dpp8 = false;
|
||||
result.dpp_row_mask = 0xf;
|
||||
result.dpp_bank_mask = 0xf;
|
||||
result.explicit_sdwa_dst = false;
|
||||
|
||||
@@ -9,6 +9,17 @@
|
||||
|
||||
namespace Libs::Graphics::ShaderRecompiler::Frontend {
|
||||
|
||||
static IR::DppMoveFlags DppFlags(const Decoder::Operand& operand) {
|
||||
return {
|
||||
.control = operand.dpp_ctrl,
|
||||
.row_mask = operand.dpp_row_mask,
|
||||
.bank_mask = operand.dpp_bank_mask,
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
.dpp8 = operand.dpp8,
|
||||
};
|
||||
}
|
||||
|
||||
const Decoder::Operand& Translator::SourceAt(const Decoder::Instruction& inst, uint32_t index) {
|
||||
switch (index) {
|
||||
case 0: return inst.src0;
|
||||
@@ -30,6 +41,7 @@ Decoder::Operand Translator::DestinationOperand(const Decoder::Instruction& inst
|
||||
continue;
|
||||
}
|
||||
destination.dpp = true;
|
||||
destination.dpp8 = source.dpp8;
|
||||
destination.dpp_ctrl = source.dpp_ctrl;
|
||||
destination.dpp_row_mask = source.dpp_row_mask;
|
||||
destination.dpp_bank_mask = source.dpp_bank_mask;
|
||||
@@ -106,6 +118,7 @@ Decoder::Operand Translator::PlainOperand(const Decoder::Operand& operand) {
|
||||
result.dpp_fetch_inactive = false;
|
||||
result.dpp_bound_ctrl = false;
|
||||
result.dpp = false;
|
||||
result.dpp8 = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -167,14 +180,8 @@ IR::U32 Translator::ReadScalarCode(uint32_t code) {
|
||||
|
||||
IR::U32 Translator::ApplyBitSourceModifiers(const Decoder::Operand& operand, IR::U32 value) {
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppMoveU32, {value, ir.GetExec()}, flags));
|
||||
value =
|
||||
IR::U32(ir.Emit(IR::ValueOpcode::DppMoveU32, {value, ir.GetExec()}, DppFlags(operand)));
|
||||
}
|
||||
if (operand.sdwa_sel != 6u) {
|
||||
uint32_t offset = 0;
|
||||
@@ -301,15 +308,8 @@ void Translator::WriteRawU32(const Decoder::Operand& operand, IR::U32 value) {
|
||||
const auto reg = static_cast<IR::VectorReg>(operand.reg);
|
||||
const auto old = ir.GetVectorReg(reg);
|
||||
if (operand.dpp) {
|
||||
const IR::DppMoveFlags flags {
|
||||
.control = static_cast<uint16_t>(operand.dpp_ctrl),
|
||||
.row_mask = static_cast<uint8_t>(operand.dpp_row_mask),
|
||||
.bank_mask = static_cast<uint8_t>(operand.dpp_bank_mask),
|
||||
.fetch_inactive = operand.dpp_fetch_inactive,
|
||||
.bound_control = operand.dpp_bound_ctrl,
|
||||
};
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()}, flags));
|
||||
value = IR::U32(ir.Emit(IR::ValueOpcode::DppUpdateU32, {value, old, ir.GetExec()},
|
||||
DppFlags(operand)));
|
||||
} else {
|
||||
value = ir.Select(ir.GetExec(), value, old);
|
||||
}
|
||||
@@ -541,10 +541,7 @@ IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_la
|
||||
|
||||
IR::U32 Translator::ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane,
|
||||
bool sign_extend) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
value = ir.BitwiseAnd(ir.ISub(IR::U32(IR::Value(0u)), value), IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
auto value = Read16LaneBits(operand, high_lane);
|
||||
if (sign_extend || operand.sdwa_sext) {
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {value, IR::Value(0u), IR::Value(16u)}));
|
||||
@@ -556,12 +553,13 @@ IR::U32 Translator::ReadU16AsU32(const Decoder::Operand& operand, bool sign_exte
|
||||
return ReadU16LaneAsU32(operand, false, sign_extend);
|
||||
}
|
||||
|
||||
IR::U32 Translator::ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane) {
|
||||
IR::U32 Translator::Read16LaneBits(const Decoder::Operand& operand, bool high_lane) {
|
||||
auto value = ReadU16LaneRaw(operand, high_lane);
|
||||
if (operand.absolute) {
|
||||
value = ir.BitwiseAnd(value, IR::U32(IR::Value(0x7fffu)));
|
||||
}
|
||||
if (high_lane ? operand.negate_hi : operand.negate) {
|
||||
// RDNA2 source NEG flips the sign bit, including packed integer operands.
|
||||
value = ir.BitwiseXor(value, IR::U32(IR::Value(0x8000u)));
|
||||
}
|
||||
return value;
|
||||
|
||||
@@ -44,7 +44,7 @@ private:
|
||||
IR::U32 ReadU16LaneRaw(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::U32 ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane, bool sign_extend);
|
||||
IR::U32 ReadU16AsU32(const Decoder::Operand& operand, bool sign_extend);
|
||||
IR::U32 ReadF16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
IR::U32 Read16LaneBits(const Decoder::Operand& operand, bool high_lane);
|
||||
std::array<IR::U32, 2> ExtractU64(IR::U64 value);
|
||||
void WriteU32Pair(const Decoder::Operand& operand, const std::array<IR::U32, 2>& value);
|
||||
IR::U32 ConditionBit(const Decoder::Operand& operand);
|
||||
@@ -164,6 +164,7 @@ private:
|
||||
bool FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
|
||||
bool Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
|
||||
bool Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
|
||||
bool V_MAD_I16(const Decoder::Instruction& inst);
|
||||
bool V_MED3_I16(const Decoder::Instruction& inst);
|
||||
bool PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
|
||||
bool arithmetic);
|
||||
|
||||
@@ -276,6 +276,9 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMPX_NEQ_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordNotEqual32, true, true);
|
||||
return true;
|
||||
case O::V_CMP_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, false);
|
||||
return true;
|
||||
case O::V_CMPX_NLT_F16:
|
||||
EmitFloatCompare(inst, IR::ValueOpcode::FPUnordGreaterThanEqual32, true, true);
|
||||
return true;
|
||||
@@ -324,6 +327,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_ADD_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::IAdd32, false);
|
||||
case O::V_SUB_NC_U16:
|
||||
case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false);
|
||||
case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false);
|
||||
case O::V_MAD_I16: return V_MAD_I16(inst);
|
||||
case O::V_MED3_I16: return V_MED3_I16(inst);
|
||||
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
|
||||
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
|
||||
|
||||
@@ -34,11 +34,12 @@ struct ImageOpcodeInfo {
|
||||
};
|
||||
|
||||
struct DppMoveFlags {
|
||||
uint16_t control = 0;
|
||||
uint8_t row_mask = 0xf;
|
||||
uint8_t bank_mask = 0xf;
|
||||
uint32_t control : 24 = 0;
|
||||
uint32_t row_mask : 4 = 0xf;
|
||||
uint32_t bank_mask : 4 = 0xf;
|
||||
bool fetch_inactive = false;
|
||||
bool bound_control = false;
|
||||
bool dpp8 = false;
|
||||
};
|
||||
static_assert(sizeof(DppMoveFlags) <= sizeof(uint64_t));
|
||||
static_assert(std::is_trivially_copyable_v<DppMoveFlags>);
|
||||
|
||||
@@ -125,6 +125,7 @@ bool IsRuntimeUniformOp(ValueOpcode op) {
|
||||
case ValueOpcode::ULessThan32:
|
||||
case ValueOpcode::IEqual32:
|
||||
case ValueOpcode::UGreaterThan32:
|
||||
case ValueOpcode::SGreaterThanEqual32:
|
||||
case ValueOpcode::INotEqual32:
|
||||
case ValueOpcode::LogicalOr:
|
||||
case ValueOpcode::LogicalAnd:
|
||||
@@ -930,6 +931,13 @@ private:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::SGreaterThanEqual32:
|
||||
if (binary()) {
|
||||
result = std::bit_cast<int32_t>(static_cast<uint32_t>(a)) >=
|
||||
std::bit_cast<int32_t>(static_cast<uint32_t>(b));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case ValueOpcode::LogicalAnd:
|
||||
if (binary()) {
|
||||
result = (a != 0u) && (b != 0u);
|
||||
|
||||
@@ -721,6 +721,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
|
||||
key.push_back(static_cast<uint32_t>(info.ps_depth_export_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
|
||||
key.push_back(static_cast<uint32_t>(info.ps_early_z));
|
||||
key.push_back(static_cast<uint32_t>(info.dual_source_blending));
|
||||
key.insert(key.end(), std::begin(info.target_output_mode), std::end(info.target_output_mode));
|
||||
for (uint32_t base = 0; base < info.target_export_mapping.size(); base += 4u) {
|
||||
uint32_t packed = 0;
|
||||
|
||||
@@ -175,6 +175,7 @@ struct ShaderPixelInputInfo {
|
||||
bool ps_depth_export_enable = false;
|
||||
bool ps_sample_mask_export_enable = false;
|
||||
bool ps_sample_shading = false;
|
||||
bool dual_source_blending = false;
|
||||
bool ps_early_z = false;
|
||||
bool ps_execute_on_noop = false;
|
||||
ShaderStageRuntime stage;
|
||||
|
||||
@@ -251,16 +251,15 @@ void FileDescriptors::CloseAll() {
|
||||
void MountPoints::Mount(const std::filesystem::path& folder, const std::string& point) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto folder_str = Common::FixDirectorySlash(Common::PathToGenericString(folder));
|
||||
auto point_str = Common::FixDirectorySlash(point);
|
||||
|
||||
Umount(point_str);
|
||||
|
||||
MountPair p;
|
||||
p.dir = folder_str;
|
||||
p.dir = folder;
|
||||
p.point = point_str;
|
||||
|
||||
m_mount_pairs.push_back(p);
|
||||
m_mount_pairs.push_back(std::move(p));
|
||||
}
|
||||
|
||||
void MountPoints::Umount(const std::string& folder_or_point) {
|
||||
@@ -270,7 +269,7 @@ void MountPoints::Umount(const std::string& folder_or_point) {
|
||||
|
||||
const auto it = std::find_if(
|
||||
m_mount_pairs.begin(), m_mount_pairs.end(), [&folder_or_point_str](const MountPair& p) {
|
||||
return Common::PathToGenericString(p.dir) == folder_or_point_str ||
|
||||
return Common::FixDirectorySlash(Common::PathToGenericString(p.dir)) == folder_or_point_str ||
|
||||
p.point == folder_or_point_str;
|
||||
});
|
||||
if (it != m_mount_pairs.end()) {
|
||||
@@ -343,14 +342,17 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name)
|
||||
while (rel_path.starts_with('/')) {
|
||||
rel_path = Common::RemoveFirst(rel_path, 1);
|
||||
}
|
||||
|
||||
const auto native_rel_path = Common::PathFromUtf8(rel_path);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (HasWindowsForbiddenFilenameCharacter(rel_path)) {
|
||||
::printf("FileSystem: Windows-incompatible guest filename: %s\n",
|
||||
mounted_name.c_str());
|
||||
}
|
||||
return p.dir / rel_path;
|
||||
return p.dir / native_rel_path;
|
||||
#else
|
||||
return ResolvePathIgnoringCase(p.dir / rel_path);
|
||||
return ResolvePathIgnoringCase(p.dir / native_rel_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,12 @@
|
||||
<property name="text"><string>Enable readback</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="checkBox_tessellation">
|
||||
<property name="toolTip"><string>Draw tessellated geometry. Disabled by default; unsupported tessellation may cause crashes.</string></property>
|
||||
<property name="text"><string>Enable tessellation support (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -275,13 +281,13 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_amd_cpu">
|
||||
<property name="toolTip"><string>Patch CPU instructions that differ between AMD and the host CPU.</string></property>
|
||||
<property name="text"><string>AMD CPU patch (EXPERIMENTAL)</string></property>
|
||||
<property name="text"><string>AMD CPU patch (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_red_zone_protection">
|
||||
<property name="toolTip"><string>Helps prevent crashes in some games on Windows</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (Experimental)</string></property>
|
||||
<property name="text"><string>Windows SysV red zone crash protection (experimental)</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
int gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool readback_linear_images = false;
|
||||
bool tessellation_enabled = false;
|
||||
int vblank_frequency = 60;
|
||||
int console_language = DEFAULT_CONSOLE_LANGUAGE;
|
||||
bool vulkan_validation_enabled = false;
|
||||
@@ -122,6 +123,7 @@ public:
|
||||
gpu_index = other.gpu_index;
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
readback_linear_images = other.readback_linear_images;
|
||||
tessellation_enabled = other.tessellation_enabled;
|
||||
vblank_frequency = other.vblank_frequency;
|
||||
console_language = other.console_language;
|
||||
vulkan_validation_enabled = other.vulkan_validation_enabled;
|
||||
@@ -169,6 +171,7 @@ public:
|
||||
KYTY_CFG_SET(gpu_index);
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
KYTY_CFG_SET(readback_linear_images);
|
||||
KYTY_CFG_SET(tessellation_enabled);
|
||||
KYTY_CFG_SET(vblank_frequency);
|
||||
KYTY_CFG_SET(console_language);
|
||||
KYTY_CFG_SET(vulkan_validation_enabled);
|
||||
@@ -210,6 +213,7 @@ public:
|
||||
}
|
||||
KYTY_CFG_GET(fullscreen_enabled);
|
||||
KYTY_CFG_GET(readback_linear_images);
|
||||
KYTY_CFG_GET(tessellation_enabled);
|
||||
vblank_frequency = s->value("vblank_frequency", vblank_frequency).toInt();
|
||||
console_language = s->value("console_language", console_language).toInt();
|
||||
if (console_language < 0 || console_language > MAX_CONSOLE_LANGUAGE) {
|
||||
|
||||
@@ -230,6 +230,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
: 0);
|
||||
m_ui->checkBox_fullscreen->setChecked(info.fullscreen_enabled);
|
||||
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
|
||||
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
|
||||
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
|
||||
m_ui->comboBox_console_language->clear();
|
||||
m_ui->comboBox_console_language->addItems(CONSOLE_LANGUAGE_NAMES);
|
||||
@@ -375,6 +376,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.gpu_index = ui.comboBox_gpu->currentIndex() - 1;
|
||||
info.fullscreen_enabled = ui.checkBox_fullscreen->isChecked();
|
||||
info.readback_linear_images = ui.checkBox_readback->isChecked();
|
||||
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
|
||||
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
|
||||
info.console_language = ui.comboBox_console_language->currentIndex();
|
||||
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
|
||||
|
||||
@@ -226,6 +226,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
args << "--fullscreen";
|
||||
}
|
||||
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
|
||||
if (info.tessellation_enabled) {
|
||||
args << "--tessellation";
|
||||
}
|
||||
args << "--vblank-frequency" << QString::number(info.vblank_frequency);
|
||||
args << "--console-language" << QString::number(info.console_language);
|
||||
args << "--vulkan-validation" << BoolArg(info.vulkan_validation_enabled);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
PatchesDialog::PatchesDialog(const Configuration& game, QWidget* parent)
|
||||
: QDialog(parent), m_title_id(game.title_id.trimmed().toUpper()) {
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle(tr("Cheats (Experimental) - %1").arg(game.name));
|
||||
setWindowTitle(tr("Cheats (experimental) - %1").arg(game.name));
|
||||
resize(640, 480);
|
||||
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
|
||||
+3
-2
@@ -849,8 +849,9 @@ int KYTY_SYSV_ABI AjmMemoryUnregister(uint32_t context, void* ptr) {
|
||||
int KYTY_SYSV_ABI AjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(buffer == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(info == nullptr);
|
||||
if (buffer == nullptr || info == nullptr) {
|
||||
return AJM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
info->buffer = buffer;
|
||||
info->offset = 0;
|
||||
|
||||
+32
-22
@@ -38,9 +38,9 @@ static constexpr uint32_t AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX = 11;
|
||||
|
||||
class AjmAacDecoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmAacDecoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
AjmAacDecoder(uint32_t max_channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding), m_flags(flags) {
|
||||
: AjmDecoder(0, sample_rate, encoding), m_max_channels(max_channels), m_flags(flags) {
|
||||
m_codec = avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||
if (m_codec == nullptr) {
|
||||
LOGF("AJM AAC: avcodec_find_decoder failed\n");
|
||||
@@ -94,6 +94,11 @@ public:
|
||||
((m_flags & AJM_INSTANCE_FLAG_DEC_M4AAC_ENABLE_NONDELAY_OUTPUT) != 0 ? 0u : 2u);
|
||||
m_total_decoded_samples = 0;
|
||||
m_bitrate = 0;
|
||||
m_channels = 0;
|
||||
m_raw_configured = false;
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW) {
|
||||
m_sample_rate = SamplingRateFromIndex(m_sampling_freq_index);
|
||||
}
|
||||
if (m_is_initialized) {
|
||||
OpenDecoder();
|
||||
}
|
||||
@@ -189,26 +194,6 @@ private:
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW) {
|
||||
const auto sample_index =
|
||||
std::min(m_sampling_freq_index, AJM_DEC_M4AAC_MAX_SAMPLING_FREQ_INDEX);
|
||||
const auto channels = std::clamp<uint32_t>(m_channels, 1u, AJM_DEC_M4AAC_MAX_CHANNELS);
|
||||
constexpr uint32_t audio_object_type = 2;
|
||||
|
||||
auto* extradata = static_cast<uint8_t*>(av_mallocz(2 + AV_INPUT_BUFFER_PADDING_SIZE));
|
||||
if (extradata == nullptr) {
|
||||
Release();
|
||||
return;
|
||||
}
|
||||
|
||||
extradata[0] = static_cast<uint8_t>((audio_object_type << 3u) | (sample_index >> 1u));
|
||||
extradata[1] = static_cast<uint8_t>(((sample_index & 0x1u) << 7u) |
|
||||
(std::min(channels, 7u) << 3u));
|
||||
m_codec_context->extradata = extradata;
|
||||
m_codec_context->extradata_size = 2;
|
||||
SetFormat(channels, SamplingRateFromIndex(sample_index), m_sample_encoding);
|
||||
}
|
||||
|
||||
if (avcodec_open2(m_codec_context, m_codec, nullptr) < 0) {
|
||||
LOGF("AJM AAC: decoder initialization failed\n");
|
||||
Release();
|
||||
@@ -222,6 +207,10 @@ private:
|
||||
}
|
||||
|
||||
const auto channels = static_cast<uint32_t>(std::max(frame->ch_layout.nb_channels, 1));
|
||||
if (channels > m_max_channels) {
|
||||
result->result = AJM_RESULT_TOO_MANY_CHANNELS;
|
||||
return false;
|
||||
}
|
||||
const auto bpf = channels * AjmBytesPerSample(m_sample_encoding);
|
||||
if (bpf == 0) {
|
||||
result->result = AJM_RESULT_INVALID_PARAMETER;
|
||||
@@ -306,6 +295,25 @@ private:
|
||||
return false;
|
||||
}
|
||||
std::memcpy(packet->data, packet_data, static_cast<size_t>(packet_size));
|
||||
if (m_config_number == AJM_DEC_M4AAC_CONFIG_NUMBER_RAW && !m_raw_configured) {
|
||||
// Only indexed stereo starts with a channel pair; the instance limit is not
|
||||
// the stream's channel count. A leading SCE remains ambiguous.
|
||||
const auto channels = (packet_data[0] >> 5u) == 1u
|
||||
? 2u
|
||||
: std::clamp(m_max_channels, 1u, AJM_DEC_M4AAC_MAX_CHANNELS);
|
||||
auto* extradata = av_packet_new_side_data(packet, AV_PKT_DATA_NEW_EXTRADATA, 2);
|
||||
if (extradata == nullptr) {
|
||||
av_packet_free(&packet);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return false;
|
||||
}
|
||||
constexpr uint32_t audio_object_type = 2;
|
||||
extradata[0] = static_cast<uint8_t>((audio_object_type << 3u) |
|
||||
(m_sampling_freq_index >> 1u));
|
||||
extradata[1] = static_cast<uint8_t>(((m_sampling_freq_index & 1u) << 7u) |
|
||||
(std::min(channels, 7u) << 3u));
|
||||
m_raw_configured = true;
|
||||
}
|
||||
|
||||
int rc = avcodec_send_packet(m_codec_context, packet);
|
||||
av_packet_free(&packet);
|
||||
@@ -353,12 +361,14 @@ private:
|
||||
|
||||
const AVCodec* m_codec = nullptr;
|
||||
AVCodecContext* m_codec_context = nullptr;
|
||||
const uint32_t m_max_channels;
|
||||
uint64_t m_flags = 0;
|
||||
uint32_t m_config_number = AJM_DEC_M4AAC_CONFIG_NUMBER_ADTS;
|
||||
uint32_t m_sampling_freq_index = 3;
|
||||
uint32_t m_skip_frames = 2;
|
||||
uint32_t m_bitrate = 0;
|
||||
bool m_is_initialized = false;
|
||||
bool m_raw_configured = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Audio::Ajm
|
||||
|
||||
@@ -72,6 +72,7 @@ constexpr int32_t AJM_RESULT_INVALID_DATA = 0x00000002;
|
||||
constexpr int32_t AJM_RESULT_INVALID_PARAMETER = 0x00000004;
|
||||
constexpr int32_t AJM_RESULT_PARTIAL_INPUT = 0x00000008;
|
||||
constexpr int32_t AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
|
||||
constexpr int32_t AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
|
||||
constexpr int32_t AJM_RESULT_CODEC_ERROR = 0x40000000;
|
||||
constexpr int32_t AJM_RESULT_FATAL = static_cast<int32_t>(0x80000000u);
|
||||
|
||||
|
||||
+542
-130
@@ -9,6 +9,7 @@
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "kernel/semaphore.h"
|
||||
#include "libs/ajm/atrac9_decoder.h"
|
||||
#include "libs/audio_internal.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -18,8 +19,10 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <magic_enum.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "libatrac9.h"
|
||||
@@ -1824,26 +1827,146 @@ enum class Ngs2RackType {
|
||||
CustomSampler,
|
||||
};
|
||||
|
||||
struct Ngs2UserFxContext {
|
||||
void* common;
|
||||
void* param;
|
||||
void* work;
|
||||
uintptr_t user_data;
|
||||
uint32_t max_voices;
|
||||
uint32_t voice_index;
|
||||
uint64_t reserved[4] {};
|
||||
};
|
||||
|
||||
struct Ngs2UserFxProcessContext {
|
||||
float** channels;
|
||||
void* common;
|
||||
const void* param;
|
||||
void* work;
|
||||
void* state;
|
||||
uintptr_t user_data;
|
||||
uint32_t flags;
|
||||
uint32_t input_channels;
|
||||
uint32_t output_channels;
|
||||
uint32_t grain_samples;
|
||||
uint32_t sample_rate;
|
||||
uint32_t reserved {};
|
||||
uint64_t reserved2[4] {};
|
||||
};
|
||||
|
||||
struct Ngs2UserFxOption {
|
||||
Ngs2CustomModuleOption header;
|
||||
int KYTY_SYSV_ABI (*setup)(Ngs2UserFxContext*);
|
||||
int KYTY_SYSV_ABI (*cleanup)(Ngs2UserFxContext*);
|
||||
uintptr_t control;
|
||||
int KYTY_SYSV_ABI (*process)(Ngs2UserFxProcessContext*);
|
||||
size_t common_size;
|
||||
size_t param_size;
|
||||
size_t work_size;
|
||||
uintptr_t user_data;
|
||||
};
|
||||
|
||||
struct Ngs2RackInternal {
|
||||
Ngs2Internal* ngs = nullptr;
|
||||
Ngs2RackInternal* next = nullptr;
|
||||
Ngs2RackType type = Ngs2RackType::Sampler;
|
||||
Ngs2RackOptionUnion option;
|
||||
Ngs2ContextBufferInfo buffer_info;
|
||||
Ngs2BufferAllocator allocator;
|
||||
Ngs2Internal* ngs = nullptr;
|
||||
Ngs2RackInternal* next = nullptr;
|
||||
Ngs2RackType type = Ngs2RackType::Sampler;
|
||||
Ngs2RackOptionUnion option;
|
||||
Ngs2ContextBufferInfo buffer_info;
|
||||
Ngs2BufferAllocator allocator;
|
||||
std::array<std::vector<uint8_t>, 24> common;
|
||||
std::array<Ngs2UserFxOption, 24> fx {};
|
||||
};
|
||||
|
||||
enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped };
|
||||
|
||||
enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill };
|
||||
|
||||
struct Ngs2VoiceInternal {
|
||||
Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None;
|
||||
Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty;
|
||||
Ngs2RackInternal* rack = nullptr;
|
||||
uintptr_t callback = 0;
|
||||
uintptr_t callback_data = 0;
|
||||
uint32_t callback_flags = 0;
|
||||
struct Port {
|
||||
Ngs2VoiceInternal* dest = nullptr;
|
||||
uint32_t input = 0;
|
||||
float volume = 1.0f;
|
||||
int32_t matrix = -1;
|
||||
};
|
||||
struct Block {
|
||||
const uint8_t* data;
|
||||
Ngs2WaveformBlock info;
|
||||
uint32_t cursor = 0;
|
||||
size_t data_cursor = 0;
|
||||
uint32_t skip_remaining = info.num_skip_samples;
|
||||
};
|
||||
struct Module {
|
||||
std::vector<uint8_t> param, work, state;
|
||||
uint32_t flags = 1;
|
||||
};
|
||||
Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None;
|
||||
Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty;
|
||||
Ngs2RackInternal* rack = nullptr;
|
||||
uintptr_t callback = 0;
|
||||
uintptr_t callback_data = 0;
|
||||
uint32_t callback_flags = 0;
|
||||
std::deque<Block> blocks;
|
||||
std::vector<Port> ports;
|
||||
std::vector<std::vector<float>> matrices;
|
||||
std::vector<Module> modules;
|
||||
std::vector<float> samples;
|
||||
std::vector<float> output_matrix;
|
||||
uint32_t channels = 0;
|
||||
uint32_t output_id = 0;
|
||||
bool rendering = false;
|
||||
bool rendered = false;
|
||||
bool has_samples = false;
|
||||
|
||||
std::unique_ptr<Ajm::AjmAt9Decoder> decoder;
|
||||
std::vector<float> decoded_frame;
|
||||
bool accepts_blocks = true;
|
||||
|
||||
void SetupSampler(const Ngs2WaveformFormat& format) {
|
||||
EXIT_NOT_IMPLEMENTED(format.sample_rate != rack->ngs->option.sample_rate ||
|
||||
format.frame_margin != 0 || format.frame_offset != 0);
|
||||
channels = format.num_channels;
|
||||
blocks.clear();
|
||||
decoder.reset();
|
||||
decoded_frame.clear();
|
||||
accepts_blocks = true;
|
||||
if (format.waveform_type == NGS2_WAVEFORM_TYPE_ATRAC9) {
|
||||
decoder = std::make_unique<Ajm::AjmAt9Decoder>(channels, format.sample_rate,
|
||||
Ajm::AjmSampleEncoding::Float, 0);
|
||||
const auto result =
|
||||
decoder->Initialize(&format.config_data, sizeof(format.config_data));
|
||||
EXIT_NOT_IMPLEMENTED(result.result != OK);
|
||||
EXIT_NOT_IMPLEMENTED(result.format.channel_num != channels ||
|
||||
result.format.sampling_frequency != format.sample_rate);
|
||||
Ajm::AjmSidebandDecAt9CodecInfo info {};
|
||||
decoder->WriteCodecInfo(&info, sizeof(info), result);
|
||||
EXIT_NOT_IMPLEMENTED(info.frame_samples > std::numeric_limits<uint16_t>::max());
|
||||
decoded_frame.resize(info.frame_samples * channels);
|
||||
} else {
|
||||
EXIT_NOT_IMPLEMENTED(format.waveform_type != 0x12);
|
||||
}
|
||||
}
|
||||
|
||||
void SetMatrix(uint32_t index, const float* levels, uint32_t count) {
|
||||
EXIT_NOT_IMPLEMENTED(index >= matrices.size());
|
||||
matrices[index].assign(levels, levels + count);
|
||||
}
|
||||
void SetVolume(uint32_t index, float volume) {
|
||||
EXIT_NOT_IMPLEMENTED(index >= ports.size());
|
||||
ports[index].volume = volume;
|
||||
}
|
||||
void SetPortMatrix(uint32_t index, int32_t matrix) {
|
||||
EXIT_NOT_IMPLEMENTED(index >= ports.size());
|
||||
ports[index].matrix = matrix;
|
||||
}
|
||||
void SetEvent(uint32_t id) {
|
||||
switch (id) {
|
||||
case 1: event = Ngs2VoicePlayEvent::Play; break;
|
||||
case 2: event = Ngs2VoicePlayEvent::Stop; break;
|
||||
case 4: event = Ngs2VoicePlayEvent::StopImm; break;
|
||||
case 8: event = Ngs2VoicePlayEvent::Kill; break;
|
||||
case 16: event = Ngs2VoicePlayEvent::Pause; break;
|
||||
case 32: event = Ngs2VoicePlayEvent::Resume; break;
|
||||
default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Ngs2VoiceParamHeader {
|
||||
@@ -1931,6 +2054,9 @@ static Ngs2RackInternal* g_racks_list = nullptr;
|
||||
static std::atomic_uint32_t g_next_ngs_uid = 1;
|
||||
static Common::Mutex g_racks_mutex;
|
||||
|
||||
static_assert(sizeof(Ngs2UserFxContext) == 72);
|
||||
static_assert(sizeof(Ngs2UserFxProcessContext) == 104);
|
||||
static_assert(sizeof(Ngs2UserFxOption) == 72);
|
||||
static_assert(sizeof(Ngs2SystemOption) == 144);
|
||||
static_assert(sizeof(Ngs2SystemInfo) == 184);
|
||||
static_assert(sizeof(Ngs2RackOption) == 176);
|
||||
@@ -2272,6 +2398,35 @@ int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferIn
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void Ngs2InitModules(Ngs2RackInternal& rack) {
|
||||
if (!Ngs2RackIsCustom(rack.type)) {
|
||||
return;
|
||||
}
|
||||
const auto& custom = rack.option.custom_sampler.custom_rack_option;
|
||||
EXIT_NOT_IMPLEMENTED(custom.num_buffers != 1);
|
||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(&rack + 1);
|
||||
for (uint32_t m = 0; m < custom.num_modules; ++m) {
|
||||
if (custom.module[m].module_id != 0x1f) {
|
||||
continue;
|
||||
}
|
||||
auto& fx = rack.fx[m];
|
||||
fx = *reinterpret_cast<const Ngs2UserFxOption*>(custom.module[m].option);
|
||||
rack.common[m].resize(fx.common_size);
|
||||
for (uint32_t i = 0; i < rack.option.common.max_voices; ++i) {
|
||||
auto& module = voices[i].modules[m];
|
||||
module.param.resize(fx.param_size);
|
||||
module.work.resize(fx.work_size);
|
||||
module.state.resize(custom.module[m].state_size);
|
||||
if (fx.setup != nullptr) {
|
||||
Ngs2UserFxContext context {
|
||||
rack.common[m].data(), module.param.data(), module.work.data(),
|
||||
fx.user_data, rack.option.common.max_voices, i};
|
||||
EXIT_NOT_IMPLEMENTED(fx.setup(&context) != OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
||||
const Ngs2RackOption* option,
|
||||
const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) {
|
||||
@@ -2313,7 +2468,7 @@ int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
||||
static_cast<uint64_t>(buffer_info->host_buffer_size));
|
||||
|
||||
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
||||
auto* rack = static_cast<Ngs2RackInternal*>(buffer_info->host_buffer);
|
||||
auto* rack = new (buffer_info->host_buffer) Ngs2RackInternal {};
|
||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||
|
||||
Common::LockGuard lock(ngs->mutex);
|
||||
@@ -2373,10 +2528,15 @@ int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id,
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < option->max_voices; i++) {
|
||||
voices[i].rack = rack;
|
||||
voices[i].event = Ngs2VoicePlayEvent::None;
|
||||
voices[i].state = Ngs2VoicePlayState::Empty;
|
||||
auto& voice = *std::construct_at(voices + i);
|
||||
voice.rack = rack;
|
||||
voice.ports.resize(option->max_ports);
|
||||
voice.matrices.resize(option->max_matrices);
|
||||
if (Ngs2RackIsCustom(rack->type)) {
|
||||
voice.modules.resize(rack->option.custom_sampler.custom_rack_option.num_modules);
|
||||
}
|
||||
}
|
||||
Ngs2InitModules(*rack);
|
||||
|
||||
*handle = reinterpret_cast<uintptr_t>(rack);
|
||||
|
||||
@@ -2406,22 +2566,9 @@ int KYTY_SYSV_ABI Ngs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(option->size < sizeof(Ngs2RackOption));
|
||||
|
||||
LOGF("\t rack_id = 0x%" PRIx32 "\n"
|
||||
"\t name = %.64s\n"
|
||||
"\t flags = %u\n"
|
||||
"\t max_grain_samples = %u\n"
|
||||
"\t max_voices = %u\n"
|
||||
"\t max_input_delay_blocks = %u\n"
|
||||
"\t max_matrices = %u\n"
|
||||
"\t max_ports = %u\n"
|
||||
"\t max_voice_channels = %u\n"
|
||||
"\t max_output_channels = %u\n"
|
||||
"\t alloc_handler = 0x%016" PRIx64 "\n"
|
||||
LOGF("\t alloc_handler = 0x%016" PRIx64 "\n"
|
||||
"\t free_handler = 0x%016" PRIx64 "\n"
|
||||
"\t user_data = 0x%016" PRIx64 "\n",
|
||||
rack_id, option->name, option->flags, option->max_grain_samples, option->max_voices,
|
||||
option->max_input_delay_blocks, option->max_matrices, option->max_ports,
|
||||
option->max_voice_channels, option->max_output_channels,
|
||||
reinterpret_cast<uint64_t>(allocator->alloc_handler),
|
||||
reinterpret_cast<uint64_t>(allocator->free_handler),
|
||||
static_cast<uint64_t>(allocator->user_data));
|
||||
@@ -2477,26 +2624,41 @@ int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo*
|
||||
if (ngs == nullptr) {
|
||||
return ERROR_INVALID_RACK_HANDLE;
|
||||
}
|
||||
|
||||
Ngs2ContextBufferInfo context_buffer;
|
||||
Ngs2BufferAllocator allocator;
|
||||
{
|
||||
Common::LockGuard lock(ngs->mutex);
|
||||
Common::LockGuard racks_lock(g_racks_mutex);
|
||||
|
||||
auto** link = &g_racks_list;
|
||||
while (*link != nullptr && *link != rack) {
|
||||
link = &(*link)->next;
|
||||
{
|
||||
Common::LockGuard racks_lock(g_racks_mutex);
|
||||
auto** link = &g_racks_list;
|
||||
while (*link != nullptr && *link != rack) {
|
||||
link = &(*link)->next;
|
||||
}
|
||||
if (*link == nullptr) {
|
||||
return ERROR_INVALID_RACK_HANDLE;
|
||||
}
|
||||
*link = rack->next;
|
||||
}
|
||||
if (*link == nullptr) {
|
||||
return ERROR_INVALID_RACK_HANDLE;
|
||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||
for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) {
|
||||
for (size_t m = 0; m < voices[i].modules.size(); ++m) {
|
||||
const auto& fx = rack->fx[m];
|
||||
if (fx.cleanup != nullptr) {
|
||||
auto& module = voices[i].modules[m];
|
||||
Ngs2UserFxContext context {rack->common[m].data(),
|
||||
module.param.data(),
|
||||
module.work.data(),
|
||||
fx.user_data,
|
||||
rack->option.common.max_voices,
|
||||
i};
|
||||
EXIT_NOT_IMPLEMENTED(fx.cleanup(&context) != OK);
|
||||
}
|
||||
}
|
||||
std::destroy_at(voices + i);
|
||||
}
|
||||
|
||||
*link = rack->next;
|
||||
context_buffer = rack->buffer_info;
|
||||
allocator = rack->allocator;
|
||||
rack->ngs = nullptr;
|
||||
rack->next = nullptr;
|
||||
std::destroy_at(rack);
|
||||
}
|
||||
|
||||
if (allocator.free_handler != nullptr) {
|
||||
@@ -2539,75 +2701,231 @@ int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void Ngs2ApplyEvent(Ngs2VoiceInternal& voice) {
|
||||
switch (voice.event) {
|
||||
case Ngs2VoicePlayEvent::None: break;
|
||||
case Ngs2VoicePlayEvent::Play:
|
||||
if (voice.state == Ngs2VoicePlayState::Empty) {
|
||||
voice.state = Ngs2VoicePlayState::Playing;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Pause:
|
||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||
voice.state = Ngs2VoicePlayState::Paused;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Resume:
|
||||
if (voice.state == Ngs2VoicePlayState::Paused) {
|
||||
voice.state = Ngs2VoicePlayState::Playing;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Stop:
|
||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||
voice.state = Ngs2VoicePlayState::Stopped;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::StopImm:
|
||||
case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break;
|
||||
}
|
||||
voice.event = Ngs2VoicePlayEvent::None;
|
||||
}
|
||||
|
||||
static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) {
|
||||
uint32_t output = 0;
|
||||
while (output < grain && !voice.blocks.empty()) {
|
||||
auto& block = voice.blocks.front();
|
||||
auto count = std::min(grain - output, block.info.num_samples - block.cursor);
|
||||
const int16_t* pcm = nullptr;
|
||||
const float* decoded = nullptr;
|
||||
if (voice.decoder != nullptr) {
|
||||
const auto skip =
|
||||
std::min(block.skip_remaining,
|
||||
static_cast<uint32_t>(voice.decoded_frame.size() / voice.channels));
|
||||
Ajm::AjmGaplessState gapless;
|
||||
gapless.Set({block.info.num_samples - block.cursor, static_cast<uint16_t>(skip), 0},
|
||||
true);
|
||||
const auto result = voice.decoder->Decode(
|
||||
block.data + block.data_cursor, block.info.data_size - block.data_cursor,
|
||||
voice.decoded_frame.data(), voice.decoded_frame.size() * sizeof(float), false,
|
||||
&gapless);
|
||||
EXIT_NOT_IMPLEMENTED(result.result != OK || result.input_consumed == 0);
|
||||
block.data_cursor += result.input_consumed;
|
||||
block.skip_remaining -= skip - gapless.current.skip_samples;
|
||||
const auto decoded_samples =
|
||||
static_cast<uint32_t>(result.output_written / (sizeof(float) * voice.channels));
|
||||
if (decoded_samples == 0) {
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(decoded_samples > count);
|
||||
count = decoded_samples;
|
||||
decoded = voice.decoded_frame.data();
|
||||
} else {
|
||||
pcm = reinterpret_cast<const int16_t*>(block.data) +
|
||||
(block.info.num_skip_samples + block.cursor) * voice.channels;
|
||||
}
|
||||
for (uint32_t c = 0; c < voice.channels; ++c) {
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto index = i * voice.channels + c;
|
||||
voice.samples[c * grain + output + i] =
|
||||
decoded != nullptr ? decoded[index] : pcm[index] / 32768.0f;
|
||||
}
|
||||
}
|
||||
output += count;
|
||||
block.cursor += count;
|
||||
if (block.cursor == block.info.num_samples) {
|
||||
struct CallbackInfo {
|
||||
uintptr_t data, voice;
|
||||
uint32_t flag, reserved;
|
||||
uintptr_t user;
|
||||
const void* block_data;
|
||||
size_t size;
|
||||
uint32_t repeats, attributes;
|
||||
} info {voice.callback_data,
|
||||
reinterpret_cast<uintptr_t>(&voice),
|
||||
1,
|
||||
0,
|
||||
block.info.user_data,
|
||||
block.data,
|
||||
block.info.data_size,
|
||||
0,
|
||||
0};
|
||||
static_assert(sizeof(CallbackInfo) == 56);
|
||||
voice.blocks.pop_front();
|
||||
if (voice.blocks.empty() && !voice.accepts_blocks) {
|
||||
voice.state = Ngs2VoicePlayState::Empty;
|
||||
}
|
||||
if (voice.callback != 0 && (voice.callback_flags & 1u) != 0) {
|
||||
reinterpret_cast<void KYTY_SYSV_ABI (*)(const CallbackInfo*)>(voice.callback)(
|
||||
&info);
|
||||
}
|
||||
}
|
||||
}
|
||||
voice.has_samples = output != 0;
|
||||
}
|
||||
|
||||
static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices,
|
||||
uint32_t grain) {
|
||||
if (voice.rendered) {
|
||||
return;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(voice.rendering);
|
||||
voice.rendering = true;
|
||||
voice.samples.assign(grain * voice.channels, 0.0f);
|
||||
voice.has_samples = false;
|
||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||
if (voice.rack->type == Ngs2RackType::CustomSampler) {
|
||||
Ngs2ConsumeSamples(voice, grain);
|
||||
}
|
||||
for (auto* source: voices) {
|
||||
for (const auto& port: source->ports) {
|
||||
if (port.dest != &voice || port.volume == 0.0f) {
|
||||
continue;
|
||||
}
|
||||
Ngs2RenderVoice(*source, voices, grain);
|
||||
if (!source->has_samples) {
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(port.input != 0);
|
||||
const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix);
|
||||
for (uint32_t dst = 0; dst < voice.channels; ++dst) {
|
||||
for (uint32_t src = 0; src < source->channels; ++src) {
|
||||
const float level =
|
||||
port.volume * (matrix == nullptr
|
||||
? (src == dst ? 1.0f : 0.0f)
|
||||
: matrix->at(dst * source->channels + src));
|
||||
for (uint32_t i = 0; i < grain; ++i) {
|
||||
voice.samples[dst * grain + i] +=
|
||||
source->samples[src * grain + i] * level;
|
||||
}
|
||||
}
|
||||
}
|
||||
voice.has_samples = true;
|
||||
}
|
||||
}
|
||||
if (voice.has_samples) {
|
||||
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
|
||||
for (size_t m = 0; m < voice.modules.size(); ++m) {
|
||||
EXIT_NOT_IMPLEMENTED(custom.module[m].module_id != 0x1f);
|
||||
EXIT_NOT_IMPLEMENTED(custom.module[m].source_buffer_id != 0 ||
|
||||
custom.module[m].dest_buffer_id != 0);
|
||||
auto& module = voice.modules[m];
|
||||
const auto& fx = voice.rack->fx[m];
|
||||
std::vector<float*> channels(voice.channels);
|
||||
for (uint32_t c = 0; c < voice.channels; ++c) {
|
||||
channels[c] = voice.samples.data() + c * grain;
|
||||
}
|
||||
Ngs2UserFxProcessContext context {channels.data(),
|
||||
voice.rack->common[m].data(),
|
||||
module.param.data(),
|
||||
module.work.data(),
|
||||
module.state.data(),
|
||||
fx.user_data,
|
||||
module.flags,
|
||||
voice.channels,
|
||||
voice.channels,
|
||||
grain,
|
||||
voice.rack->ngs->option.sample_rate};
|
||||
EXIT_NOT_IMPLEMENTED(fx.process(&context) != OK);
|
||||
module.flags = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
voice.rendering = false;
|
||||
voice.rendered = true;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
|
||||
uint32_t num_buffer_info) {
|
||||
static std::atomic_uint32_t render_log_count = 0;
|
||||
const auto log_index = render_log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
const bool log_this_call = (log_index < 16 || (log_index % 600) == 0);
|
||||
|
||||
if (log_this_call) {
|
||||
PRINT_NAME();
|
||||
LOGF("\t call_count = %" PRIu32 "\n", log_index + 1);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(buffer_info == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(system_handle == 0);
|
||||
EXIT_NOT_IMPLEMENTED(num_buffer_info == 0);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(buffer_info == nullptr || system_handle == 0 || num_buffer_info == 0);
|
||||
auto* ngs = reinterpret_cast<Ngs2Internal*>(system_handle);
|
||||
|
||||
Common::LockGuard lock(ngs->mutex);
|
||||
|
||||
for (uint32_t i = 0; i < num_buffer_info; i++) {
|
||||
std::vector<Ngs2VoiceInternal*> voices;
|
||||
{
|
||||
Common::LockGuard racks_lock(g_racks_mutex);
|
||||
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
|
||||
if (rack->ngs != ngs) {
|
||||
continue;
|
||||
}
|
||||
auto* items = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||
for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) {
|
||||
Ngs2ApplyEvent(items[i]);
|
||||
items[i].rendered = false;
|
||||
if (items[i].channels != 0) {
|
||||
voices.push_back(items + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < num_buffer_info; ++i) {
|
||||
if (buffer_info[i].buffer != nullptr && buffer_info[i].buffer_size != 0) {
|
||||
std::memset(buffer_info[i].buffer, 0, buffer_info[i].buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
Common::LockGuard racks_lock(g_racks_mutex);
|
||||
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
|
||||
if (rack->ngs == ngs) {
|
||||
auto* voices = reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||
|
||||
for (uint32_t i = 0; i < rack->option.common.max_voices; i++) {
|
||||
auto& voice = voices[i];
|
||||
switch (voice.event) {
|
||||
case Ngs2VoicePlayEvent::None:
|
||||
if (voice.state == Ngs2VoicePlayState::Playing ||
|
||||
voice.state == Ngs2VoicePlayState::Stopped) {
|
||||
voice.state = Ngs2VoicePlayState::Empty;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Play:
|
||||
if (voice.state == Ngs2VoicePlayState::Empty) {
|
||||
voice.state = Ngs2VoicePlayState::Playing;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Pause:
|
||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||
voice.state = Ngs2VoicePlayState::Paused;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Resume:
|
||||
if (voice.state == Ngs2VoicePlayState::Paused) {
|
||||
voice.state = Ngs2VoicePlayState::Playing;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::Stop:
|
||||
if (voice.state == Ngs2VoicePlayState::Playing) {
|
||||
voice.state = Ngs2VoicePlayState::Stopped;
|
||||
}
|
||||
break;
|
||||
case Ngs2VoicePlayEvent::StopImm:
|
||||
case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break;
|
||||
const auto grain = ngs->option.num_grain_samples;
|
||||
for (auto* voice: voices) {
|
||||
Ngs2RenderVoice(*voice, voices, grain);
|
||||
if (voice->rack->type != Ngs2RackType::Mastering || !voice->has_samples) {
|
||||
continue;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info);
|
||||
const auto& output = buffer_info[voice->output_id];
|
||||
EXIT_NOT_IMPLEMENTED(output.buffer == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x18 ||
|
||||
output.num_channels != voice->channels);
|
||||
EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * sizeof(float));
|
||||
auto* pcm = static_cast<float*>(output.buffer);
|
||||
for (uint32_t dst = 0; dst < output.num_channels; ++dst) {
|
||||
for (uint32_t src = 0; src < voice->channels; ++src) {
|
||||
const float level = voice->output_matrix.empty()
|
||||
? (src == dst ? 1.0f : 0.0f)
|
||||
: voice->output_matrix.at(src * output.num_channels + dst);
|
||||
for (uint32_t i = 0; i < grain; ++i) {
|
||||
pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level;
|
||||
}
|
||||
voice.event = Ngs2VoicePlayEvent::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngs->render_count++;
|
||||
|
||||
++ngs->render_count;
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -2944,6 +3262,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
case 0x0001: {
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceMatrixLevelsParam));
|
||||
const auto* ml = reinterpret_cast<const Ngs2VoiceMatrixLevelsParam*>(param);
|
||||
voice->SetMatrix(ml->matrix_id, ml->levels, ml->num_levels);
|
||||
LOGF("\t matrix_id = %u\n"
|
||||
"\t num_levels = %u\n"
|
||||
"\t levels = 0x%016" PRIx64 "\n",
|
||||
@@ -2954,6 +3273,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortVolumeParam));
|
||||
const auto* volume =
|
||||
reinterpret_cast<const Ngs2VoicePortVolumeParam*>(param);
|
||||
voice->SetVolume(volume->port, volume->level);
|
||||
LOGF("\t port = %u\n"
|
||||
"\t level = %f\n",
|
||||
volume->port, volume->level);
|
||||
@@ -2962,6 +3282,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
case 0x0003: {
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortMatrixParam));
|
||||
const auto* pm = reinterpret_cast<const Ngs2VoicePortMatrixParam*>(param);
|
||||
voice->SetPortMatrix(pm->port, pm->matrix_id);
|
||||
LOGF("\t port = %u\n"
|
||||
"\t matrix_id = %d\n",
|
||||
pm->port, pm->matrix_id);
|
||||
@@ -2978,6 +3299,10 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
case 0x0005: {
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePatchParam));
|
||||
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
|
||||
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
|
||||
voice->ports[patch->port].dest =
|
||||
reinterpret_cast<Ngs2VoiceInternal*>(patch->dest_handle);
|
||||
voice->ports[patch->port].input = patch->dest_input_id;
|
||||
LOGF("\t connect->port = %u\n"
|
||||
"\t connect->dest_input_id = %u\n"
|
||||
"\t connect->dest_handle = 0x%016" PRIx64 "\n",
|
||||
@@ -2987,15 +3312,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
case 0x0006: {
|
||||
EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceEventParam));
|
||||
const auto* event = reinterpret_cast<const Ngs2VoiceEventParam*>(param);
|
||||
switch (event->event_id) {
|
||||
case 0x0001: voice->event = Ngs2VoicePlayEvent::Play; break;
|
||||
case 0x0002: voice->event = Ngs2VoicePlayEvent::Stop; break;
|
||||
case 0x0004: voice->event = Ngs2VoicePlayEvent::StopImm; break;
|
||||
case 0x0008: voice->event = Ngs2VoicePlayEvent::Kill; break;
|
||||
case 0x0010: voice->event = Ngs2VoicePlayEvent::Pause; break;
|
||||
case 0x0020: voice->event = Ngs2VoicePlayEvent::Resume; break;
|
||||
default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", event->event_id);
|
||||
}
|
||||
voice->SetEvent(event->event_id);
|
||||
LOGF("\t event = %u\n", event->event_id);
|
||||
break;
|
||||
}
|
||||
@@ -3020,24 +3337,89 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
case 0x1000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler); break;
|
||||
case 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break;
|
||||
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break;
|
||||
case 0x3000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); break;
|
||||
case 0x4000: {
|
||||
EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type));
|
||||
auto cid = param->id & 0xffffu;
|
||||
auto module_id = (cid >> 8u) & 0xffu;
|
||||
auto ctl_id = (cid >> 5u) & 0x7u;
|
||||
auto module_no = cid & 0x1fu;
|
||||
LOGF("\t custom module_id = 0x%02" PRIx32 ", ctl_id = 0x%" PRIx32
|
||||
", module_no = %" PRIu32 "\n",
|
||||
module_id, ctl_id, module_no);
|
||||
case 0x3000: {
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
|
||||
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
|
||||
switch (param->id & 0xffffu) {
|
||||
case 0: voice->channels = values[0]; break;
|
||||
case 1: {
|
||||
const auto* matrix =
|
||||
reinterpret_cast<const Ngs2VoiceMatrixLevelsParam*>(param);
|
||||
EXIT_NOT_IMPLEMENTED(matrix->matrix_id != 5);
|
||||
voice->output_matrix.assign(matrix->levels,
|
||||
matrix->levels + matrix->num_levels);
|
||||
break;
|
||||
}
|
||||
case 5: voice->output_id = values[0]; break;
|
||||
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x4001:
|
||||
case 0x4000: {
|
||||
EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type));
|
||||
const auto index = param->id & 0x1fu;
|
||||
EXIT_NOT_IMPLEMENTED((param->id & 0xffffffe0u) != 0x40001f00u ||
|
||||
index >= voice->modules.size());
|
||||
struct FxParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
const void* data;
|
||||
size_t size;
|
||||
};
|
||||
const auto& fx = *reinterpret_cast<const FxParam*>(param);
|
||||
auto& module = voice->modules[index];
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->fx[index].control != 0 ||
|
||||
fx.size != module.param.size());
|
||||
std::memcpy(module.param.data(), fx.data, fx.size);
|
||||
module.flags |= 2;
|
||||
break;
|
||||
}
|
||||
case 0x4001: {
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSampler);
|
||||
switch (param->id & 0xffffu) {
|
||||
case 0: {
|
||||
const auto& format =
|
||||
*reinterpret_cast<const Ngs2WaveformFormat*>(param + 1);
|
||||
voice->SetupSampler(format);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
struct BlocksParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
const uint8_t* data;
|
||||
uint32_t flags, count;
|
||||
const Ngs2WaveformBlock* blocks;
|
||||
};
|
||||
const auto& blocks = *reinterpret_cast<const BlocksParam*>(param);
|
||||
EXIT_NOT_IMPLEMENTED(!voice->accepts_blocks ||
|
||||
(blocks.flags != 0 && blocks.flags != 0x11));
|
||||
voice->accepts_blocks = (blocks.flags & 1u) != 0;
|
||||
for (uint32_t i = 0; i < blocks.count; ++i) {
|
||||
const auto& block = blocks.blocks[i];
|
||||
EXIT_NOT_IMPLEMENTED(block.num_repeats != 0 || block.num_samples == 0);
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
voice->decoder == nullptr &&
|
||||
(uint64_t(block.num_skip_samples) + block.num_samples) *
|
||||
voice->channels * 2 >
|
||||
block.data_size);
|
||||
voice->blocks.push_back({blocks.data + block.data_offset, block});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
EXIT_NOT_IMPLEMENTED(*reinterpret_cast<const float*>(param + 1) != 1.0f);
|
||||
break;
|
||||
default: EXIT("unsupported sampler control: 0x%08" PRIx32 "\n", param->id);
|
||||
}
|
||||
break;
|
||||
case 0x4002:
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSubmixer);
|
||||
}
|
||||
case 0x4002: {
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSubmixer ||
|
||||
(param->id & 0xffffu) != 0);
|
||||
const auto* channels = reinterpret_cast<const uint32_t*>(param + 1);
|
||||
EXIT_NOT_IMPLEMENTED(channels[0] != channels[1]);
|
||||
voice->channels = channels[0];
|
||||
break;
|
||||
}
|
||||
case 0x4003:
|
||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomMastering);
|
||||
break;
|
||||
@@ -3055,14 +3437,44 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
||||
uint32_t num_commands, uint32_t flags) {
|
||||
PRINT_NAME();
|
||||
|
||||
(void)voice_handle;
|
||||
(void)commands;
|
||||
(void)num_commands;
|
||||
(void)flags;
|
||||
|
||||
size_t num_commands) {
|
||||
struct Command {
|
||||
uint32_t id;
|
||||
uint8_t flags, type;
|
||||
uint16_t count;
|
||||
union {
|
||||
uint32_t u;
|
||||
int32_t i;
|
||||
float f;
|
||||
const float* levels;
|
||||
} value;
|
||||
};
|
||||
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
|
||||
Common::LockGuard lock(voice->rack->ngs->mutex);
|
||||
const auto* params = static_cast<const Command*>(commands);
|
||||
for (size_t i = 0; i < num_commands; ++i) {
|
||||
const auto& p = params[i];
|
||||
const auto index = p.id >> 24;
|
||||
switch (p.id & 0xffffffu) {
|
||||
case 2:
|
||||
EXIT_NOT_IMPLEMENTED(p.type != 4);
|
||||
voice->SetEvent(p.value.u);
|
||||
break;
|
||||
case 5:
|
||||
EXIT_NOT_IMPLEMENTED(p.type != 0x11);
|
||||
voice->SetMatrix(index, p.value.levels, p.count);
|
||||
break;
|
||||
case 6:
|
||||
EXIT_NOT_IMPLEMENTED(p.type != 1);
|
||||
voice->SetVolume(index, p.value.f);
|
||||
break;
|
||||
case 7:
|
||||
EXIT_NOT_IMPLEMENTED(p.type != 3);
|
||||
voice->SetPortMatrix(index, p.value.i);
|
||||
break;
|
||||
default: EXIT("unknown command: 0x%08" PRIx32 "\n", p.id);
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -336,7 +336,7 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
|
||||
uintptr_t* handle);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands,
|
||||
uint32_t num_commands, uint32_t flags);
|
||||
size_t num_commands);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
||||
size_t state_size);
|
||||
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
||||
|
||||
+89
-79
@@ -117,6 +117,13 @@ struct AvPlayerEventReplacement {
|
||||
void* object_pointer = nullptr;
|
||||
AvPlayerEventCallback event_callback = nullptr;
|
||||
};
|
||||
static void emit_event(AvPlayerEventReplacement event, int32_t id, void* data = nullptr) {
|
||||
if (event.event_callback != nullptr) {
|
||||
LOGF("\t event_id = %d\n", id);
|
||||
event.event_callback(event.object_pointer, id, 0, data);
|
||||
}
|
||||
}
|
||||
|
||||
struct AvPlayerUri {
|
||||
const char* name = nullptr;
|
||||
uint32_t length = 0;
|
||||
@@ -639,9 +646,9 @@ private:
|
||||
|
||||
class Source {
|
||||
public:
|
||||
Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, int32_t video_buffers, bool vdec2,
|
||||
uint32_t demux_buffer_size)
|
||||
: mem(m), file(f),
|
||||
Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, AvPlayerEventReplacement e,
|
||||
int32_t video_buffers, bool vdec2, uint32_t demux_buffer_size)
|
||||
: mem(m), file(f), event(e),
|
||||
max_video_buffers(std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16)),
|
||||
use_vdec2(vdec2),
|
||||
video_packets(
|
||||
@@ -699,7 +706,7 @@ public:
|
||||
int Enable(uint32_t id) {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
std::lock_guard state_lock(mutex);
|
||||
if (!stopped) {
|
||||
if (state == State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (fmt == nullptr || id >= fmt->nb_streams) {
|
||||
@@ -717,7 +724,7 @@ public:
|
||||
int Disable(uint32_t id) {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
std::lock_guard state_lock(mutex);
|
||||
if (!stopped) {
|
||||
if (state == State::Playing) {
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (video_id == static_cast<int>(id)) {
|
||||
@@ -748,9 +755,11 @@ public:
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
time = CurrentTimeNoLock();
|
||||
restart = !stopped;
|
||||
restart = state == State::Playing;
|
||||
was_paused = paused;
|
||||
stopped = true;
|
||||
if (restart) {
|
||||
state = State::Ready;
|
||||
}
|
||||
}
|
||||
if (restart) {
|
||||
StopWorkers();
|
||||
@@ -771,7 +780,7 @@ public:
|
||||
int StartImpl(uint64_t ms, bool paused_after_start = false, bool show_seek_frame = false) {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
stopped = true;
|
||||
state = State::Ready;
|
||||
}
|
||||
StopWorkers();
|
||||
interrupt_io = false;
|
||||
@@ -784,14 +793,17 @@ public:
|
||||
AutoEnable();
|
||||
}
|
||||
if (!video_id && !audio_id) {
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!OpenCodecs()) {
|
||||
ResetNoLock(true);
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
if (!AllocateBuffers()) {
|
||||
ResetNoLock(true);
|
||||
state = State::Stopped;
|
||||
return AVPLAYER_ERROR_NO_MEMORY;
|
||||
}
|
||||
SeekNoLock(ms);
|
||||
@@ -802,12 +814,11 @@ public:
|
||||
pause_time = clock_start;
|
||||
seek_video_frame_pending =
|
||||
paused_after_start && show_seek_frame && video_id.has_value();
|
||||
stopped = false;
|
||||
}
|
||||
|
||||
if (!StartWorkers()) {
|
||||
std::lock_guard lock(mutex);
|
||||
stopped = true;
|
||||
state = State::Stopped;
|
||||
ResetNoLock(true);
|
||||
result = AVPLAYER_ERROR_OPERATION_FAILED;
|
||||
}
|
||||
@@ -817,21 +828,28 @@ public:
|
||||
return result;
|
||||
}
|
||||
int Stop() {
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
bool was_playing_video = false;
|
||||
bool was_playing_video = false;
|
||||
AvPlayerEventReplacement stop_event {};
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
was_playing_video = !stopped && video_id.has_value();
|
||||
stopped = true;
|
||||
}
|
||||
StopWorkers();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ResetNoLock();
|
||||
std::scoped_lock lifecycle_lock(lifecycle_mutex);
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (state == State::Playing) {
|
||||
was_playing_video = video_id.has_value();
|
||||
stop_event = event;
|
||||
}
|
||||
state = State::Stopped;
|
||||
}
|
||||
StopWorkers();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
ResetNoLock();
|
||||
}
|
||||
}
|
||||
if (was_playing_video) {
|
||||
::printf("AvPlayer video stopped\n");
|
||||
}
|
||||
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
|
||||
return 0;
|
||||
}
|
||||
void Pause() {
|
||||
@@ -860,9 +878,8 @@ public:
|
||||
}
|
||||
bool Active() const {
|
||||
std::lock_guard lock(mutex);
|
||||
return !stopped && !pipeline_failed &&
|
||||
(!demux_eof || !video_done || !audio_done || !video_frames.Empty() ||
|
||||
!audio_frames.Empty());
|
||||
return state == State::Ready ||
|
||||
(state == State::Playing && !pipeline_failed && !DrainedNoLock());
|
||||
}
|
||||
uint64_t CurrentTime() const {
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -878,7 +895,7 @@ public:
|
||||
return StartImpl(ms, was_paused, true);
|
||||
}
|
||||
uint64_t CurrentTimeNoLock() const {
|
||||
if (stopped) {
|
||||
if (state != State::Playing) {
|
||||
return 0;
|
||||
}
|
||||
using namespace std::chrono;
|
||||
@@ -915,7 +932,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
if (stopped || !video_id) {
|
||||
if (state != State::Playing || !video_id) {
|
||||
return false;
|
||||
}
|
||||
const bool deliver_seek_frame = paused && seek_video_frame_pending;
|
||||
@@ -955,7 +972,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
if (paused || stopped || !audio_id || trick_speed != AVPLAYER_TRICK_SPEED_NORMAL) {
|
||||
if (paused || state != State::Playing || !audio_id ||
|
||||
trick_speed != AVPLAYER_TRICK_SPEED_NORMAL) {
|
||||
return false;
|
||||
}
|
||||
auto frame = audio_frames.TryPop();
|
||||
@@ -988,6 +1006,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
enum class State { Ready, Playing, Stopped };
|
||||
|
||||
bool DrainedNoLock() const {
|
||||
return demux_eof && video_done && audio_done && video_frames.Empty() &&
|
||||
audio_frames.Empty();
|
||||
}
|
||||
void RecordLoopBoundary(const ReadyFrame& frame) {
|
||||
if (frame.timestamp_offset > last_output_loop_offset) {
|
||||
last_output_loop_offset = frame.timestamp_offset;
|
||||
@@ -1002,23 +1026,17 @@ private:
|
||||
streamer.reset();
|
||||
}
|
||||
void ResetNoLock(bool retain_output_buffers = false) {
|
||||
std::optional<ReadyFrame> delivered_video;
|
||||
std::optional<ReadyFrame> delivered_audio;
|
||||
std::deque<std::unique_ptr<GuestBuffer>> in_flight_video;
|
||||
if (retain_output_buffers) {
|
||||
delivered_video = std::move(current_video);
|
||||
delivered_audio = std::move(current_audio);
|
||||
in_flight_video = std::move(retired_video);
|
||||
}
|
||||
video_packets.Clear();
|
||||
audio_packets.Clear();
|
||||
video_frames.Clear();
|
||||
audio_frames.Clear();
|
||||
video_buffers.Clear();
|
||||
audio_buffers.Clear();
|
||||
retired_video.clear();
|
||||
current_video.reset();
|
||||
current_audio.reset();
|
||||
if (!retain_output_buffers) {
|
||||
retired_video.clear();
|
||||
current_video.reset();
|
||||
current_audio.reset();
|
||||
}
|
||||
if (sws != nullptr) {
|
||||
sws_freeContext(sws);
|
||||
sws = nullptr;
|
||||
@@ -1042,9 +1060,6 @@ private:
|
||||
last_audio_ts = 0;
|
||||
last_output_loop_offset = 0;
|
||||
pending_loop_warnings = 0;
|
||||
current_video = std::move(delivered_video);
|
||||
current_audio = std::move(delivered_audio);
|
||||
retired_video = std::move(in_flight_video);
|
||||
}
|
||||
void AutoEnable() {
|
||||
for (uint32_t i = 0; i < fmt->nb_streams; i++) {
|
||||
@@ -1174,6 +1189,7 @@ private:
|
||||
return true;
|
||||
}
|
||||
bool StartWorkers() {
|
||||
std::unique_lock lock(mutex);
|
||||
worker_stop = false;
|
||||
interrupt_io = false;
|
||||
pipeline_failed = false;
|
||||
@@ -1183,9 +1199,11 @@ private:
|
||||
if ((video_id && !CreateWorker(video_thread, VideoDecoderEntry, "AvPlayerVideoDecoder")) ||
|
||||
(audio_id && !CreateWorker(audio_thread, AudioDecoderEntry, "AvPlayerAudioDecoder")) ||
|
||||
!CreateWorker(demux_thread, DemuxEntry, "AvPlayerDemuxer")) {
|
||||
lock.unlock();
|
||||
StopWorkers();
|
||||
return false;
|
||||
}
|
||||
state = State::Playing;
|
||||
return true;
|
||||
}
|
||||
void NotifyWorkers() {
|
||||
@@ -1195,8 +1213,11 @@ private:
|
||||
audio_buffers.Notify();
|
||||
}
|
||||
void StopWorkers() {
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
}
|
||||
NotifyWorkers();
|
||||
if (demux_thread != nullptr) {
|
||||
LibKernel::PthreadJoin(demux_thread, nullptr);
|
||||
@@ -1212,9 +1233,12 @@ private:
|
||||
}
|
||||
}
|
||||
void FailPipeline() {
|
||||
pipeline_failed = true;
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
pipeline_failed = true;
|
||||
worker_stop = true;
|
||||
interrupt_io = true;
|
||||
}
|
||||
NotifyWorkers();
|
||||
}
|
||||
void Demux() {
|
||||
@@ -1381,15 +1405,8 @@ private:
|
||||
return use_vdec2 ? static_cast<uint32_t>(s->codecpar->height)
|
||||
: align_up(static_cast<uint32_t>(s->codecpar->height), 16);
|
||||
}
|
||||
float Aspect(AVStream* s) const {
|
||||
if (s->codecpar->height == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
double r = static_cast<double>(s->codecpar->width) / s->codecpar->height;
|
||||
if (s->sample_aspect_ratio.num > 0 && s->sample_aspect_ratio.den > 0) {
|
||||
r *= av_q2d(s->sample_aspect_ratio);
|
||||
}
|
||||
return static_cast<float>(r);
|
||||
float Aspect(AVRational ratio) const {
|
||||
return ratio.num > 0 && ratio.den > 0 ? static_cast<float>(av_q2d(ratio)) : 0.0f;
|
||||
}
|
||||
uint64_t Duration(AVStream* s) const {
|
||||
return s->duration != AV_NOPTS_VALUE
|
||||
@@ -1402,14 +1419,14 @@ private:
|
||||
std::memset(v, 0, sizeof(*v));
|
||||
v->width = Width(s);
|
||||
v->height = Height(s);
|
||||
v->aspect_ratio = Aspect(s);
|
||||
v->aspect_ratio = Aspect(s->sample_aspect_ratio);
|
||||
lang(v->language_code, s->metadata);
|
||||
}
|
||||
void FillVideoEx(AVStream* s, AvPlayerVideoEx* v) const {
|
||||
void FillVideoEx(AVStream* s, AvPlayerVideoEx* v, AVRational aspect) const {
|
||||
std::memset(v, 0, sizeof(*v));
|
||||
v->width = Width(s);
|
||||
v->height = Height(s);
|
||||
v->aspect_ratio = Aspect(s);
|
||||
v->aspect_ratio = Aspect(aspect);
|
||||
v->pitch = VideoPitch(s);
|
||||
v->luma_bit_depth = 8;
|
||||
v->chroma_bit_depth = 8;
|
||||
@@ -1445,7 +1462,7 @@ private:
|
||||
FillVideo(s, &d->video);
|
||||
}
|
||||
if (ex) {
|
||||
FillVideoEx(s, &ex->video);
|
||||
FillVideoEx(s, &ex->video, s->sample_aspect_ratio);
|
||||
}
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
@@ -1530,7 +1547,7 @@ private:
|
||||
info->time_stamp = to_ms(
|
||||
src->best_effort_timestamp != AV_NOPTS_VALUE ? src->best_effort_timestamp : src->pts,
|
||||
s->time_base);
|
||||
FillVideoEx(s, &info->details.video);
|
||||
FillVideoEx(s, &info->details.video, src->sample_aspect_ratio);
|
||||
info->details.video.crop_left_offset = static_cast<uint32_t>(src->crop_left);
|
||||
info->details.video.crop_right_offset =
|
||||
static_cast<uint32_t>(src->crop_right + (pitch - src->width));
|
||||
@@ -1607,6 +1624,7 @@ private:
|
||||
}
|
||||
AvPlayerMemAllocator mem;
|
||||
AvPlayerFileReplacement file;
|
||||
AvPlayerEventReplacement event;
|
||||
int max_video_buffers = 2;
|
||||
bool use_vdec2 = false;
|
||||
AvPlayerSourceType source_type = AvPlayerSourceUnknown;
|
||||
@@ -1642,7 +1660,7 @@ private:
|
||||
std::atomic_bool audio_done {true};
|
||||
std::mutex lifecycle_mutex;
|
||||
mutable std::mutex mutex;
|
||||
bool stopped = true;
|
||||
State state = State::Ready;
|
||||
bool paused = false;
|
||||
bool seek_video_frame_pending = false;
|
||||
std::atomic_bool loop {false};
|
||||
@@ -1673,19 +1691,13 @@ struct AvPlayerInternal {
|
||||
static bool valid_allocators(const AvPlayerMemAllocator& m) {
|
||||
return m.allocate && m.deallocate && m.allocate_texture && m.deallocate_texture;
|
||||
}
|
||||
static void emit_event(AvPlayerInternal* h, int32_t id, void* data = nullptr) {
|
||||
if (h != nullptr && h->event.event_callback != nullptr) {
|
||||
LOGF("\t event_id = %d\n", id);
|
||||
h->event.event_callback(h->event.object_pointer, id, 0, data);
|
||||
}
|
||||
}
|
||||
static void pump_warnings(AvPlayerInternal* h) {
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
return;
|
||||
}
|
||||
while (auto w = h->source->TakeWarning()) {
|
||||
int32_t warning = *w;
|
||||
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &warning);
|
||||
emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &warning);
|
||||
}
|
||||
}
|
||||
static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem,
|
||||
@@ -1713,17 +1725,17 @@ static int add_source(AvPlayerInternal* h, const std::string& filename, AvPlayer
|
||||
}
|
||||
bool vdec2 =
|
||||
h->post_init.video_decoder_init.decoder_type.video_type == AvPlayerVideoDecoderSoftware2;
|
||||
auto s = std::make_unique<Source>(h->mem, h->file, h->video_buffers, vdec2,
|
||||
auto s = std::make_unique<Source>(h->mem, h->file, h->event, h->video_buffers, vdec2,
|
||||
h->post_init.demux_video_buffer_size);
|
||||
if (auto rc = s->Init(filename, type); rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
h->source = std::move(s);
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_READY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_READY);
|
||||
if (h->auto_start) {
|
||||
auto rc = h->source->Start();
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1872,7 +1884,7 @@ int KYTY_SYSV_ABI AvPlayerStart(AvPlayerInternal* h) {
|
||||
}
|
||||
auto rc = h->source->Start();
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1887,7 +1899,7 @@ int KYTY_SYSV_ABI AvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex
|
||||
: static_cast<const AvPlayerStartInfoEx*>(start_info_ex)->start_time_milliseconds;
|
||||
auto rc = h->source->Start(ms);
|
||||
if (rc == 0) {
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -1896,9 +1908,7 @@ int KYTY_SYSV_ABI AvPlayerStop(AvPlayerInternal* h) {
|
||||
if (h == nullptr || h->source == nullptr) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
auto rc = h->source->Stop();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_STOP);
|
||||
return rc;
|
||||
return h->source->Stop();
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
@@ -1906,7 +1916,7 @@ int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
h->source->Pause();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PAUSE);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
|
||||
return 0;
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
|
||||
@@ -1915,7 +1925,7 @@ int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
|
||||
return AVPLAYER_ERROR_INVALID_PARAMS;
|
||||
}
|
||||
h->source->Resume();
|
||||
emit_event(h, AVPLAYER_EVENT_STATE_PLAY);
|
||||
emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
|
||||
return 0;
|
||||
}
|
||||
int KYTY_SYSV_ABI AvPlayerSetLooping(AvPlayerInternal* h, Bool loop) {
|
||||
@@ -2003,7 +2013,7 @@ Bool KYTY_SYSV_ABI AvPlayerGetAudioData(AvPlayerInternal* h, AvPlayerFrameInfo*
|
||||
}
|
||||
Bool KYTY_SYSV_ABI AvPlayerIsActive(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
return h != nullptr && h->source != nullptr && h->source->Active() ? 1 : 0;
|
||||
return h != nullptr && (h->source == nullptr || h->source->Active());
|
||||
}
|
||||
uint64_t KYTY_SYSV_ABI AvPlayerCurrentTime(AvPlayerInternal* h) {
|
||||
PRINT_NAME();
|
||||
@@ -2017,7 +2027,7 @@ int KYTY_SYSV_ABI AvPlayerJumpToTime(AvPlayerInternal* h, uint64_t time_ms) {
|
||||
auto rc = h->source->Jump(time_ms);
|
||||
if (rc == 0) {
|
||||
int32_t w = AVPLAYER_WARNING_JUMP_COMPLETE;
|
||||
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &w);
|
||||
emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &w);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -812,6 +812,20 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle != 1) {
|
||||
return PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (is_remote == nullptr) {
|
||||
return PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*is_remote = false;
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable);
|
||||
int KYTY_SYSV_ABI PadResetOrientation(int handle);
|
||||
int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info);
|
||||
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote);
|
||||
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data);
|
||||
int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num);
|
||||
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param);
|
||||
|
||||
@@ -1052,7 +1052,7 @@ static int32_t KYTY_SYSV_ABI AudiodecDecode(int32_t handle, AudiodecCtrl* ctrl)
|
||||
ctrl->pPcmItem->uiPcmSize = static_cast<uint32_t>(result.output_written);
|
||||
audiodec_fill_info(ctrl, decoder);
|
||||
auto* info = static_cast<AudiodecM4aacInfo*>(ctrl->pBsiInfo);
|
||||
info->iResult = (result.result & AJM_RESULT_CODEC_ERROR) != 0
|
||||
info->iResult = (result.result & (AJM_RESULT_CODEC_ERROR | AJM_RESULT_TOO_MANY_CHANNELS)) != 0
|
||||
? AUDIODEC_M4AAC_RESULT_DECODE_ERROR
|
||||
: (result.result & AJM_RESULT_PARTIAL_INPUT) != 0
|
||||
? AUDIODEC_M4AAC_RESULT_INSUFFICIENT_DATA : 0;
|
||||
|
||||
+62
-30
@@ -8,22 +8,13 @@
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define NET_CALL(func) \
|
||||
[&]() { \
|
||||
auto result = func; \
|
||||
if (result < 0) { \
|
||||
*GetNetErrorAddr() = result; \
|
||||
} \
|
||||
return result; \
|
||||
}()
|
||||
|
||||
namespace Libs {
|
||||
|
||||
namespace Network::Net {
|
||||
@@ -43,8 +34,15 @@ KYTY_SYSV_ABI int* GetNetErrorAddr() {
|
||||
return &g_net_errno;
|
||||
}
|
||||
|
||||
static int FinishNetCall(int result) {
|
||||
if (result < 0) {
|
||||
g_net_errno = result - Network::NET_ERROR_EPERM + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI NetInit() {
|
||||
return NET_CALL(Net::NetInit());
|
||||
return FinishNetCall(Net::NetInit());
|
||||
}
|
||||
|
||||
static int PosixToNetError(int error) {
|
||||
@@ -66,13 +64,7 @@ static int PosixToNetError(int error) {
|
||||
}
|
||||
|
||||
static int FinishSocketCall(int result) {
|
||||
if (result >= 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const int net_error = PosixToNetError(*Posix::GetErrorAddr());
|
||||
*GetNetErrorAddr() = net_error;
|
||||
return net_error;
|
||||
return FinishNetCall(result < 0 ? PosixToNetError(*Posix::GetErrorAddr()) : result);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSocket(const char* name, int family, int type, int protocol) {
|
||||
@@ -102,48 +94,48 @@ int KYTY_SYSV_ABI NetGetsockname(int s, void* addr, uint32_t* addrlen) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetPoolCreate(const char* name, int size, int flags) {
|
||||
return NET_CALL(Net::NetPoolCreate(name, size, flags));
|
||||
return FinishNetCall(Net::NetPoolCreate(name, size, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetPoolDestroy(int memid) {
|
||||
return NET_CALL(Net::NetPoolDestroy(memid));
|
||||
return FinishNetCall(Net::NetPoolDestroy(memid));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetResolverCreate(const char* name, int memid, int flags) {
|
||||
return NET_CALL(Net::NetResolverCreate(name, memid, flags));
|
||||
return FinishNetCall(Net::NetResolverCreate(name, memid, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetResolverDestroy(int rid) {
|
||||
return NET_CALL(Net::NetResolverDestroy(rid));
|
||||
return FinishNetCall(Net::NetResolverDestroy(rid));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout,
|
||||
int retry, int flags) {
|
||||
return NET_CALL(Net::NetResolverStartNtoa(rid, hostname, addr, timeout, retry, flags));
|
||||
return FinishNetCall(Net::NetResolverStartNtoa(rid, hostname, addr, timeout, retry, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetInetPton(int af, const char* src, void* dst) {
|
||||
return NET_CALL(Net::NetInetPton(af, src, dst));
|
||||
return FinishNetCall(Net::NetInetPton(af, src, dst));
|
||||
}
|
||||
|
||||
const char* KYTY_SYSV_ABI NetInetNtop(int af, const void* src, char* dst, uint32_t size) {
|
||||
const char* result = Net::NetInetNtop(af, src, dst, size);
|
||||
if (result == nullptr) {
|
||||
*GetNetErrorAddr() = PosixToNetError(*Posix::GetErrorAddr());
|
||||
FinishNetCall(PosixToNetError(*Posix::GetErrorAddr()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetEtherNtostr(const Net::NetEtherAddr* n, char* str, size_t len) {
|
||||
return NET_CALL(Net::NetEtherNtostr(n, str, len));
|
||||
return FinishNetCall(Net::NetEtherNtostr(n, str, len));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetGetMacAddress(Net::NetEtherAddr* addr, int flags) {
|
||||
return NET_CALL(Net::NetGetMacAddress(addr, flags));
|
||||
return FinishNetCall(Net::NetGetMacAddress(addr, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetGetSockInfo(int s, void* info, int n, int flags) {
|
||||
return NET_CALL(Net::NetGetSockInfo(s, info, n, flags));
|
||||
return FinishNetCall(Net::NetGetSockInfo(s, info, n, flags));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetEpollCreate(const char* name, int flags) {
|
||||
@@ -163,7 +155,7 @@ int KYTY_SYSV_ABI NetEpollDestroy(int eid) {
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSocketClose(int s) {
|
||||
return NET_CALL(Net::SocketClose(s));
|
||||
return FinishNetCall(Net::SocketClose(s));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSetsockopt(int s, int level, int optname, const void* optval,
|
||||
@@ -1465,7 +1457,7 @@ LIB_DEFINE(InitNet_1_NpManager) {
|
||||
LIB_FUNC("Ec63y59l9tw", NpManager::NpSetNpTitleId);
|
||||
LIB_FUNC("A2CQ3kgSopQ", NpManager::NpSetContentRestriction);
|
||||
LIB_FUNC("VfRSmPmj8Q8", NpManager::NpRegisterStateCallback);
|
||||
LIB_FUNC("qQJfO8HAiaY", NpManager::NpRegisterStateCallback);
|
||||
LIB_FUNC("qQJfO8HAiaY", NpManager::NpRegisterStateCallbackA);
|
||||
LIB_FUNC("M3wFXbYQtAA", NpManager::NpUnregisterStateCallback);
|
||||
LIB_FUNC("uFJpaKNBAj4", NpManager::NpRegisterGamePresenceCallback);
|
||||
LIB_FUNC("GImICnh+boA", NpManager::NpRegisterPlusEventCallback);
|
||||
@@ -3730,6 +3722,21 @@ namespace LibGameLiveStreaming {
|
||||
|
||||
LIB_VERSION("GameLiveStreaming", 1, "GameLiveStreaming", 1, 1);
|
||||
|
||||
constexpr int GAME_LIVE_STREAMING_ERROR_INVALID_PARAM = -2136997886;
|
||||
|
||||
struct GameLiveStreamingStatus2 {
|
||||
int32_t user_id;
|
||||
bool is_on_air;
|
||||
uint32_t spectator_count;
|
||||
uint32_t text_message_count;
|
||||
uint32_t command_message_count;
|
||||
uint8_t reserved[52];
|
||||
};
|
||||
|
||||
static_assert(sizeof(GameLiveStreamingStatus2) == 72);
|
||||
static_assert(offsetof(GameLiveStreamingStatus2, is_on_air) == 4);
|
||||
static_assert(offsetof(GameLiveStreamingStatus2, spectator_count) == 8);
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingInitialize(size_t heap_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -3744,9 +3751,34 @@ static int KYTY_SYSV_ABI GameLiveStreamingTerminate() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingGetCurrentStatus2(GameLiveStreamingStatus2* status) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (status == nullptr) {
|
||||
return GAME_LIVE_STREAMING_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
std::memset(status, 0, sizeof(*status));
|
||||
status->user_id = -1;
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI GameLiveStreamingGetSocialFeedbackMessagesCount(int type, uint32_t* count) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (type < 1 || type > 3 || count == nullptr) {
|
||||
return GAME_LIVE_STREAMING_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*count = 0;
|
||||
return OK;
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitPlatform_1_GameLiveStreaming) {
|
||||
LIB_FUNC("kvYEw2lBndk", LibGameLiveStreaming::GameLiveStreamingInitialize);
|
||||
LIB_FUNC("9yK6Fk8mKOQ", LibGameLiveStreaming::GameLiveStreamingTerminate);
|
||||
LIB_FUNC("lK8dLBNp9OE", LibGameLiveStreaming::GameLiveStreamingGetCurrentStatus2);
|
||||
LIB_FUNC("yeQKjHETi40", LibGameLiveStreaming::GameLiveStreamingGetSocialFeedbackMessagesCount);
|
||||
}
|
||||
|
||||
} // namespace LibGameLiveStreaming
|
||||
|
||||
@@ -206,6 +206,7 @@ LIB_DEFINE(InitPad_1) {
|
||||
LIB_FUNC("rIZnR6eSpvk", Controller::PadResetOrientation);
|
||||
LIB_FUNC("vDLMoJLde8I", PadSetTiltCorrectionState);
|
||||
LIB_FUNC("gjP9-KQzoUk", Controller::PadGetControllerInformation);
|
||||
LIB_FUNC("fCWdlnmB1Ks", Controller::PadIsRemoteController);
|
||||
LIB_FUNC("hGbf2QTBmqc", PadGetExtControllerInformationStub);
|
||||
LIB_FUNC("2JgFB2n9oUM", Controller::PadSetTriggerEffect);
|
||||
LIB_FUNC("YndgXqQVV7c", Controller::PadReadState);
|
||||
|
||||
+306
-53
@@ -14,7 +14,9 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs {
|
||||
@@ -176,6 +178,13 @@ struct SaveDataMemorySet2 {
|
||||
uint8_t reserved[24];
|
||||
};
|
||||
|
||||
struct SaveDataMemorySync {
|
||||
int32_t user_id;
|
||||
uint32_t slot_id;
|
||||
uint32_t option;
|
||||
uint8_t reserved[28];
|
||||
};
|
||||
|
||||
struct SaveDataTransferringMount {
|
||||
int32_t user_id;
|
||||
const SceSaveDataTitleId* title_id;
|
||||
@@ -230,23 +239,155 @@ static constexpr uint32_t SAVE_DATA_COMMIT_MODE_BACKUP_ASYNC = 1u;
|
||||
|
||||
static constexpr uint32_t SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP_END = 1u;
|
||||
static constexpr uint32_t SAVE_DATA_EVENT_TYPE_BACKUP_END = 2u;
|
||||
static constexpr uint32_t SAVE_DATA_EVENT_TYPE_MEMORY_SYNC_END = 3u;
|
||||
static constexpr uint32_t SAVE_DATA_EVENT_TYPE_COMMIT_BACKUP_END = 4u;
|
||||
|
||||
static std::vector<uint8_t> g_save_data_memory(0x10000);
|
||||
static constexpr size_t SAVE_DATA_MEMORY_MAX_SIZE = 32 * 1024 * 1024;
|
||||
static constexpr uint32_t SAVE_DATA_MEMORY_SET_PARAM = 1u;
|
||||
static constexpr uint32_t SAVE_DATA_MEMORY_DOUBLE_BUFFER = 2u;
|
||||
|
||||
struct SaveDataMemory {
|
||||
std::vector<uint8_t> data;
|
||||
SaveDataParam param {};
|
||||
uint32_t option = 0;
|
||||
};
|
||||
|
||||
static std::map<std::filesystem::path, SaveDataMemory> g_save_data_memory;
|
||||
static int32_t g_next_transaction_resource = 1;
|
||||
static std::deque<SaveDataEvent> g_save_data_events;
|
||||
static SaveDataMountSlots g_mount_slots;
|
||||
static Common::Mutex g_mount_mutex;
|
||||
|
||||
static bool valid_path_component(std::string_view name) {
|
||||
return !name.empty() && name != "." && name != ".." &&
|
||||
name.find_first_not_of(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.@") ==
|
||||
std::string_view::npos;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
static bool valid_path_component(const char (&name)[N]) {
|
||||
const auto end = std::find(name, name + N, '\0');
|
||||
return end != name + N && valid_path_component(std::string_view(name, end - name));
|
||||
}
|
||||
|
||||
static std::string get_title_id() {
|
||||
std::string title_id;
|
||||
if (!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || title_id.empty()) {
|
||||
if (!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) ||
|
||||
!valid_path_component(title_id)) {
|
||||
title_id = "UNKNOWN";
|
||||
}
|
||||
|
||||
return title_id;
|
||||
}
|
||||
|
||||
static std::string memory_dir_name(uint32_t slot_id) {
|
||||
return "sce_sdmemory" + (slot_id == 0 ? std::string() : std::to_string(slot_id));
|
||||
}
|
||||
|
||||
static std::filesystem::path save_directory(std::string_view title_id, std::string_view dir_name,
|
||||
int32_t user_id) {
|
||||
const auto directory = std::filesystem::path(SAVE_DATA_DIR) / title_id / dir_name;
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
if (dir_name == memory_dir_name(slot)) {
|
||||
return directory / std::to_string(user_id);
|
||||
}
|
||||
}
|
||||
return directory;
|
||||
}
|
||||
|
||||
static std::filesystem::path memory_directory(int32_t user_id, uint32_t slot_id) {
|
||||
return save_directory(get_title_id(), memory_dir_name(slot_id), user_id);
|
||||
}
|
||||
|
||||
static bool valid_memory_range(const SaveDataMemoryData& data, size_t size) {
|
||||
return data.buf != nullptr && data.offset >= 0 && static_cast<uint64_t>(data.offset) <= size &&
|
||||
data.buf_size <= size - static_cast<size_t>(data.offset);
|
||||
}
|
||||
|
||||
// Keep memory.dat as raw guest bytes. Replacing a flushed temporary file protects the
|
||||
// previous save if writing fails or the emulator exits partway through an update.
|
||||
static int write_memory_file(const std::filesystem::path& path, const void* data, uint32_t size) {
|
||||
const auto temporary = std::filesystem::path(path.string() + ".tmp");
|
||||
Common::File file;
|
||||
if (!file.Create(temporary)) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
uint32_t written = 0;
|
||||
file.Write(data, size, &written);
|
||||
const bool flushed = file.Flush();
|
||||
file.Close();
|
||||
std::error_code error;
|
||||
if (written == size && flushed) {
|
||||
// Common::File::RenameFile deliberately refuses to replace an existing file.
|
||||
std::filesystem::rename(temporary, path, error);
|
||||
if (!error) {
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
std::filesystem::remove(temporary, error);
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
|
||||
static int save_memory(const std::filesystem::path& directory, const SaveDataMemory& memory) {
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(directory, error);
|
||||
if (error) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
// Display metadata is a separate host sidecar, leaving memory.dat as raw guest bytes.
|
||||
int result = write_memory_file(directory / "param.bin", &memory.param, sizeof(memory.param));
|
||||
if (result == OK) {
|
||||
result =
|
||||
write_memory_file(directory / "memory.dat", memory.data.data(), memory.data.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int load_memory(const std::filesystem::path& directory, SaveDataMemory* memory,
|
||||
size_t* existed_size) {
|
||||
std::error_code error;
|
||||
const bool exists = std::filesystem::exists(directory / "memory.dat", error);
|
||||
if (error) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
if (!exists) {
|
||||
return OK;
|
||||
}
|
||||
Common::File file(directory / "memory.dat", Common::File::Mode::Read);
|
||||
if (file.IsInvalid()) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
*existed_size = file.Size();
|
||||
if (*existed_size == 0 || *existed_size > SAVE_DATA_MEMORY_MAX_SIZE) {
|
||||
return SAVE_DATA_ERROR_BROKEN;
|
||||
}
|
||||
const auto size = static_cast<uint32_t>(std::min(*existed_size, memory->data.size()));
|
||||
uint32_t read = 0;
|
||||
file.Read(memory->data.data(), size, &read);
|
||||
if (read != size) {
|
||||
return SAVE_DATA_ERROR_BROKEN;
|
||||
}
|
||||
file.Close();
|
||||
const bool has_param = std::filesystem::exists(directory / "param.bin", error);
|
||||
if (error) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
if (has_param) {
|
||||
if (!file.Open(directory / "param.bin", Common::File::Mode::Read)) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
if (file.Size() != sizeof(memory->param)) {
|
||||
return SAVE_DATA_ERROR_BROKEN;
|
||||
}
|
||||
file.Read(&memory->param, sizeof(memory->param), &read);
|
||||
if (read != sizeof(memory->param)) {
|
||||
return SAVE_DATA_ERROR_BROKEN;
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
static void queue_save_data_event(uint32_t type, int32_t user_id,
|
||||
const SceSaveDataTitleId* title_id,
|
||||
const SceSaveDataDirName* dir_name, int32_t error_code = OK) {
|
||||
@@ -322,6 +463,7 @@ int KYTY_SYSV_ABI SaveDataTerminate() {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
g_save_data_events.clear();
|
||||
g_save_data_memory.clear();
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -347,10 +489,17 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
PRINT_NAME();
|
||||
|
||||
if (cond == nullptr || result == nullptr ||
|
||||
(cond->title_id != nullptr && !valid_path_component(cond->title_id->data)) ||
|
||||
(result->dir_names_num != 0 && result->dir_names == nullptr) ||
|
||||
(cond->dir_name != nullptr &&
|
||||
std::memchr(cond->dir_name->data, 0, sizeof(cond->dir_name->data)) == nullptr) ||
|
||||
static_cast<uint32_t>(cond->key) > static_cast<uint32_t>(SaveDataSortKey::FreeBlocks) ||
|
||||
static_cast<uint32_t>(cond->order) > static_cast<uint32_t>(SaveDataSortOrder::Descent)) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (cond->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %d\n"
|
||||
"\t title_id = %s\n"
|
||||
@@ -369,14 +518,15 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
std::memset(result->reserved, 0, sizeof(result->reserved));
|
||||
result->pad2 = 0;
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string title_id = cond->title_id != nullptr ? cond->title_id->data : get_title_id();
|
||||
const auto root = std::filesystem::path(SAVE_DATA_DIR) / title_id;
|
||||
std::vector<std::string> dir_list;
|
||||
std::string root =
|
||||
Common::FixDirectorySlash((std::string(SAVE_DATA_DIR) + "/" + get_title_id()));
|
||||
|
||||
if (Common::File::IsDirectoryExisting(root)) {
|
||||
for (const auto& entry: Common::File::GetDirEntries(root)) {
|
||||
if (!entry.is_file && entry.name != "." && entry.name != ".." &&
|
||||
!entry.name.starts_with("sce_")) {
|
||||
!entry.name.starts_with("sce_") && valid_path_component(entry.name)) {
|
||||
if (cond->dir_name == nullptr || cond->dir_name->data[0] == '\0' ||
|
||||
dir_name_match(Common::ToLower(entry.name).c_str(),
|
||||
Common::ToLower(std::string(cond->dir_name->data)).c_str())) {
|
||||
@@ -423,9 +573,13 @@ int KYTY_SYSV_ABI SaveDataDirNameSearch(const SaveDataDirNameSearchCond* cond,
|
||||
int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result) {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(mount == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(mount_result == nullptr);
|
||||
EXIT_NOT_IMPLEMENTED(mount->dir_name == nullptr);
|
||||
if (mount == nullptr || mount_result == nullptr || mount->dir_name == nullptr ||
|
||||
!valid_path_component(mount->dir_name->data)) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (mount->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %d\n"
|
||||
"\t dir_name = %s\n"
|
||||
@@ -439,9 +593,8 @@ int KYTY_SYSV_ABI SaveDataMount3(const SaveDataMount3* mount, SaveDataMountResul
|
||||
*mount_result = {};
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir =
|
||||
std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + dir_name;
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const auto mount_dir = save_directory(get_title_id(), dir_name, mount->user_id).string();
|
||||
const bool create = ((mount->mount_mode & 4u) != 0);
|
||||
const bool create2 = ((mount->mount_mode & 32u) != 0);
|
||||
const bool open = (!create && !create2 && ((mount->mount_mode & 3u) != 0));
|
||||
@@ -481,9 +634,13 @@ int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup
|
||||
SaveDataMemorySetupResult* result) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (setup_param == nullptr) {
|
||||
if (setup_param == nullptr || setup_param->slot_id >= 4 || setup_param->memory_size == 0 ||
|
||||
setup_param->memory_size > SAVE_DATA_MEMORY_MAX_SIZE || (setup_param->option & ~3u) != 0) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (setup_param->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t option = 0x%08" PRIx32 "\n"
|
||||
"\t user_id = %" PRId32 "\n"
|
||||
@@ -493,13 +650,41 @@ int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup
|
||||
setup_param->option, setup_param->user_id, static_cast<uint64_t>(setup_param->memory_size),
|
||||
static_cast<uint64_t>(setup_param->icon_memory_size), setup_param->slot_id);
|
||||
|
||||
if (setup_param->memory_size > g_save_data_memory.size()) {
|
||||
g_save_data_memory.resize(setup_param->memory_size);
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto directory = memory_directory(setup_param->user_id, setup_param->slot_id);
|
||||
if (g_save_data_memory.contains(directory) ||
|
||||
g_mount_slots.FindAvailable(memory_dir_name(setup_param->slot_id)) ==
|
||||
SaveDataMountSlots::BUSY) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
size_t total = setup_param->memory_size *
|
||||
((setup_param->option & SAVE_DATA_MEMORY_DOUBLE_BUFFER) != 0 ? 2 : 1);
|
||||
for (const auto& [path, memory]: g_save_data_memory) {
|
||||
total +=
|
||||
memory.data.size() * ((memory.option & SAVE_DATA_MEMORY_DOUBLE_BUFFER) != 0 ? 2 : 1);
|
||||
}
|
||||
if (g_save_data_memory.size() >= 4 || total > SAVE_DATA_MEMORY_MAX_SIZE) {
|
||||
return SAVE_DATA_ERROR_LIMITATION_OVER;
|
||||
}
|
||||
SaveDataMemory memory;
|
||||
memory.data.resize(setup_param->memory_size);
|
||||
memory.option = setup_param->option;
|
||||
std::snprintf(memory.param.title, sizeof(memory.param.title), "Saved Data");
|
||||
size_t existed_size = 0;
|
||||
const int status = load_memory(directory, &memory, &existed_size);
|
||||
if (status != OK) {
|
||||
return status;
|
||||
}
|
||||
if (existed_size == 0 && setup_param->init_param != nullptr &&
|
||||
(memory.option & SAVE_DATA_MEMORY_SET_PARAM) != 0) {
|
||||
memory.param = *setup_param->init_param;
|
||||
memory.param.mtime = 0;
|
||||
}
|
||||
g_save_data_memory.emplace(directory, std::move(memory));
|
||||
|
||||
if (result != nullptr) {
|
||||
*result = {};
|
||||
result->existed_memory_size = g_save_data_memory.size();
|
||||
result->existed_memory_size = existed_size;
|
||||
}
|
||||
|
||||
return OK;
|
||||
@@ -508,9 +693,12 @@ int KYTY_SYSV_ABI SaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup
|
||||
int KYTY_SYSV_ABI SaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (get_param == nullptr) {
|
||||
if (get_param == nullptr || get_param->slot_id >= 4) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (get_param->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %" PRId32 "\n"
|
||||
"\t data = 0x%016" PRIx64 "\n"
|
||||
@@ -521,20 +709,23 @@ int KYTY_SYSV_ABI SaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) {
|
||||
reinterpret_cast<uint64_t>(get_param->param), reinterpret_cast<uint64_t>(get_param->icon),
|
||||
get_param->slot_id);
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto it =
|
||||
g_save_data_memory.find(memory_directory(get_param->user_id, get_param->slot_id));
|
||||
if (it == g_save_data_memory.end()) {
|
||||
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
|
||||
}
|
||||
const auto& memory = it->second;
|
||||
if ((get_param->param != nullptr && (memory.option & SAVE_DATA_MEMORY_SET_PARAM) == 0) ||
|
||||
(get_param->data != nullptr && !valid_memory_range(*get_param->data, memory.data.size()))) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (get_param->data != nullptr) {
|
||||
auto* data = get_param->data;
|
||||
if (data->buf == nullptr || data->offset < 0) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
const auto offset = static_cast<size_t>(data->offset);
|
||||
if (offset + data->buf_size > g_save_data_memory.size()) {
|
||||
g_save_data_memory.resize(offset + data->buf_size);
|
||||
}
|
||||
std::memcpy(data->buf, g_save_data_memory.data() + offset, data->buf_size);
|
||||
const auto& data = *get_param->data;
|
||||
std::memcpy(data.buf, memory.data.data() + data.offset, data.buf_size);
|
||||
}
|
||||
if (get_param->param != nullptr) {
|
||||
std::memset(get_param->param, 0, sizeof(*get_param->param));
|
||||
*get_param->param = memory.param;
|
||||
}
|
||||
if (get_param->icon != nullptr) {
|
||||
get_param->icon->data_size = 0;
|
||||
@@ -546,9 +737,13 @@ int KYTY_SYSV_ABI SaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) {
|
||||
int KYTY_SYSV_ABI SaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (set_param == nullptr) {
|
||||
if (set_param == nullptr || set_param->slot_id >= 4 || set_param->data_num > 5 ||
|
||||
(set_param->data == nullptr && set_param->data_num != 0)) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (set_param->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %" PRId32 "\n"
|
||||
"\t data = 0x%016" PRIx64 "\n"
|
||||
@@ -560,23 +755,42 @@ int KYTY_SYSV_ABI SaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param
|
||||
reinterpret_cast<uint64_t>(set_param->param), reinterpret_cast<uint64_t>(set_param->icon),
|
||||
set_param->data_num, set_param->slot_id);
|
||||
|
||||
const uint32_t data_num = (set_param->data_num == 0 ? 1 : set_param->data_num);
|
||||
if (set_param->data != nullptr) {
|
||||
for (uint32_t i = 0; i < data_num; i++) {
|
||||
const auto& data = set_param->data[i];
|
||||
if (data.buf == nullptr || data.offset < 0) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
const auto offset = static_cast<size_t>(data.offset);
|
||||
if (offset + data.buf_size > g_save_data_memory.size()) {
|
||||
g_save_data_memory.resize(offset + data.buf_size);
|
||||
}
|
||||
std::memcpy(g_save_data_memory.data() + offset, data.buf, data.buf_size);
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto it =
|
||||
g_save_data_memory.find(memory_directory(set_param->user_id, set_param->slot_id));
|
||||
if (it == g_save_data_memory.end()) {
|
||||
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
|
||||
}
|
||||
if (set_param->param != nullptr && (it->second.option & SAVE_DATA_MEMORY_SET_PARAM) == 0) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
// Some games supply one descriptor with an implicit zero count.
|
||||
const uint32_t data_num =
|
||||
set_param->data != nullptr && set_param->data_num == 0 ? 1 : set_param->data_num;
|
||||
for (uint32_t i = 0; i < data_num; i++) {
|
||||
if (!valid_memory_range(set_param->data[i], it->second.data.size())) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
}
|
||||
if (data_num == 0 && set_param->param == nullptr) {
|
||||
return OK;
|
||||
}
|
||||
|
||||
return OK;
|
||||
// Save the complete update before publishing it so progress survives process exit.
|
||||
auto updated = it->second;
|
||||
for (uint32_t i = 0; i < data_num; i++) {
|
||||
const auto& data = set_param->data[i];
|
||||
std::memcpy(updated.data.data() + data.offset, data.buf, data.buf_size);
|
||||
}
|
||||
if (set_param->param != nullptr) {
|
||||
updated.param = *set_param->param;
|
||||
}
|
||||
updated.param.mtime = static_cast<int64_t>(std::time(nullptr));
|
||||
const int status = save_memory(it->first, updated);
|
||||
if (status == OK) {
|
||||
it->second = std::move(updated);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mount,
|
||||
@@ -584,9 +798,13 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
|
||||
PRINT_NAME();
|
||||
|
||||
if (mount == nullptr || mount_result == nullptr || mount->title_id == nullptr ||
|
||||
mount->dir_name == nullptr) {
|
||||
mount->dir_name == nullptr || !valid_path_component(mount->title_id->data) ||
|
||||
!valid_path_component(mount->dir_name->data)) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (mount->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %" PRId32 "\n"
|
||||
"\t title_id = %s\n"
|
||||
@@ -597,8 +815,7 @@ int KYTY_SYSV_ABI SaveDataTransferringMount(const SaveDataTransferringMount* mou
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const std::string dir_name = mount->dir_name->data;
|
||||
const std::string mount_dir =
|
||||
std::string(SAVE_DATA_DIR) + "/" + mount->title_id->data + "/" + dir_name;
|
||||
const auto mount_dir = save_directory(mount->title_id->data, dir_name, mount->user_id).string();
|
||||
const int slot = g_mount_slots.FindAvailable(dir_name);
|
||||
if (slot == SaveDataMountSlots::BUSY) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
@@ -669,6 +886,7 @@ int KYTY_SYSV_ABI SaveDataCommit(const SaveDataCommitParam* param) {
|
||||
param->resource, param->commit_mode);
|
||||
|
||||
if ((param->commit_mode & SAVE_DATA_COMMIT_MODE_BACKUP_ASYNC) != 0) {
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
queue_save_data_event(SAVE_DATA_EVENT_TYPE_COMMIT_BACKUP_END, 0, nullptr, nullptr);
|
||||
}
|
||||
|
||||
@@ -678,9 +896,13 @@ int KYTY_SYSV_ABI SaveDataCommit(const SaveDataCommitParam* param) {
|
||||
int KYTY_SYSV_ABI SaveDataDelete(const SaveDataDelete* del) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (del == nullptr || del->dir_name == nullptr) {
|
||||
if (del == nullptr || del->dir_name == nullptr || !valid_path_component(del->dir_name->data) ||
|
||||
(del->title_id != nullptr && !valid_path_component(del->title_id->data))) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (del->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
|
||||
LOGF("\t user_id = %" PRId32 "\n"
|
||||
"\t title_id = %s\n"
|
||||
@@ -688,13 +910,17 @@ int KYTY_SYSV_ABI SaveDataDelete(const SaveDataDelete* del) {
|
||||
del->user_id, del->title_id != nullptr ? del->title_id->data : "<default>",
|
||||
del->dir_name->data);
|
||||
|
||||
std::string dir =
|
||||
std::string(SAVE_DATA_DIR) + "/" + get_title_id() + "/" + std::string(del->dir_name->data);
|
||||
if (Common::File::IsDirectoryExisting(dir)) {
|
||||
Common::File::DeleteDirectory(dir);
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto dir = save_directory(del->title_id != nullptr ? del->title_id->data : get_title_id(),
|
||||
del->dir_name->data, del->user_id);
|
||||
if (g_mount_slots.FindAvailable(del->dir_name->data) == SaveDataMountSlots::BUSY ||
|
||||
g_save_data_memory.contains(dir)) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
std::error_code error;
|
||||
std::filesystem::remove_all(dir, error);
|
||||
|
||||
return OK;
|
||||
return error ? SAVE_DATA_ERROR_INTERNAL : OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataGetParam(const SaveDataMountPoint* mount_point, uint32_t param_type,
|
||||
@@ -760,10 +986,35 @@ int KYTY_SYSV_ABI SaveDataSaveIconByPath(const SaveDataMountPoint* mount_point,
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const void* sync_param) {
|
||||
int KYTY_SYSV_ABI SaveDataSyncSaveDataMemory(const SaveDataMemorySync* sync_param) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t sync_param = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(sync_param));
|
||||
if (sync_param == nullptr || sync_param->slot_id >= 4 || sync_param->option > 1) {
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
if (sync_param->user_id < 0) {
|
||||
return SAVE_DATA_ERROR_INVALID_LOGIN_USER;
|
||||
}
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
const auto it =
|
||||
g_save_data_memory.find(memory_directory(sync_param->user_id, sync_param->slot_id));
|
||||
if (it == g_save_data_memory.end()) {
|
||||
return SAVE_DATA_ERROR_MEMORY_NOT_READY;
|
||||
}
|
||||
const auto dir_name = memory_dir_name(sync_param->slot_id);
|
||||
if (g_mount_slots.FindAvailable(dir_name) == SaveDataMountSlots::BUSY) {
|
||||
return SAVE_DATA_ERROR_BUSY;
|
||||
}
|
||||
const int status = save_memory(it->first, it->second);
|
||||
if (sync_param->option == 1) {
|
||||
return status;
|
||||
}
|
||||
SceSaveDataTitleId title {};
|
||||
SceSaveDataDirName directory {};
|
||||
std::snprintf(title.data, sizeof(title.data), "%s", get_title_id().c_str());
|
||||
std::snprintf(directory.data, sizeof(directory.data), "%s", dir_name.c_str());
|
||||
queue_save_data_event(SAVE_DATA_EVENT_TYPE_MEMORY_SYNC_END, sync_param->user_id, &title,
|
||||
&directory, status);
|
||||
|
||||
return OK;
|
||||
}
|
||||
@@ -779,6 +1030,7 @@ int KYTY_SYSV_ABI SaveDataGetEventResult(const void* event_param, SaveDataEvent*
|
||||
return SAVE_DATA_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
if (g_save_data_events.empty()) {
|
||||
return SAVE_DATA_ERROR_NOT_FOUND;
|
||||
}
|
||||
@@ -812,6 +1064,7 @@ int KYTY_SYSV_ABI SaveDataBackup(const SaveDataBackup* backup) {
|
||||
backup->user_id, backup->title_id != nullptr ? backup->title_id->data : "<default>",
|
||||
backup->dir_name->data);
|
||||
|
||||
Common::LockGuard lock(g_mount_mutex);
|
||||
queue_save_data_event(SAVE_DATA_EVENT_TYPE_BACKUP_END, backup->user_id, backup->title_id,
|
||||
backup->dir_name);
|
||||
|
||||
|
||||
@@ -92,7 +92,9 @@ static KYTY_SYSV_ABI int UserServiceGetLoginUserIdList(UserServiceLoginUserIdLis
|
||||
}
|
||||
|
||||
static KYTY_SYSV_ABI int UserServiceGetUserName(int user_id, char* name, size_t size) {
|
||||
EXIT_NOT_IMPLEMENTED(user_id != Config::GetUserId());
|
||||
if (user_id != Config::GetUserId()) {
|
||||
return USER_SERVICE_ERROR_NOT_LOGGED_IN;
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(size < 5);
|
||||
|
||||
const auto& user_name = Config::GetUserName();
|
||||
|
||||
+295
-76
@@ -14,15 +14,17 @@
|
||||
#endif
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
@@ -40,8 +42,11 @@
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ranges.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Network {
|
||||
@@ -251,7 +256,7 @@ void Shutdown() {
|
||||
int Network::PoolCreate(const char* name, int size) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (int id = 0; id < POOLS_MAX; id++) {
|
||||
for (int id = 1; id < POOLS_MAX; id++) {
|
||||
if (!m_pools[id].used) {
|
||||
m_pools[id].used = true;
|
||||
m_pools[id].size = size;
|
||||
@@ -843,9 +848,37 @@ using SocketLength = socklen_t;
|
||||
using SocketIoLength = size_t;
|
||||
#endif
|
||||
|
||||
struct SocketTransport {
|
||||
explicit SocketTransport(NativeSocket value): socket(value) {}
|
||||
~SocketTransport() { Close(); }
|
||||
int Close() {
|
||||
if (socket == INVALID_NATIVE_SOCKET) {
|
||||
return 0;
|
||||
}
|
||||
const auto value = std::exchange(socket, INVALID_NATIVE_SOCKET);
|
||||
#if defined(_WIN32)
|
||||
return closesocket(value);
|
||||
#else
|
||||
return ::close(value);
|
||||
#endif
|
||||
}
|
||||
NativeSocket socket;
|
||||
};
|
||||
|
||||
struct P2pEndpoint {
|
||||
bool bound = false;
|
||||
uint32_t address = 0;
|
||||
uint16_t port = 0;
|
||||
uint16_t virtual_port = 0;
|
||||
int nonblocking = 0;
|
||||
int broadcast = 0;
|
||||
int send_buffer = 9216;
|
||||
int receive_buffer = 40 * 1024;
|
||||
};
|
||||
|
||||
struct SocketSlot {
|
||||
bool used = false;
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
std::shared_ptr<SocketTransport> transport;
|
||||
std::optional<P2pEndpoint> p2p;
|
||||
};
|
||||
|
||||
struct NetTimeval {
|
||||
@@ -997,6 +1030,16 @@ static int ConvertSocketOptionLevel(int level) {
|
||||
return (level == 0xffff ? SOL_SOCKET : level);
|
||||
}
|
||||
|
||||
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
|
||||
switch (option) {
|
||||
case 0x0020: return &endpoint.broadcast;
|
||||
case 0x1001: return &endpoint.send_buffer;
|
||||
case 0x1002: return &endpoint.receive_buffer;
|
||||
case 0x1200: return &endpoint.nonblocking;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static int ConvertMessageFlags(int flags) {
|
||||
constexpr int guest_msg_peek = 0x00000002;
|
||||
constexpr int guest_msg_dontroute = 0x00000004;
|
||||
@@ -1103,19 +1146,30 @@ static int ConvertHostSockaddr(const sockaddr_storage* addr, SocketLength addrle
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool GetSocketBackend(int guest_fd, NativeSocket* out) {
|
||||
static bool GetSocketBackend(int guest_fd, NativeSocket* out, SocketSlot* state = nullptr) {
|
||||
EXIT_IF(out == nullptr);
|
||||
|
||||
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
|
||||
SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
if (!g_sockets[static_cast<size_t>(guest_fd)].used) {
|
||||
const auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
|
||||
if (!slot.transport) {
|
||||
SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
return false;
|
||||
}
|
||||
// Native-only operations cannot act on a transport shared by virtual ports.
|
||||
if (slot.p2p && state == nullptr) {
|
||||
SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = g_sockets[static_cast<size_t>(guest_fd)].socket;
|
||||
*out = slot.transport->socket;
|
||||
if (state != nullptr) {
|
||||
*state = slot;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1125,10 +1179,10 @@ bool KYTY_SYSV_ABI IsSocket(int s) {
|
||||
}
|
||||
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
return g_sockets[static_cast<size_t>(s)].used;
|
||||
return g_sockets[static_cast<size_t>(s)].transport != nullptr;
|
||||
}
|
||||
|
||||
static bool TakeSocketBackend(int guest_fd, NativeSocket* out) {
|
||||
static bool TakeSocketBackend(int guest_fd, SocketSlot* out) {
|
||||
EXIT_IF(out == nullptr);
|
||||
|
||||
if (guest_fd < 0 || guest_fd >= SOCKET_FD_MAX) {
|
||||
@@ -1137,22 +1191,24 @@ static bool TakeSocketBackend(int guest_fd, NativeSocket* out) {
|
||||
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
auto& slot = g_sockets[static_cast<size_t>(guest_fd)];
|
||||
if (!slot.used) {
|
||||
if (!slot.transport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*out = slot.socket;
|
||||
*out = std::move(slot);
|
||||
slot = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
static int AllocSocketFd(NativeSocket socket) {
|
||||
static int AllocSocketFd(const std::shared_ptr<SocketTransport>& transport, bool p2p = false) {
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
for (int fd = SOCKET_FD_MIN; fd < SOCKET_FD_MAX; fd++) {
|
||||
auto& slot = g_sockets[static_cast<size_t>(fd)];
|
||||
if (!slot.used) {
|
||||
slot.used = true;
|
||||
slot.socket = socket;
|
||||
if (!slot.transport) {
|
||||
slot.transport = transport;
|
||||
if (p2p) {
|
||||
slot.p2p.emplace();
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
@@ -1527,10 +1583,10 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct HostRegistration {
|
||||
EpollRegistration guest;
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
SocketSlot state;
|
||||
};
|
||||
|
||||
fd_set host_read {};
|
||||
@@ -1542,14 +1598,23 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
|
||||
std::vector<HostRegistration> host_registrations;
|
||||
host_registrations.reserve(registrations.size());
|
||||
int host_nfds = 0;
|
||||
for (const auto& registration: registrations) {
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(registration.id, &socket)) {
|
||||
SocketSlot state;
|
||||
if (!GetSocketBackend(registration.id, &socket, &state)) {
|
||||
continue;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if (host_registrations.size() >= FD_SETSIZE) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
#else
|
||||
if (socket >= FD_SETSIZE) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
host_nfds = std::max(host_nfds, socket + 1);
|
||||
#endif
|
||||
if ((registration.event.events & EPOLL_IN) != 0) {
|
||||
FD_SET(socket, &host_read);
|
||||
}
|
||||
@@ -1557,7 +1622,7 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
FD_SET(socket, &host_write);
|
||||
}
|
||||
FD_SET(socket, &host_except);
|
||||
host_registrations.push_back({registration, socket});
|
||||
host_registrations.push_back({registration, socket, std::move(state)});
|
||||
}
|
||||
|
||||
if (host_registrations.empty()) {
|
||||
@@ -1572,8 +1637,8 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
host_timeout_ptr = &host_timeout;
|
||||
}
|
||||
|
||||
const int result = ::select(0, &host_read, &host_write, &host_except, host_timeout_ptr);
|
||||
if (result == SOCKET_ERROR) {
|
||||
const int result = ::select(host_nfds, &host_read, &host_write, &host_except, host_timeout_ptr);
|
||||
if (result < 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
if (result == 0) {
|
||||
@@ -1583,10 +1648,15 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
int count = 0;
|
||||
for (const auto& registration: host_registrations) {
|
||||
uint32_t ready = 0;
|
||||
if (FD_ISSET(registration.socket, &host_read)) {
|
||||
if ((registration.guest.event.events & EPOLL_IN) != 0 &&
|
||||
FD_ISSET(registration.socket, &host_read)) {
|
||||
if (registration.state.p2p) {
|
||||
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
|
||||
}
|
||||
ready |= EPOLL_IN;
|
||||
}
|
||||
if (FD_ISSET(registration.socket, &host_write)) {
|
||||
if ((registration.guest.event.events & EPOLL_OUT) != 0 &&
|
||||
FD_ISSET(registration.socket, &host_write)) {
|
||||
ready |= EPOLL_OUT;
|
||||
}
|
||||
if (FD_ISSET(registration.socket, &host_except)) {
|
||||
@@ -1606,10 +1676,6 @@ int KYTY_SYSV_ABI EpollWait(int eid, NetEpollEvent* events, int maxevents, int t
|
||||
}
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
(void)timeout;
|
||||
return SetGuestSocketError(Posix::POSIX_ENOSYS);
|
||||
#endif
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI EpollDestroy(int eid) {
|
||||
@@ -1631,18 +1697,13 @@ int KYTY_SYSV_ABI SocketClose(int s) {
|
||||
|
||||
LOGF("\t s = %d\n", s);
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!TakeSocketBackend(s, &socket)) {
|
||||
SocketSlot slot;
|
||||
if (!TakeSocketBackend(s, &slot)) {
|
||||
return NET_ERROR_EBADF;
|
||||
}
|
||||
RemoveSocketFromEpolls(s);
|
||||
|
||||
#if defined(_WIN32)
|
||||
const int result = closesocket(socket);
|
||||
#else
|
||||
const int result = ::close(socket);
|
||||
#endif
|
||||
if (result != 0) {
|
||||
if (slot.transport.use_count() == 1 && slot.transport->Close() != 0) {
|
||||
return NET_ERROR_EBADF;
|
||||
}
|
||||
|
||||
@@ -1667,18 +1728,31 @@ int KYTY_SYSV_ABI Socket(int family, int type, int protocol) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
NativeSocket socket = ::socket(host_family, type, protocol);
|
||||
constexpr int socket_dgram_p2p = 6;
|
||||
const bool p2p = type == socket_dgram_p2p;
|
||||
if (p2p && (family != 2 || protocol != 0)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EPROTONOSUPPORT);
|
||||
}
|
||||
NativeSocket socket = ::socket(host_family, p2p ? SOCK_DGRAM : type, protocol);
|
||||
if (socket == INVALID_NATIVE_SOCKET) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
|
||||
const int fd = AllocSocketFd(socket);
|
||||
if (fd < 0) {
|
||||
auto transport = std::make_shared<SocketTransport>(socket);
|
||||
if (p2p) {
|
||||
// Logical sockets keep their own blocking mode; the shared transport never blocks.
|
||||
#if defined(_WIN32)
|
||||
closesocket(socket);
|
||||
u_long enabled = 1;
|
||||
const int result = ioctlsocket(socket, FIONBIO, &enabled);
|
||||
#else
|
||||
::close(socket);
|
||||
const int result = ::fcntl(socket, F_SETFL, O_NONBLOCK);
|
||||
#endif
|
||||
if (result != 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
}
|
||||
const int fd = AllocSocketFd(transport, p2p);
|
||||
if (fd < 0) {
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
|
||||
return -1;
|
||||
}
|
||||
@@ -1696,8 +1770,11 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
|
||||
s, reinterpret_cast<uint64_t>(addr), addrlen);
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (addr == nullptr || !GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = (addr == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
|
||||
SocketSlot state;
|
||||
if (addr == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
if (!GetSocketBackend(s, &socket, &state)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1707,6 +1784,67 @@ int KYTY_SYSV_ABI Bind(int s, const void* addr, uint32_t addrlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state.p2p) {
|
||||
if (addrlen < 16) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
const auto& in = *reinterpret_cast<const sockaddr_in*>(&host_addr);
|
||||
uint32_t address = 0;
|
||||
std::memcpy(&address, &in.sin_addr, sizeof(address));
|
||||
uint16_t virtual_port = 0;
|
||||
std::memcpy(&virtual_port, static_cast<const uint8_t*>(addr) + 8, sizeof(virtual_port));
|
||||
const auto requested_port = ntohs(virtual_port);
|
||||
if (requested_port == 0) {
|
||||
return SetGuestSocketError(Posix::POSIX_EOPNOTSUPP);
|
||||
}
|
||||
if (ntohs(in.sin_port) != 3658) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
if (requested_port == 5353 || requested_port >= 32768) {
|
||||
return SetGuestSocketError(Posix::POSIX_EACCES);
|
||||
}
|
||||
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
auto& slot = g_sockets[static_cast<size_t>(s)];
|
||||
if (slot.transport != state.transport) {
|
||||
return SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
}
|
||||
auto& endpoint = *slot.p2p;
|
||||
if (endpoint.bound) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
auto conflicts = [&](uint16_t port) {
|
||||
return std::any_of(g_sockets.begin(), g_sockets.end(), [&](const auto& other) {
|
||||
return other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
|
||||
other.p2p->address == address && other.p2p->virtual_port == port;
|
||||
});
|
||||
};
|
||||
if (conflicts(virtual_port)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EADDRINUSE);
|
||||
}
|
||||
std::shared_ptr<SocketTransport> shared;
|
||||
for (const auto& other: g_sockets) {
|
||||
if (other.p2p && other.p2p->bound && other.p2p->port == in.sin_port &&
|
||||
other.p2p->address == address) {
|
||||
shared = other.transport;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shared) {
|
||||
slot.transport = std::move(shared);
|
||||
} else if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) !=
|
||||
0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
endpoint.bound = true;
|
||||
endpoint.address = address;
|
||||
endpoint.port = in.sin_port;
|
||||
endpoint.virtual_port = virtual_port;
|
||||
LOGF("\t UDPP2P bound UDP port=%u virtual port=%u\n", ntohs(endpoint.port),
|
||||
ntohs(endpoint.virtual_port));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (::bind(socket, reinterpret_cast<const sockaddr*>(&host_addr), host_addrlen) != 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
@@ -1723,8 +1861,10 @@ int KYTY_SYSV_ABI Connect(int s, const void* addr, uint32_t addrlen) {
|
||||
s, reinterpret_cast<uint64_t>(addr), addrlen);
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (addr == nullptr || !GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = (addr == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
|
||||
if (addr == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1750,7 +1890,6 @@ int KYTY_SYSV_ABI Listen(int s, int backlog) {
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1771,7 +1910,6 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1783,13 +1921,9 @@ int KYTY_SYSV_ABI Accept(int s, void* addr, uint32_t* addrlen) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
|
||||
const int fd = AllocSocketFd(accepted);
|
||||
auto transport = std::make_shared<SocketTransport>(accepted);
|
||||
const int fd = AllocSocketFd(transport);
|
||||
if (fd < 0) {
|
||||
#if defined(_WIN32)
|
||||
closesocket(accepted);
|
||||
#else
|
||||
::close(accepted);
|
||||
#endif
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EMFILE;
|
||||
return -1;
|
||||
}
|
||||
@@ -1818,7 +1952,6 @@ int KYTY_SYSV_ABI Shutdown(int s, int how) {
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -1847,12 +1980,14 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
|
||||
"\t addrlen = 0x%016" PRIx64 "\n",
|
||||
s, reinterpret_cast<uint64_t>(addr), reinterpret_cast<uint64_t>(addrlen));
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
const bool socket_ok = GetSocketBackend(s, &socket);
|
||||
if (addr == nullptr || addrlen == nullptr || !socket_ok) {
|
||||
*Posix::GetErrorAddr() = (!socket_ok ? Posix::POSIX_EBADF : Posix::POSIX_EFAULT);
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
SocketSlot state;
|
||||
if (!GetSocketBackend(s, &socket, &state)) {
|
||||
return -1;
|
||||
}
|
||||
if (addr == nullptr || addrlen == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
|
||||
sockaddr_storage host_addr {};
|
||||
SocketLength host_addrlen = sizeof(host_addr);
|
||||
@@ -1860,7 +1995,11 @@ int KYTY_SYSV_ABI Getsockname(int s, void* addr, uint32_t* addrlen) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
|
||||
return ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
|
||||
const int result = ConvertHostSockaddr(&host_addr, host_addrlen, addr, addrlen);
|
||||
if (result == 0 && state.p2p) {
|
||||
std::memcpy(static_cast<uint8_t*>(addr) + 8, &state.p2p->virtual_port, sizeof(uint16_t));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) {
|
||||
@@ -1871,12 +2010,26 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
|
||||
"\t optname = 0x%08" PRIx32 "\n",
|
||||
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname));
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
const bool socket_ok = GetSocketBackend(s, &socket);
|
||||
if (optval == nullptr || optlen == nullptr || !socket_ok) {
|
||||
*Posix::GetErrorAddr() = (!socket_ok ? Posix::POSIX_EBADF : Posix::POSIX_EFAULT);
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
SocketSlot state;
|
||||
if (!GetSocketBackend(s, &socket, &state)) {
|
||||
return -1;
|
||||
}
|
||||
if (optval == nullptr || optlen == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
if (state.p2p) {
|
||||
const auto* value = level == 0xffff ? P2pSocketOption(*state.p2p, optname) : nullptr;
|
||||
if (value == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
|
||||
}
|
||||
if (*optlen < sizeof(int)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
std::memcpy(optval, value, sizeof(int));
|
||||
*optlen = sizeof(int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007.
|
||||
const bool socket_error = (level == 0xffff && optname == 0x1007);
|
||||
@@ -1911,10 +2064,38 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
|
||||
s, static_cast<uint32_t>(level), static_cast<uint32_t>(optname), optlen);
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (optval == nullptr || !GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = (optval == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
|
||||
SocketSlot state;
|
||||
if (optval == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
if (!GetSocketBackend(s, &socket, &state)) {
|
||||
return -1;
|
||||
}
|
||||
if (state.p2p) {
|
||||
Common::LockGuard lock(g_socket_mutex);
|
||||
auto& slot = g_sockets[static_cast<size_t>(s)];
|
||||
if (slot.transport != state.transport) {
|
||||
return SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
}
|
||||
auto* option = level == 0xffff ? P2pSocketOption(*slot.p2p, optname) : nullptr;
|
||||
if (option == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
|
||||
}
|
||||
if (optlen != sizeof(int)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
int value = 0;
|
||||
std::memcpy(&value, optval, sizeof(value));
|
||||
if (optname == 0x1001 || optname == 0x1002) {
|
||||
if (value <= 0) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
*option = optname == 0x1002 ? std::min(value, 512 * 1024) : value;
|
||||
} else {
|
||||
*option = value != 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
constexpr int ORBIS_SO_NBIO = 0x1200;
|
||||
@@ -1961,8 +2142,11 @@ int64_t KYTY_SYSV_ABI Sendto(int s, const void* buf, uint64_t len, int flags, co
|
||||
reinterpret_cast<uint64_t>(addr), addrlen);
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (buf == nullptr || !GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = (buf == nullptr ? Posix::POSIX_EFAULT : Posix::POSIX_EBADF);
|
||||
if (buf == nullptr) {
|
||||
return SetGuestSocketError(Posix::POSIX_EFAULT);
|
||||
}
|
||||
// UDPP2P requires protocol framing; the native-only lookup rejects raw sends.
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -2016,7 +2200,6 @@ int64_t KYTY_SYSV_ABI Recvfrom(int s, void* buf, uint64_t len, int flags, void*
|
||||
|
||||
NativeSocket socket = INVALID_NATIVE_SOCKET;
|
||||
if (!GetSocketBackend(s, &socket)) {
|
||||
*Posix::GetErrorAddr() = Posix::POSIX_EBADF;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -2106,7 +2289,7 @@ int KYTY_SYSV_ABI Select(int nfds, void* readfds, void* writefds, void* exceptfd
|
||||
} else
|
||||
#endif
|
||||
if (!GetSocketBackend(fd, &socket)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EBADF);
|
||||
return -1;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
if ((read && host_read.fd_count == FD_SETSIZE) ||
|
||||
@@ -3370,14 +3553,24 @@ struct NpCheckPremiumResult {
|
||||
uint8_t reserved[32];
|
||||
};
|
||||
|
||||
constexpr int np_error_invalid_argument = -2141913085; /* 0x80550003 */
|
||||
constexpr int np_error_signed_out = -2141913082; /* 0x80550006 */
|
||||
constexpr int np_error_invalid_size = -2141913071; /* 0x80550011 */
|
||||
constexpr int np_error_aborted = -2141913070; /* 0x80550012 */
|
||||
constexpr int np_error_request_max = -2141913069; /* 0x80550013 */
|
||||
constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */
|
||||
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
|
||||
constexpr int np_request_max = 128;
|
||||
constexpr int np_error_invalid_argument = -2141913085; /* 0x80550003 */
|
||||
constexpr int np_error_signed_out = -2141913082; /* 0x80550006 */
|
||||
constexpr int np_error_callback_already_registered = -2141913080; /* 0x80550008 */
|
||||
constexpr int np_error_invalid_size = -2141913071; /* 0x80550011 */
|
||||
constexpr int np_error_aborted = -2141913070; /* 0x80550012 */
|
||||
constexpr int np_error_request_max = -2141913069; /* 0x80550013 */
|
||||
constexpr int np_error_request_not_found = -2141913068; /* 0x80550014 */
|
||||
constexpr int np_error_invalid_id = -2141913067; /* 0x80550015 */
|
||||
constexpr int np_request_max = 128;
|
||||
constexpr uint32_t np_state_signed_out = 1;
|
||||
|
||||
struct NpStateCallback {
|
||||
NpStateCallbackA callback = nullptr;
|
||||
void* userdata = nullptr;
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
static std::vector<NpStateCallback> g_np_state_callbacks;
|
||||
|
||||
enum class NpRequestState {
|
||||
Free,
|
||||
@@ -3448,6 +3641,17 @@ static int np_complete_signed_out_locked(NpRequest* request) {
|
||||
int KYTY_SYSV_ABI NpCheckCallback() {
|
||||
PRINT_NAME();
|
||||
|
||||
std::vector<NpStateCallback> pending;
|
||||
for (auto& entry: g_np_state_callbacks) {
|
||||
if (entry.pending) {
|
||||
pending.push_back(entry);
|
||||
entry.pending = false;
|
||||
}
|
||||
}
|
||||
for (const auto& entry: pending) {
|
||||
entry.callback(Config::GetUserId(), np_state_signed_out, entry.userdata);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -3496,6 +3700,21 @@ int KYTY_SYSV_ABI NpUnregisterStateCallback() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (callback == nullptr) {
|
||||
return np_error_invalid_argument;
|
||||
}
|
||||
for (const auto& entry: g_np_state_callbacks) {
|
||||
if (entry.callback == callback) {
|
||||
return np_error_callback_already_registered;
|
||||
}
|
||||
}
|
||||
g_np_state_callbacks.push_back({callback, userdata, true});
|
||||
return static_cast<int>(g_np_state_callbacks.size());
|
||||
}
|
||||
|
||||
void KYTY_SYSV_ABI NpRegisterGamePresenceCallback(void* /*callback*/, void* /*userdata*/) {
|
||||
PRINT_NAME();
|
||||
}
|
||||
@@ -3801,7 +4020,7 @@ int KYTY_SYSV_ABI NpGetState(int user_id, uint32_t* state) {
|
||||
|
||||
LOGF("\t user_id = %d\n", user_id);
|
||||
|
||||
*state = 1; // Signed out
|
||||
*state = np_state_signed_out;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -167,11 +167,14 @@ struct NpCreateAsyncRequestParameter;
|
||||
struct NpCheckPremiumParameter;
|
||||
struct NpCheckPremiumResult;
|
||||
|
||||
using NpStateCallbackA = KYTY_SYSV_ABI void (*)(int user_id, uint32_t state, void* userdata);
|
||||
|
||||
int KYTY_SYSV_ABI NpCheckCallback();
|
||||
int KYTY_SYSV_ABI NpSetNpTitleId(const NpTitleId* title_id, const NpTitleSecret* title_secret);
|
||||
int KYTY_SYSV_ABI NpSetContentRestriction(const NpContentRestriction* restriction);
|
||||
int KYTY_SYSV_ABI NpRegisterStateCallback(void* callback, void* userdata);
|
||||
int KYTY_SYSV_ABI NpUnregisterStateCallback();
|
||||
int KYTY_SYSV_ABI NpRegisterStateCallbackA(NpStateCallbackA callback, void* userdata);
|
||||
void KYTY_SYSV_ABI NpRegisterGamePresenceCallback(void* callback, void* userdata);
|
||||
int KYTY_SYSV_ABI NpRegisterPlusEventCallback(void* callback, void* userdata);
|
||||
int KYTY_SYSV_ABI NpRegisterPremiumEventCallback(void* callback, void* userdata);
|
||||
|
||||
@@ -647,20 +647,20 @@ uint64_t SystemContentGetFlexibleMemorySize() {
|
||||
return sc->psf.GetFlexibleMemorySize();
|
||||
}
|
||||
|
||||
bool SystemContentGetIconPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
if (sc->icon_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (sc->icon_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*path = Common::PathToString(sc->icon_path);
|
||||
*path = sc->icon_path;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemContentGetChunksNum(uint32_t* num) {
|
||||
|
||||
@@ -15,7 +15,7 @@ bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
||||
uint64_t SystemContentGetFlexibleMemorySize();
|
||||
bool SystemContentGetIconPath(std::string* path);
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path);
|
||||
bool SystemContentGetChunksNum(uint32_t* num);
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
+56
-11
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
@@ -62,6 +64,7 @@ static void PrintUsage() {
|
||||
::printf(" --gpu-assisted-validation <t|f> Bounds-check shader accesses on the GPU.\n"
|
||||
" Implies --vulkan-validation; very slow.\n");
|
||||
::printf(" --shader-validation <true|false> Enable shader validation.\n");
|
||||
::printf(" --tessellation Draw tessellation patches; skipped by default.\n");
|
||||
::printf(" --shader-optimization-type <value> None, Size, or Performance.\n");
|
||||
::printf(" --shader-log-direction <value> Silent, Console, or File.\n");
|
||||
::printf(" --shader-log-folder <path> Shader log output folder.\n");
|
||||
@@ -178,6 +181,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--tessellation") {
|
||||
options.config.tessellation_enabled = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--profile") {
|
||||
options.config.profiler_enabled = true;
|
||||
continue;
|
||||
@@ -207,15 +215,19 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
}
|
||||
|
||||
value = Common::FixFilenameSlash(value);
|
||||
if (Common::File::IsDirectoryExisting(value)) {
|
||||
options.app0_dir = value;
|
||||
const auto path = Common::PathFromUtf8(value);
|
||||
|
||||
if (Common::File::IsDirectoryExisting(path)) {
|
||||
options.app0_dir = path;
|
||||
options.elf = "/app0/eboot.bin";
|
||||
} else if (Common::File::IsFileExisting(value)) {
|
||||
options.app0_dir = Common::DirectoryWithoutFilename(value);
|
||||
} else if (Common::File::IsFileExisting(path)) {
|
||||
options.app0_dir = path.parent_path();
|
||||
|
||||
if (options.app0_dir.empty()) {
|
||||
options.app0_dir = ".";
|
||||
}
|
||||
options.elf = "/app0/" + Common::FilenameWithoutDirectory(value);
|
||||
|
||||
options.elf = std::filesystem::path("/app0") / path.filename();
|
||||
} else {
|
||||
::printf("--game must point to an existing directory or ELF: %s\n", value.c_str());
|
||||
return false;
|
||||
@@ -226,11 +238,13 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
return false;
|
||||
}
|
||||
value = Common::FixFilenameSlash(value);
|
||||
if (!Common::File::IsFileExisting(value)) {
|
||||
const auto path = Common::PathFromUtf8(value);
|
||||
|
||||
if (!Common::File::IsFileExisting(path)) {
|
||||
::printf("--game-patch must point to an existing file: %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
options.game_patch = value;
|
||||
options.game_patch = path;
|
||||
} else if (arg == "--screen-width") {
|
||||
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
|
||||
} else if (arg == "--screen-height") {
|
||||
@@ -291,14 +305,14 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--shader-log-folder") {
|
||||
options.config.shader_log_folder = value;
|
||||
options.config.shader_log_folder = Common::PathFromUtf8(value);
|
||||
} else if (arg == "--command-buffer-dump") {
|
||||
if (!ParseBool(value, options.config.command_buffer_dump_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--command-buffer-dump-folder") {
|
||||
options.config.command_buffer_dump_folder = value;
|
||||
options.config.command_buffer_dump_folder = Common::PathFromUtf8(value);
|
||||
} else if (arg == "--graphics-debug-dump") {
|
||||
if (!ParseBool(value, options.config.graphics_debug_dump_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
@@ -310,7 +324,7 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
return false;
|
||||
}
|
||||
} else if (arg == "--printf-output-file") {
|
||||
options.config.printf_output_file = value;
|
||||
options.config.printf_output_file = Common::PathFromUtf8(value);
|
||||
} else if (arg == "--spirv-debug-printf") {
|
||||
if (!ParseBool(value, options.config.spirv_debug_printf_enabled)) {
|
||||
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
|
||||
@@ -341,7 +355,7 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
return show_help || (!options.app0_dir.empty() && !options.elf.empty());
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
static int Main(int argc, char* argv[]) {
|
||||
VirtualMemory::Init();
|
||||
InitializeThreads();
|
||||
|
||||
@@ -367,3 +381,34 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
std::vector<std::string> utf8_args;
|
||||
utf8_args.reserve(static_cast<size_t>(argc));
|
||||
|
||||
for (int index = 0; index < argc; index++) {
|
||||
const std::wstring_view wide(argv[index]);
|
||||
const std::u16string utf16(wide.begin(), wide.end());
|
||||
|
||||
utf8_args.push_back(Common::Utf16ToUtf8(utf16));
|
||||
}
|
||||
|
||||
std::vector<char*> utf8_argv;
|
||||
utf8_argv.reserve(utf8_args.size());
|
||||
|
||||
for (auto& argument: utf8_args) {
|
||||
utf8_argv.push_back(argument.data());
|
||||
}
|
||||
|
||||
return Main(argc, utf8_argv.data());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
return Main(argc, argv);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include <chrono>
|
||||
@@ -89,6 +91,33 @@ void CheckSaveRename(const std::filesystem::path &root,
|
||||
"renamed save contents");
|
||||
}
|
||||
|
||||
void TestSaveOpenVisibility() {
|
||||
constexpr char Path[] = "/savedata0/visible-save.dat";
|
||||
constexpr char Payload[] = "saved progress";
|
||||
|
||||
const int fd = FileSystem::KernelOpen(Path, 0xa01, 0777);
|
||||
Check(fd >= 3, "create save file exclusively");
|
||||
FileSystem::FileStat stat {};
|
||||
Check(FileSystem::KernelStat(Path, &stat) == OK && stat.st_size == 0,
|
||||
"created save file is visible before close");
|
||||
Check(FileSystem::KernelOpen(Path, 0xa01, 0777) ==
|
||||
Libs::LibKernel::KERNEL_ERROR_EEXIST,
|
||||
"exclusive creation detects an open save file");
|
||||
Check(FileSystem::KernelWrite(fd, Payload, sizeof(Payload) - 1) ==
|
||||
sizeof(Payload) - 1,
|
||||
"populate save file before truncation");
|
||||
Check(FileSystem::KernelClose(fd) == OK, "close populated save file");
|
||||
Check(FileSystem::KernelStat(Path, &stat) == OK &&
|
||||
stat.st_size == sizeof(Payload) - 1,
|
||||
"save file contains the truncation fixture");
|
||||
|
||||
const int truncated = FileSystem::KernelOpen(Path, 0x401, 0777);
|
||||
Check(truncated >= 3, "truncate existing save file");
|
||||
Check(FileSystem::KernelStat(Path, &stat) == OK && stat.st_size == 0,
|
||||
"save truncation is visible before close");
|
||||
Check(FileSystem::KernelClose(truncated) == OK, "close truncated save file");
|
||||
}
|
||||
|
||||
void CheckMountRoot(const std::filesystem::path &root) {
|
||||
Common::File cache;
|
||||
Check(cache.Create(root / "rpf.cache"), "create directory listing fixture");
|
||||
@@ -126,6 +155,94 @@ void CheckMountRoot(const std::filesystem::path &root) {
|
||||
}
|
||||
}
|
||||
FileSystem::Umount("/app0");
|
||||
Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache",
|
||||
"unmount by guest path");
|
||||
for (const auto &folder : {root, root / ""}) {
|
||||
for (const auto &host : {root, root / ""}) {
|
||||
FileSystem::Mount(folder, "/app0");
|
||||
FileSystem::Umount(Common::PathToGenericString(host));
|
||||
Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache",
|
||||
"unmount by host path with or without trailing separator");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUnicodePaths(const std::filesystem::path &root) {
|
||||
constexpr std::string_view HostDirectory =
|
||||
"Test\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
|
||||
constexpr std::string_view GuestFilename =
|
||||
"asset-\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e.bin";
|
||||
|
||||
const auto unicode_root =
|
||||
root / Common::PathFromUtf8(HostDirectory);
|
||||
const auto nested_root = unicode_root / "nested";
|
||||
|
||||
Check(Common::File::CreateDirectories(nested_root),
|
||||
"create nested Unicode host directory");
|
||||
|
||||
CheckMountRoot(nested_root);
|
||||
|
||||
const auto native_file =
|
||||
nested_root / Common::PathFromUtf8(GuestFilename);
|
||||
|
||||
Common::File fixture;
|
||||
Check(fixture.Create(native_file), "create Unicode filename");
|
||||
fixture.Close();
|
||||
|
||||
for (const auto &directory : {unicode_root, std::filesystem::relative(unicode_root)}) {
|
||||
// A Windows temporary directory may be on a different drive from the working directory.
|
||||
if (directory.empty()) {
|
||||
continue;
|
||||
}
|
||||
const auto files = Common::File::FindFiles(directory);
|
||||
const auto relative_file =
|
||||
std::filesystem::path(u8"nested/asset-\u00e9-\u65e5\u672c\u8a9e.bin");
|
||||
Check(std::any_of(files.begin(), files.end(), [&](const auto &file) {
|
||||
return file.path_with_name.lexically_normal() ==
|
||||
(directory / relative_file).lexically_normal() &&
|
||||
file.rel_path_with_name == relative_file;
|
||||
}), "recursive enumeration preserves native and relative Unicode paths");
|
||||
}
|
||||
const auto entries = Common::File::GetDirEntries(nested_root);
|
||||
Check(std::any_of(entries.begin(), entries.end(), [&](const auto &entry) {
|
||||
return entry.is_file && entry.name == GuestFilename;
|
||||
}), "directory enumeration returns UTF-8 filenames");
|
||||
|
||||
FileSystem::Mount(nested_root, "/app0");
|
||||
|
||||
const auto guest_file =
|
||||
std::string("/app0/") + std::string(GuestFilename);
|
||||
|
||||
Check(FileSystem::GetRealFilename(guest_file) == native_file,
|
||||
"resolve Unicode guest path");
|
||||
|
||||
const int fd = FileSystem::KernelOpen(guest_file.c_str(), 0, 0);
|
||||
Check(fd >= 3, "open Unicode guest path");
|
||||
Check(FileSystem::KernelClose(fd) == OK,
|
||||
"close Unicode guest path");
|
||||
|
||||
FileSystem::Umount("/app0");
|
||||
}
|
||||
|
||||
void CheckUnicodeLogPath(const std::filesystem::path &root) {
|
||||
Config::ConfigOptions options;
|
||||
options.printf_direction = Config::LogDirection::File;
|
||||
options.printf_output_file = root / u8"logs-\u65e5\u672c\u8a9e-\U0001f600" / u8"log-\u00e9.txt";
|
||||
Config::Load(options);
|
||||
Log::Initialize();
|
||||
constexpr std::string_view Payload = "Unicode log path\n";
|
||||
Log::Write(Payload);
|
||||
Log::Shutdown();
|
||||
|
||||
Common::File result(options.printf_output_file, Common::File::Mode::Read);
|
||||
Check(!result.IsInvalid(), "open Unicode log file");
|
||||
const auto data = result.ReadWholeBuffer();
|
||||
Check(data.size() == Payload.size() &&
|
||||
std::memcmp(data.data(), Payload.data(), data.size()) == 0,
|
||||
"Unicode log file contains output");
|
||||
options.printf_direction = Config::LogDirection::Silent;
|
||||
Config::Load(options);
|
||||
Log::Initialize();
|
||||
}
|
||||
|
||||
void CheckDirectoryStream(const std::filesystem::path &root) {
|
||||
@@ -387,9 +504,12 @@ int main() {
|
||||
TempDirectory temporary;
|
||||
FileSystem::Initialize();
|
||||
CheckMountRoot(temporary.Path());
|
||||
CheckUnicodePaths(temporary.Path());
|
||||
CheckUnicodeLogPath(temporary.Path());
|
||||
CheckDirectoryStream(temporary.Path());
|
||||
CheckAprPaths(temporary.Path());
|
||||
FileSystem::Mount(temporary.Path(), "/savedata0");
|
||||
TestSaveOpenVisibility();
|
||||
CheckSaveRename(temporary.Path(), "first-save");
|
||||
CheckSaveRename(temporary.Path(), "replacement-save");
|
||||
FileSystem::Shutdown();
|
||||
|
||||
@@ -1246,7 +1246,7 @@ ResourcePlan ConditionalSamplerPlan(bool diamond, bool reverse, bool reverse_phi
|
||||
flag = fixture.Emit(ValueOpcode::LaneId);
|
||||
}
|
||||
fixture.program.block_info[0].condition =
|
||||
fixture.Emit(ValueOpcode::IEqual32, {flag, Value(0u)});
|
||||
fixture.Emit(ValueOpcode::SGreaterThanEqual32, {flag, Value(0u)});
|
||||
if (writable) {
|
||||
MemoryInfo memory;
|
||||
memory.kind = ResourceKind::Buffer;
|
||||
@@ -1324,9 +1324,10 @@ void TestConditionalSamplerPhi() {
|
||||
.userdata = &memory,
|
||||
.read_specialization_memory =
|
||||
ReadLinearTestMemory};
|
||||
for (uint32_t flag = 0; flag < 2; ++flag) {
|
||||
memory.words[(0x1000 + 196) / 4] = flag;
|
||||
const uint32_t first = (flag != 0u) != reverse ? 480 / 4 : 448 / 4;
|
||||
// Sampler selection compares a signed value against zero.
|
||||
for (const auto flag : {-1, 0, 1, INT32_MIN, INT32_MAX}) {
|
||||
memory.words[(0x1000 + 196) / 4] = std::bit_cast<uint32_t>(flag);
|
||||
const uint32_t first = (flag < 0) != reverse ? 480 / 4 : 448 / 4;
|
||||
memory.fail_address = 0x1000 + (first == 448 / 4 ? 480u : 448u);
|
||||
DescriptorValue selected;
|
||||
Check(EvaluateDescriptorSource(plan, source, runtime, selected),
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
// Exercise the guest ABI without linking unrelated emulator subsystems.
|
||||
#include "libs/libSaveData.cpp"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
std::string g_test_title = "PPSA21564";
|
||||
std::filesystem::path g_mounted_directory;
|
||||
} // namespace
|
||||
|
||||
namespace Loader {
|
||||
bool SystemContentParamSfoGetString(const char*, std::string* value) {
|
||||
*value = g_test_title;
|
||||
return true;
|
||||
}
|
||||
void SymbolDatabase::Add(const SymbolResolve&, uint64_t, const std::string&) {}
|
||||
namespace Timer {
|
||||
Common::Time GetTime() {
|
||||
return Common::Time(0);
|
||||
}
|
||||
} // namespace Timer
|
||||
} // namespace Loader
|
||||
|
||||
namespace Libs::LibKernel::FileSystem {
|
||||
void Mount(const std::filesystem::path& directory, const std::string&) {
|
||||
g_mounted_directory = directory;
|
||||
}
|
||||
void Umount(const std::string&) {}
|
||||
} // namespace Libs::LibKernel::FileSystem
|
||||
|
||||
namespace {
|
||||
using namespace Libs::SaveData;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::fprintf(stderr, "SaveDataMemoryTests:%d: %s\n", __LINE__, #condition); \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
SaveDataMemorySetup2 SetupParam(int32_t user = 1, uint32_t slot = 0, size_t size = 16) {
|
||||
SaveDataMemorySetup2 setup {};
|
||||
setup.user_id = user;
|
||||
setup.slot_id = slot;
|
||||
setup.memory_size = size;
|
||||
return setup;
|
||||
}
|
||||
|
||||
size_t Setup(int32_t user = 1, uint32_t slot = 0, size_t size = 16, uint32_t option = 0) {
|
||||
auto setup = SetupParam(user, slot, size);
|
||||
setup.option = option;
|
||||
SaveDataMemorySetupResult result {};
|
||||
CHECK(SaveDataSetupSaveDataMemory2(&setup, &result) == OK);
|
||||
return result.existed_memory_size;
|
||||
}
|
||||
|
||||
int Set(void* bytes, size_t size, int64_t offset = 0, int32_t user = 1, uint32_t slot = 0) {
|
||||
SaveDataMemoryData data {bytes, size, offset, {}};
|
||||
SaveDataMemorySet2 set {};
|
||||
set.user_id = user;
|
||||
set.slot_id = slot;
|
||||
set.data = &data;
|
||||
set.data_num = 1;
|
||||
return SaveDataSetSaveDataMemory2(&set);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Get(size_t size = 16, int32_t user = 1, uint32_t slot = 0) {
|
||||
std::vector<uint8_t> bytes(size);
|
||||
SaveDataMemoryData data {bytes.data(), size, 0, {}};
|
||||
SaveDataMemoryGet2 get {};
|
||||
get.user_id = user;
|
||||
get.slot_id = slot;
|
||||
get.data = &data;
|
||||
CHECK(SaveDataGetSaveDataMemory2(&get) == OK);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void Reset(const char* title) {
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
g_test_title = title;
|
||||
}
|
||||
|
||||
void TestValidationAndScatter() {
|
||||
Reset("VALIDATE");
|
||||
uint8_t byte = 0x55;
|
||||
SaveDataMemoryGet2 get {};
|
||||
get.user_id = 1;
|
||||
SaveDataMemorySync sync {};
|
||||
sync.user_id = 1;
|
||||
CHECK(SaveDataGetSaveDataMemory2(&get) == SAVE_DATA_ERROR_MEMORY_NOT_READY);
|
||||
CHECK(Set(&byte, 1) == SAVE_DATA_ERROR_MEMORY_NOT_READY);
|
||||
CHECK(SaveDataSyncSaveDataMemory(&sync) == SAVE_DATA_ERROR_MEMORY_NOT_READY);
|
||||
CHECK(SaveDataSetupSaveDataMemory2(nullptr, nullptr) == SAVE_DATA_ERROR_PARAMETER);
|
||||
auto invalid_user = SetupParam(-1);
|
||||
CHECK(SaveDataSetupSaveDataMemory2(&invalid_user, nullptr) ==
|
||||
SAVE_DATA_ERROR_INVALID_LOGIN_USER);
|
||||
for (auto invalid:
|
||||
{SetupParam(1, 4), SetupParam(1, 0, 0), SetupParam(1, 0, 32 * 1024 * 1024 + 1)}) {
|
||||
CHECK(SaveDataSetupSaveDataMemory2(&invalid, nullptr) == SAVE_DATA_ERROR_PARAMETER);
|
||||
}
|
||||
CHECK(Setup() == 0);
|
||||
auto setup = SetupParam();
|
||||
CHECK(SaveDataSetupSaveDataMemory2(&setup, nullptr) == SAVE_DATA_ERROR_BUSY);
|
||||
std::array<uint8_t, 16> original {};
|
||||
for (size_t i = 0; i < original.size(); ++i) {
|
||||
original[i] = static_cast<uint8_t>(i);
|
||||
}
|
||||
CHECK(Set(original.data(), original.size()) == OK);
|
||||
SaveDataMemoryData data[] = {{&byte, 1, 2, {}}, {&byte, 1, 16, {}}};
|
||||
SaveDataMemorySet2 set {};
|
||||
set.user_id = 1;
|
||||
set.data = data;
|
||||
set.data_num = 2;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
data[1].offset = 12;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == OK);
|
||||
original[2] = original[12] = byte;
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
|
||||
byte = 0xaa;
|
||||
set.data_num = 0;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == OK);
|
||||
original[2] = byte;
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
set.data = nullptr;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == OK);
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
set.data_num = 6;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER);
|
||||
set.data_num = 1;
|
||||
set.data = nullptr;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(Set(&byte, 1, -1) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(Set(&byte, 1, std::numeric_limits<int64_t>::max()) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(Set(&byte, std::numeric_limits<size_t>::max(), 1) == SAVE_DATA_ERROR_PARAMETER);
|
||||
SaveDataMemoryData invalid {&byte, std::numeric_limits<size_t>::max(), 1, {}};
|
||||
get.data = &invalid;
|
||||
CHECK(SaveDataGetSaveDataMemory2(&get) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(byte == 0xaa);
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
CHECK(Setup() == original.size());
|
||||
CHECK(Get() == std::vector<uint8_t>(original.begin(), original.end()));
|
||||
}
|
||||
|
||||
void TestIsolationAndSync() {
|
||||
Reset("ISOLATE");
|
||||
uint8_t byte = 11;
|
||||
CHECK(Setup() == 0);
|
||||
CHECK(Set(&byte, 1) == OK);
|
||||
CHECK(Setup(2) == 0);
|
||||
CHECK(Setup(1, 1) == 0);
|
||||
CHECK(Get(16, 2) == std::vector<uint8_t>(16));
|
||||
CHECK(Get(16, 1, 1) == std::vector<uint8_t>(16));
|
||||
byte = 22;
|
||||
CHECK(Set(&byte, 1, 0, 2) == OK);
|
||||
byte = 33;
|
||||
CHECK(Set(&byte, 1, 0, 1, 1) == OK);
|
||||
CHECK(Get()[0] == 11);
|
||||
CHECK(Get(16, 2)[0] == 22);
|
||||
CHECK(Get(16, 1, 1)[0] == 33);
|
||||
|
||||
SaveDataMemorySync sync {};
|
||||
sync.user_id = 1;
|
||||
sync.slot_id = 1;
|
||||
CHECK(SaveDataSyncSaveDataMemory(&sync) == OK);
|
||||
SaveDataEvent event {};
|
||||
CHECK(SaveDataGetEventResult(nullptr, &event) == OK);
|
||||
CHECK(event.type == 3 && event.error_code == OK && event.user_id == 1);
|
||||
CHECK(std::string(event.dir_name.data) == "sce_sdmemory1");
|
||||
CHECK(SaveDataGetEventResult(nullptr, &event) == SAVE_DATA_ERROR_NOT_FOUND);
|
||||
sync.option = 1;
|
||||
CHECK(SaveDataSyncSaveDataMemory(&sync) == OK);
|
||||
CHECK(SaveDataGetEventResult(nullptr, &event) == SAVE_DATA_ERROR_NOT_FOUND);
|
||||
sync.option = 2;
|
||||
CHECK(SaveDataSyncSaveDataMemory(&sync) == SAVE_DATA_ERROR_PARAMETER);
|
||||
|
||||
Reset("OTHER");
|
||||
CHECK(Setup() == 0);
|
||||
CHECK(Get() == std::vector<uint8_t>(16));
|
||||
Reset("ISOLATE");
|
||||
CHECK(Setup() == 16);
|
||||
CHECK(Setup(2) == 16);
|
||||
CHECK(Setup(1, 1) == 16);
|
||||
CHECK(Get()[0] == 11 && Get(16, 2)[0] == 22 && Get(16, 1, 1)[0] == 33);
|
||||
Reset("OTHER");
|
||||
SceSaveDataTitleId title {};
|
||||
std::strcpy(title.data, "ISOLATE");
|
||||
SceSaveDataDirName name {};
|
||||
std::strcpy(name.data, "sce_sdmemory");
|
||||
struct SaveDataTransferringMount mount {};
|
||||
mount.user_id = 2;
|
||||
mount.title_id = &title;
|
||||
mount.dir_name = &name;
|
||||
SaveDataMountResult result {};
|
||||
CHECK(SaveDataTransferringMount(&mount, &result) == OK);
|
||||
CHECK(g_mounted_directory == fs::path("_SaveData/ISOLATE/sce_sdmemory/2"));
|
||||
CHECK(std::ifstream(g_mounted_directory / "memory.dat", std::ios::binary).get() == 22);
|
||||
CHECK(SaveDataUmount2(0, &result.mount_point) == OK);
|
||||
struct SaveDataDelete del {};
|
||||
del.user_id = 1;
|
||||
del.title_id = &title;
|
||||
del.dir_name = &name;
|
||||
CHECK(SaveDataDelete(&del) == OK);
|
||||
Reset("ISOLATE");
|
||||
CHECK(Setup() == 0);
|
||||
CHECK(Setup(2) == 16);
|
||||
CHECK(Get(16, 2)[0] == 22);
|
||||
}
|
||||
|
||||
void TestFailedWritePreservesSave() {
|
||||
Reset("IOFAIL");
|
||||
CHECK(Setup() == 0);
|
||||
uint8_t byte = 42;
|
||||
CHECK(Set(&byte, 1) == OK);
|
||||
fs::rename("_SaveData", "save-backup");
|
||||
{
|
||||
std::ofstream blocker("_SaveData");
|
||||
blocker << "not a directory";
|
||||
}
|
||||
byte = 99;
|
||||
CHECK(Set(&byte, 1) < 0);
|
||||
CHECK(Get()[0] == 42);
|
||||
CHECK(fs::remove("_SaveData"));
|
||||
fs::rename("save-backup", "_SaveData");
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
CHECK(Setup() == 16);
|
||||
CHECK(Get()[0] == 42);
|
||||
}
|
||||
|
||||
void WriteRestartFixture() {
|
||||
auto setup = SetupParam(7, 3);
|
||||
setup.option = 1;
|
||||
std::array<uint8_t, 4> icon_bytes {1, 2, 3, 4};
|
||||
SaveDataIcon icon {icon_bytes.data(), icon_bytes.size(), icon_bytes.size(), {}};
|
||||
setup.init_icon = &icon;
|
||||
setup.icon_memory_size = icon_bytes.size();
|
||||
SaveDataParam param {};
|
||||
std::strcpy(param.title, "Astro progress");
|
||||
param.user_param = 17;
|
||||
setup.init_param = ¶m;
|
||||
CHECK(SaveDataSetupSaveDataMemory2(&setup, nullptr) == OK);
|
||||
std::array<uint8_t, 4> progress {11, 22, 33, 44};
|
||||
SaveDataMemoryData data {progress.data(), progress.size(), 4, {}};
|
||||
SaveDataMemorySet2 set {};
|
||||
set.user_id = 7;
|
||||
set.slot_id = 3;
|
||||
set.data = &data;
|
||||
set.icon = &icon;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == OK);
|
||||
set.data = nullptr;
|
||||
param.user_param = 18;
|
||||
std::strcpy(param.detail, "Coins, level state, rescued bots");
|
||||
set.param = ¶m;
|
||||
CHECK(SaveDataSetSaveDataMemory2(&set) == OK);
|
||||
// No Sync or Terminate: Set must survive process exit on its own.
|
||||
}
|
||||
|
||||
void ReadRestartFixture() {
|
||||
CHECK(Setup(7, 3, 24, 1) == 16);
|
||||
auto expected = std::vector<uint8_t>(24);
|
||||
expected[4] = 11;
|
||||
expected[5] = 22;
|
||||
expected[6] = 33;
|
||||
expected[7] = 44;
|
||||
CHECK(Get(24, 7, 3) == expected);
|
||||
SaveDataParam param {};
|
||||
std::array<uint8_t, 4> icon_bytes {9, 9, 9, 9};
|
||||
SaveDataIcon icon {icon_bytes.data(), icon_bytes.size(), icon_bytes.size(), {}};
|
||||
SaveDataMemoryGet2 get {};
|
||||
get.user_id = 7;
|
||||
get.slot_id = 3;
|
||||
get.param = ¶m;
|
||||
get.icon = &icon;
|
||||
CHECK(SaveDataGetSaveDataMemory2(&get) == OK);
|
||||
CHECK(icon.data_size == 0);
|
||||
CHECK(
|
||||
std::all_of(icon_bytes.begin(), icon_bytes.end(), [](uint8_t byte) { return byte == 9; }));
|
||||
CHECK(std::string(param.title) == "Astro progress");
|
||||
CHECK(std::string(param.detail) == "Coins, level state, rescued bots");
|
||||
CHECK(param.user_param == 18);
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
CHECK(Setup(7, 3, 6) == 16);
|
||||
expected.resize(6);
|
||||
CHECK(Get(6, 7, 3) == expected);
|
||||
}
|
||||
|
||||
SceSaveDataDirName DirName(const char* text) {
|
||||
SceSaveDataDirName name {};
|
||||
std::snprintf(name.data, sizeof(name.data), "%s", text);
|
||||
return name;
|
||||
}
|
||||
|
||||
std::vector<std::string> Search(int32_t user, const SceSaveDataTitleId* title = nullptr) {
|
||||
SaveDataDirNameSearchCond cond {};
|
||||
cond.user_id = user;
|
||||
cond.title_id = title;
|
||||
std::array<SceSaveDataDirName, 8> names {};
|
||||
SaveDataDirNameSearchResult result {};
|
||||
result.dir_names = names.data();
|
||||
result.dir_names_num = names.size();
|
||||
CHECK(SaveDataDirNameSearch(&cond, &result) == OK);
|
||||
CHECK(result.hit_num == result.set_num && result.set_num <= names.size());
|
||||
std::vector<std::string> found;
|
||||
for (uint32_t i = 0; i < result.set_num; ++i) {
|
||||
found.emplace_back(names[i].data);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
void TestClassicSavePaths() {
|
||||
Reset("CLASSIC");
|
||||
const std::vector<std::string> names {"save.1", "save1", "slot@A", "slotA"};
|
||||
fs::create_directories("_SaveData/CLASSIC/save.1");
|
||||
{
|
||||
std::ofstream saved("_SaveData/CLASSIC/save.1/progress");
|
||||
saved << "save.1";
|
||||
}
|
||||
CHECK(Search(1) == std::vector<std::string> {"save.1"});
|
||||
for (const auto& text: names) {
|
||||
auto name = DirName(text.c_str());
|
||||
struct SaveDataMount3 mount {};
|
||||
mount.user_id = 1;
|
||||
mount.dir_name = &name;
|
||||
mount.mount_mode = text == "save.1" ? 1 : 4;
|
||||
SaveDataMountResult result {};
|
||||
CHECK(SaveDataMount3(&mount, &result) == OK);
|
||||
CHECK(g_mounted_directory == fs::path("_SaveData") / "CLASSIC" / text);
|
||||
if (text != "save.1") {
|
||||
std::ofstream saved(g_mounted_directory / "progress");
|
||||
saved << text;
|
||||
}
|
||||
CHECK(SaveDataUmount2(0, &result.mount_point) == OK);
|
||||
}
|
||||
CHECK(Search(1) == names);
|
||||
SceSaveDataTitleId title {};
|
||||
std::strcpy(title.data, "CLASSIC");
|
||||
Reset("OTHER");
|
||||
CHECK(Search(1).empty());
|
||||
CHECK(Search(1, &title) == names);
|
||||
for (const auto& text: names) {
|
||||
auto name = DirName(text.c_str());
|
||||
struct SaveDataTransferringMount mount {};
|
||||
mount.user_id = 1;
|
||||
mount.title_id = &title;
|
||||
mount.dir_name = &name;
|
||||
SaveDataMountResult result {};
|
||||
CHECK(SaveDataTransferringMount(&mount, &result) == OK);
|
||||
std::ifstream saved(g_mounted_directory / "progress");
|
||||
std::string progress;
|
||||
saved >> progress;
|
||||
CHECK(progress == text);
|
||||
CHECK(SaveDataUmount2(0, &result.mount_point) == OK);
|
||||
}
|
||||
Reset("CLASSIC");
|
||||
for (const char* text: {"", ".", "..", "../save1", "save/1", "save\\1"}) {
|
||||
auto name = DirName(text);
|
||||
struct SaveDataMount3 mount {};
|
||||
mount.user_id = 1;
|
||||
mount.dir_name = &name;
|
||||
mount.mount_mode = 32;
|
||||
SaveDataMountResult result {};
|
||||
g_mounted_directory.clear();
|
||||
CHECK(SaveDataMount3(&mount, &result) == SAVE_DATA_ERROR_PARAMETER);
|
||||
CHECK(g_mounted_directory.empty());
|
||||
struct SaveDataDelete del {};
|
||||
del.user_id = 1;
|
||||
del.dir_name = &name;
|
||||
CHECK(SaveDataDelete(&del) == SAVE_DATA_ERROR_PARAMETER);
|
||||
}
|
||||
CHECK(Search(1) == names);
|
||||
}
|
||||
|
||||
void RunChild(const fs::path& executable, const char* mode) {
|
||||
#ifdef _WIN32
|
||||
CHECK(_spawnl(_P_WAIT, executable.string().c_str(), executable.string().c_str(), mode,
|
||||
static_cast<char*>(nullptr)) == 0);
|
||||
#else
|
||||
const pid_t pid = fork();
|
||||
CHECK(pid >= 0);
|
||||
if (pid == 0) {
|
||||
execl(executable.c_str(), executable.c_str(), mode, static_cast<char*>(nullptr));
|
||||
_exit(127);
|
||||
}
|
||||
int status = 0;
|
||||
CHECK(waitpid(pid, &status, 0) == pid);
|
||||
CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 0);
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc == 2) {
|
||||
if (std::string_view(argv[1]) == "write") {
|
||||
WriteRestartFixture();
|
||||
} else {
|
||||
CHECK(std::string_view(argv[1]) == "read");
|
||||
ReadRestartFixture();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
const auto executable = fs::absolute(argv[0]);
|
||||
const auto previous = fs::current_path();
|
||||
const auto unique = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
const auto temp = fs::temp_directory_path() / ("kyty_save_memory_" + std::to_string(unique));
|
||||
CHECK(fs::create_directories(temp));
|
||||
fs::current_path(temp);
|
||||
RunChild(executable, "write");
|
||||
RunChild(executable, "read");
|
||||
TestValidationAndScatter();
|
||||
TestIsolationAndSync();
|
||||
TestFailedWritePreservesSave();
|
||||
TestClassicSavePaths();
|
||||
CHECK(SaveDataTerminate() == OK);
|
||||
fs::current_path(previous);
|
||||
fs::remove_all(temp);
|
||||
return 0;
|
||||
}
|
||||
@@ -12831,6 +12831,7 @@ public:
|
||||
const uint32_t extent = depth_feedback ? 8 : 32;
|
||||
constexpr uintptr_t depth_address = 0x0000000204400000ull;
|
||||
constexpr uint64_t allocation_size = 0x40000;
|
||||
constexpr uint64_t rect_address = depth_address + 0x8000;
|
||||
EnsureRuntimeContext();
|
||||
RenderContext context(m_runtime_context);
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
@@ -12865,6 +12866,15 @@ public:
|
||||
static_cast<float *>(mapped)[y * 64 + x] = initial_depth(x, y);
|
||||
}
|
||||
}
|
||||
if (!depth_feedback && !packed_vertex_color) {
|
||||
// Rectangle input contains three position/UV records.
|
||||
constexpr std::array<float, 24> rect_vertices{
|
||||
-0.75f, -0.75f, 0, 0, 0, 0, 0, 0,
|
||||
0.75f, -0.75f, 0, 0, 1, 0, 0, 0,
|
||||
-0.75f, 0.75f, 0, 0, 0, 1, 0, 0};
|
||||
std::memcpy(reinterpret_cast<void *>(rect_address), rect_vertices.data(),
|
||||
sizeof(rect_vertices));
|
||||
}
|
||||
resources.MapMemory(depth_address, allocation_size);
|
||||
if (depth_feedback) {
|
||||
depth.desc.type = BindingType::DepthTarget;
|
||||
@@ -13521,6 +13531,82 @@ public:
|
||||
Require(name, "sparse MRT3 output", sparse_pixels[component] == expected,
|
||||
"MRT3 was compacted to a different slot or clipped by unused MRT0");
|
||||
}
|
||||
|
||||
// A fourth VS invocation reads beyond the descriptor instead of reconstructing the corner.
|
||||
const ShaderBufferResource rect_buffer{{
|
||||
static_cast<u32>(rect_address),
|
||||
static_cast<u32>(rect_address >> 32u) | (32u << 16u), 3,
|
||||
DstSel(4, 5, 6, 7) |
|
||||
(static_cast<u32>(Prospero::BufferFormat::k32Float) << 12u)}};
|
||||
static const auto rect_vertex_code = [] {
|
||||
std::vector<u32> code{
|
||||
EncodeMubuf0(0x0d, 0, true, false), EncodeMubuf1(0, 2, 5),
|
||||
EncodeMubuf0(0x0d, 16, true, false), EncodeMubuf1(2, 2, 5)};
|
||||
AppendVMovU32(&code, 4, 0);
|
||||
AppendVMovLiteral(&code, 6, 0x3f800000u);
|
||||
code.insert(code.end(), {EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 4, 6),
|
||||
EncodeExp0(0x20, 0xf), EncodeExp1(2, 3, 4, 6)});
|
||||
AppendEnd(&code);
|
||||
return code;
|
||||
}();
|
||||
static const auto rect_pixel_code = [] {
|
||||
std::vector<u32> code;
|
||||
for (u32 component = 0; component < 2; component++) {
|
||||
code.push_back(EncodeVintrp(0, 20 + component, 0, component, 0));
|
||||
code.push_back(EncodeVintrp(1, 20 + component, 0, component, 1));
|
||||
}
|
||||
AppendVMovU32(&code, 22, 0);
|
||||
AppendVMovLiteral(&code, 23, 0x3f800000u);
|
||||
code.insert(code.end(), {EncodeExp0(0x03, 0xf), EncodeExp1(20, 21, 22, 23)});
|
||||
AppendEnd(&code);
|
||||
return code;
|
||||
}();
|
||||
const auto rect_vs = reinterpret_cast<uint64_t>(rect_vertex_code.data());
|
||||
const auto rect_ps = reinterpret_cast<uint64_t>(rect_pixel_code.data());
|
||||
ShaderMapUserData(rect_vs,
|
||||
{.type = Prospero::ShaderBinaryType::kGs, .user_data = &native_user_data,
|
||||
.code_size_bytes = static_cast<u32>(rect_vertex_code.size() * sizeof(u32))});
|
||||
ShaderMapUserData(rect_ps,
|
||||
{.type = Prospero::ShaderBinaryType::kPs,
|
||||
.code_size_bytes = static_cast<u32>(rect_pixel_code.size() * sizeof(u32))});
|
||||
shaders.SetEsShaderBase(rect_vs);
|
||||
shaders.SetPsShaderBase(rect_ps);
|
||||
shaders.SetGsShaderResource2({.user_sgpr = 4});
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
shaders.SetGsUserSgpr(i, rect_buffer.fields[i], HW::UserSgprType::Vsharp);
|
||||
}
|
||||
registers.SetPsInControl(0x8001);
|
||||
registers.SetPsInputEna(2);
|
||||
registers.SetPsInputAddr(2);
|
||||
registers.SetPsInputSettings(0, 0);
|
||||
for (const auto primitive : {Prospero::PrimitiveType::kRectList,
|
||||
Prospero::PrimitiveType::kRectListLegacy}) {
|
||||
const char *rect_name = primitive == Prospero::PrimitiveType::kRectList
|
||||
? "RectListThreeRecords" : "LegacyRectListThreeRecords";
|
||||
user_config.SetPrimitiveType(primitive);
|
||||
registers.SetModeControl({});
|
||||
TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id,
|
||||
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {});
|
||||
RenderExecutorTestAccess::DrawAuto(
|
||||
executor, scheduler.Current(), {.vertex_count = 3, .instance_count = 1});
|
||||
const auto pixels = ReadCachedTexel(name, context, sparse_color.image_id,
|
||||
{}, {extent, extent, 1});
|
||||
for (u32 y = 0; y < extent; y++) {
|
||||
for (u32 x = 0; x < extent; x++) {
|
||||
const bool inside = x >= 4 && x < 28 && y >= 4 && y < 28;
|
||||
const auto offset = 4 * (y * extent + x);
|
||||
const std::array<float, 4> expected{
|
||||
inside ? (x + 0.5f - 4) / 24 : 0,
|
||||
inside ? (y + 0.5f - 4) / 24 : 0, 0, inside ? 1.0f : 0};
|
||||
for (u32 component = 0; component < 4; component++) {
|
||||
Require(rect_name, "three-vertex rectangle coverage and UV",
|
||||
std::abs(std::bit_cast<float>(pixels[offset + component]) -
|
||||
expected[component]) < 0.0001f,
|
||||
"rectangle reconstruction lost its fourth corner or interpolated UVs");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DestroyBuffer(&stencil_readback);
|
||||
|
||||
vertex_shader = owned_vertex_shader;
|
||||
@@ -15011,6 +15097,7 @@ private:
|
||||
Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true,
|
||||
"vertex layer output is not supported");
|
||||
Require("VulkanHarness", "graphics", available_features.fillModeNonSolid &&
|
||||
available_features.tessellationShader &&
|
||||
available_depth_clip.depthClipEnable && available_clip_control.depthClipControl &&
|
||||
available_color_write.colorWriteEnable &&
|
||||
available_feedback_layout.attachmentFeedbackLoopLayout &&
|
||||
@@ -15076,6 +15163,7 @@ private:
|
||||
device_features.sampleRateShading = true;
|
||||
device_features.shaderInt64 = true;
|
||||
device_features.fillModeNonSolid = true;
|
||||
device_features.tessellationShader = true;
|
||||
device_info.pEnabledFeatures = &device_features;
|
||||
constexpr const char *device_extensions[] = {
|
||||
VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME,
|
||||
@@ -15734,6 +15822,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode,
|
||||
case Opcode::V_CMP_GE_F16:
|
||||
case Opcode::V_CMP_NGT_F16:
|
||||
case Opcode::V_CMP_NEQ_F16:
|
||||
case Opcode::V_CMP_NLT_F16:
|
||||
case Opcode::V_CMPX_LT_F16:
|
||||
case Opcode::V_CMPX_EQ_F16:
|
||||
case Opcode::V_CMPX_LE_F16:
|
||||
@@ -18159,6 +18248,109 @@ TestCase Vop3CvtPkI16I32Captured() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop3MulLoU16CapturedAndSelectors() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
TestCase test;
|
||||
test.name = "Vop3MulLoU16CapturedAndSelectors";
|
||||
// Distinct runtime halves expose source selection and prevent constant folding.
|
||||
test.initial = {0x00138001u, 0x0005ffffu, 0xffffffffu};
|
||||
test.expected = {0x00138001u, 0x0005ffffu, 0xffffffffu,
|
||||
0xa5a57fffu, 0xa5a5ffedu, 0xa5a58005u, 0xa5a5005fu,
|
||||
0x7fff1234u, 0xffed1234u, 0x80051234u, 0x005f1234u,
|
||||
0xffff0001u, 0x00231234u};
|
||||
auto &code = test.code;
|
||||
AppendBufferLoadDword(&code, 12, 30);
|
||||
AppendVMovU32(&code, 30, 4);
|
||||
AppendBufferLoadDword(&code, 24, 30);
|
||||
u32 output = 3;
|
||||
for (u32 selectors : {0u, 1u, 2u, 3u, 8u, 9u, 10u, 11u}) {
|
||||
AppendVMovLiteral(&code, 2, 0xa5a51234u);
|
||||
if (selectors == 8u) {
|
||||
code.push_back(0xd7054002u);
|
||||
code.push_back(0x0002310cu); // Captured v_mul_lo_u16 v2.hi, v12.lo, v24.lo.
|
||||
} else {
|
||||
AppendVop3(&code, 0x305, 2, Vgpr(12), Vgpr(24), 0, 0, selectors);
|
||||
}
|
||||
AppendStoreVgpr(&code, 2, output++);
|
||||
}
|
||||
|
||||
// 0xffff * 0xffff wraps to 1; aliasing preserves the original high half.
|
||||
AppendVMovU32(&code, 30, 8);
|
||||
AppendBufferLoadDword(&code, 12, 30);
|
||||
AppendVop3(&code, 0x305, 12, Vgpr(12), Vgpr(12), 0, 0, 1);
|
||||
AppendStoreVgpr(&code, 12, output++);
|
||||
|
||||
AppendVMovLiteral(&code, 2, 0xa5a51234u);
|
||||
AppendVop3(&code, 0x305, 2, 255, Vgpr(24), 0, 0, 11);
|
||||
code.push_back(0x0007deadu); // Select literal high half: 7 * 5 = 35.
|
||||
AppendStoreVgpr(&code, 2, output);
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MUL_LO_U16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"V_MUL_LO_U16", 10}};
|
||||
test.required_spirv = {"OpIMul"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop3MadI16CapturedSelectorsAndSaturation() {
|
||||
using O = ShaderOpcode;
|
||||
struct MadCase {
|
||||
u32 lhs, rhs, addend, expected;
|
||||
u32 selectors = 0;
|
||||
bool clamp = false;
|
||||
u32 dst = 1;
|
||||
};
|
||||
constexpr u32 a = 0x8000fffdu, b = 0xfffe0005u, c = 0xfff90007u;
|
||||
const std::array<MadCase, 14> cases{{
|
||||
{a, b, c, 0xa5a5fff8u}, // Captured: -3 * 5 + 7 = -8.
|
||||
{a, b, c, 0x8007beefu, 9}, // Source 0 high; destination high.
|
||||
{a, b, c, 0xa5a5000du, 2}, // Source 1 high: -3 * -2 + 7.
|
||||
{a, b, c, 0xa5a5ffeau, 4}, // Source 2 high: -3 * 5 - 7.
|
||||
{a, b, c, 0xfff9beefu, 15}, // All high: -32768 * -2 - 7 wraps.
|
||||
{a, b, c, 0xfff8fffdu, 8, false, 9}, // Destination aliases source 0.
|
||||
{0x7fffu, 1, 1, 0xa5a58000u},
|
||||
{0x7fffu, 1, 1, 0x7fffbeefu, 8, true}, // Positive saturation.
|
||||
{0x8000u, 1, 0xffffu, 0xa5a57fffu},
|
||||
{0x8000u, 1, 0xffffu, 0x8000beefu, 8, true}, // Negative saturation.
|
||||
{0x8000u, 0x8000u, 0xffffu, 0xa5a5ffffu},
|
||||
{0x8000u, 0x8000u, 0xffffu, 0xa5a57fffu, 0, true},
|
||||
{200, 200, 0xd8f0u, 0xa5a57530u, 0, true}, // 40000 - 10000 = 30000.
|
||||
{200, 0xff38u, 10000, 0xa5a58ad0u, 0, true}, // -40000 + 10000 = -30000.
|
||||
}};
|
||||
TestCase test;
|
||||
test.name = "Vop3MadI16CapturedSelectorsAndSaturation";
|
||||
for (const auto &entry : cases) {
|
||||
test.initial.insert(test.initial.end(), {entry.lhs, entry.rhs, entry.addend});
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
for (u32 i = 0; i < cases.size(); ++i) {
|
||||
const auto &entry = cases[i];
|
||||
u32 offset = i * 12u;
|
||||
for (u32 reg : {9u, 6u, 15u}) {
|
||||
AppendVMovU32(&code, 30, offset);
|
||||
AppendBufferLoadDword(&code, reg, 30); // Runtime inputs prevent folding.
|
||||
offset += 4u;
|
||||
}
|
||||
AppendVMovLiteral(&code, 1, 0xa5a5beefu);
|
||||
if (i == 0u) {
|
||||
code.insert(code.end(), {0xd75e0001u, 0x043e0d09u});
|
||||
} else {
|
||||
AppendVop3(&code, 0x35e, entry.dst, Vgpr(9), Vgpr(6), Vgpr(15), 0,
|
||||
entry.selectors, entry.clamp);
|
||||
}
|
||||
AppendStoreVgpr(&code, entry.dst, static_cast<u32>(test.initial.size()) + i);
|
||||
test.expected.push_back(entry.expected);
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAD_I16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"V_MAD_I16", cases.size()}};
|
||||
test.required_spirv = {"OpIMul", "OpIAdd"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase Vop3Med3I16Captured() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -18993,6 +19185,37 @@ TestCase Vop3pOpselHiUsesArchitecturalSourceBits() {
|
||||
{O::V_MOV_B32, O::V_PK_FMA_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase Vop3pIntegerNegationCapturedAndSelectedHalves() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
AppendVMovLiteral(&code, 6, 0xfed4ff38u); // (-200, -300)
|
||||
AppendVMovLiteral(&code, 3, 0x012c00c8u); // (200, 300)
|
||||
// Captured temporal shader: NEG_LO/HI turns 0x7fff into 0xffff (-1).
|
||||
// Adding that clamped offset must select (199,299), not collapse to (0,0).
|
||||
code.insert(code.end(), {0xcc074107u, 0x30020cffu, 0x00007fffu});
|
||||
code.insert(code.end(), {0xcc0a4010u, 0x18020f03u});
|
||||
AppendStoreVgpr(&code, 7, 0);
|
||||
AppendStoreVgpr(&code, 16, 1);
|
||||
|
||||
AppendVMovLiteral(&code, 0, 0x80011234u);
|
||||
AppendVMovLiteral(&code, 1, 0xbeef5678u);
|
||||
// Independently negate src0 low and src1 high, then reverse both the
|
||||
// modified sources and src1 half selection. NEG flips the selected sign bit.
|
||||
AppendVop3p(&code, 0x0a, 10, Vgpr(0), Vgpr(1), 0, 0x3, 0, 0x2, 0x1);
|
||||
AppendVop3p(&code, 0x0a, 11, Vgpr(0), Vgpr(1), 0, 0x1, 0x2, 0x1, 0x2);
|
||||
AppendStoreVgpr(&code, 10, 2);
|
||||
AppendStoreVgpr(&code, 11, 3);
|
||||
AppendEnd(&code);
|
||||
|
||||
return {"Vop3pIntegerNegationCapturedAndSelectedHalves",
|
||||
code,
|
||||
{},
|
||||
{0xffffffffu, 0x012b00c7u, 0xbef0e8acu, 0x56795123u},
|
||||
{O::V_MOV_B32, O::V_PK_MAX_I16, O::V_PK_ADD_U16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM}};
|
||||
}
|
||||
|
||||
TestCase CvtPkU8F32PacksSelectedByte() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -19690,6 +19913,62 @@ TestCase VectorPermlane16FetchInactiveFi() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorDpp8Captured(bool masked_exec) {
|
||||
using O = ShaderOpcode;
|
||||
constexpr u32 sentinel = 0xaaaaaaaau;
|
||||
const u32 masks[] = {masked_exec ? 0x0f0f0f0fu : 0xffffffffu,
|
||||
masked_exec ? 0xf0f0f0f0u : 0xffffffffu};
|
||||
std::vector<u32> code;
|
||||
AppendVMovU32(&code, 5, 100);
|
||||
code.push_back(EncodeVop2(0x25, 5, Vgpr(0), 5));
|
||||
AppendVMovLiteral(&code, 2, sentinel);
|
||||
AppendVMovLiteral(&code, 3, sentinel);
|
||||
AppendSMovLiteral(&code, 126, masks[0]);
|
||||
AppendSMovLiteral(&code, 127, masks[1]);
|
||||
// Captured DPP8 selectors broadcast each group's upper/lower four lanes.
|
||||
code.push_back(EncodeVop1(0x01, 2, 233));
|
||||
code.push_back(0xfacfac05u);
|
||||
code.push_back(EncodeVop1(0x01, 3, 233));
|
||||
code.push_back(0x68868805u);
|
||||
AppendSMovLiteral(&code, 126, 0xffffffffu);
|
||||
AppendSMovLiteral(&code, 127, 0xffffffffu);
|
||||
code.push_back(EncodeVop2(0x1a, 6, InlineU32(2), 0));
|
||||
AppendBufferStoreDword(&code, 2, 6);
|
||||
AppendVMovU32(&code, 7, 64u * sizeof(u32));
|
||||
code.push_back(EncodeVop2(0x25, 6, Vgpr(7), 6));
|
||||
AppendBufferStoreDword(&code, 3, 6);
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = masked_exec ? "VectorDpp8CapturedMaskedExec"
|
||||
: "VectorDpp8Captured";
|
||||
test.code = std::move(code);
|
||||
test.expected.resize(128, sentinel);
|
||||
for (u32 lane = 0; lane < 64; ++lane) {
|
||||
const auto mask = masks[lane / 32u];
|
||||
if ((mask & (1u << (lane % 32u))) == 0) {
|
||||
continue;
|
||||
}
|
||||
const u32 sources[] = {(lane & ~7u) | 4u | (lane & 3u),
|
||||
(lane & ~7u) | (lane & 3u)};
|
||||
for (u32 permutation = 0; permutation < 2; ++permutation) {
|
||||
const auto source = sources[permutation];
|
||||
test.expected[permutation * 64u + lane] =
|
||||
(mask & (1u << (source % 32u))) != 0 ? 100u + source : 0u;
|
||||
}
|
||||
}
|
||||
test.opcodes = {O::V_MOV_B32, O::V_ADD_NC_U32, O::S_MOV_B32,
|
||||
O::V_LSHLREV_B32, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{".dpp8(", 2}};
|
||||
test.compute_info.wave_size = 64;
|
||||
test.compute_info.threads_num[0] = 64;
|
||||
test.compute_info.threads_num[1] = 1;
|
||||
test.compute_info.threads_num[2] = 1;
|
||||
test.compute_info.thread_ids_num = 1;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorDppQuadPermuteReverse() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -21343,6 +21622,88 @@ TestCase VectorVopcCmpNgtF16CapturedSdwaAndEdges() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorVopcCmpNltF16CapturedSdwaAndEdges() {
|
||||
using O = ShaderOpcode;
|
||||
enum Encoding { Captured, Compact, Vop3NegAbs, SdwaNegAbs };
|
||||
struct CompareCase {
|
||||
u32 packed; // Captured instruction compares low half against high half.
|
||||
u32 expected;
|
||||
Encoding encoding = Captured;
|
||||
u32 exec = 1;
|
||||
};
|
||||
const std::array<CompareCase, 16> cases{{
|
||||
{0x40003c00u, 0}, // +1 < +2
|
||||
{0x3c004000u, 1}, // +2 > +1
|
||||
{0x3c003c00u, 1}, // Equal
|
||||
{0x80000000u, 1}, // +0 versus -0
|
||||
{0x00008000u, 1}, // -0 versus +0
|
||||
{0x3c007e00u, 1}, // Quiet NaN in src0: unordered GE, not ordered GE.
|
||||
{0x7e003c00u, 1}, // Quiet NaN in src1
|
||||
{0x3c007c01u, 1}, // Signaling NaN in src0
|
||||
{0x7c013c00u, 1}, // Signaling NaN in src1
|
||||
{0xfc007c00u, 1}, // +infinity versus -infinity
|
||||
{0x7c00fc00u, 0}, // -infinity versus +infinity
|
||||
{0x00003c00u, 0, Captured, 0}, // Inactive lane contributes no mask bit.
|
||||
{0x40003c00u, 0, Compact},
|
||||
{0x7e003c00u, 1, Compact},
|
||||
{0x0000bc00u, 0, Vop3NegAbs}, // -abs(-1) < 0
|
||||
{0x0000bc00u, 0, SdwaNegAbs},
|
||||
}};
|
||||
constexpr u32 sentinel = 0x89abcdefu;
|
||||
TestCase test;
|
||||
test.name = "VectorVopcCmpNltF16CapturedSdwaAndEdges";
|
||||
for (const auto &entry : cases) {
|
||||
test.initial.push_back(entry.packed);
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
code.push_back(EncodeSMovB32(22, 2)); // Preserve the input buffer descriptor.
|
||||
code.push_back(EncodeSMovB32(23, 3));
|
||||
for (u32 i = 0; i < cases.size(); ++i) {
|
||||
const auto &entry = cases[i];
|
||||
AppendVMovU32(&code, 30, i * 4u);
|
||||
AppendBufferLoadDword(&code, 3, 30); // Runtime input prevents constant folding.
|
||||
AppendVMovU32(&code, 1, entry.packed >> 16u);
|
||||
for (u32 reg : {3u, 106u, 107u}) {
|
||||
AppendSMovLiteral(&code, reg, sentinel);
|
||||
}
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(entry.exec)));
|
||||
switch (entry.encoding) {
|
||||
case Captured: code.insert(code.end(), {0x7ddc06f9u, 0x05068203u}); break;
|
||||
case Compact: code.push_back(EncodeVopc(0xee, Vgpr(3), 1)); break;
|
||||
case Vop3NegAbs:
|
||||
AppendVop3(&code, 0xee, 2, Vgpr(3), Vgpr(1), 0, 1, 0, false, 0, 1);
|
||||
break;
|
||||
case SdwaNegAbs:
|
||||
code.insert(code.end(), {0x7ddc06f9u, 0x05368203u});
|
||||
break;
|
||||
}
|
||||
code.push_back(EncodeSMovB32(20, entry.encoding == Compact ? 106 : 2));
|
||||
code.push_back(EncodeSMovB32(21, 3)); // Wave32 must preserve adjacent SGPR.
|
||||
code.push_back(EncodeSMovB32(24, 126));
|
||||
code.push_back(EncodeSMovB32(25, 127));
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
code.push_back(EncodeSMovB32(2, 22));
|
||||
code.push_back(EncodeSMovB32(3, 23));
|
||||
const u32 out = static_cast<u32>(cases.size()) + i * 6u;
|
||||
AppendStoreSgprPair(&code, 20, out);
|
||||
AppendStoreSgprPair(&code, 24, out + 2u);
|
||||
AppendStoreSgprPair(&code, 106, out + 4u);
|
||||
test.expected.insert(test.expected.end(),
|
||||
{entry.expected, sentinel, entry.exec, 0u,
|
||||
entry.encoding == Compact ? entry.expected : sentinel,
|
||||
sentinel});
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_CMP_NLT_F16, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"V_CMP_NLT_F16", cases.size()}};
|
||||
test.required_spirv = {"OpFUnordGreaterThanEqual"};
|
||||
test.compute_info.wave_size = 32;
|
||||
test.has_compute_info = true;
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorVopcCmpxNgtF16CapturedSdwaExecMask() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -26822,6 +27183,8 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(Vop2SdwaLshrrevCapturedByte1Source);
|
||||
AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations);
|
||||
AddCase(Vop3CvtPkI16I32Captured);
|
||||
AddCase(Vop3MulLoU16CapturedAndSelectors);
|
||||
AddCase(Vop3MadI16CapturedSelectorsAndSaturation);
|
||||
AddCase(Vop3Med3I16Captured);
|
||||
AddCase(Vop2SdwaMinU32PreservesWordDestination);
|
||||
AddCase(VectorShiftCountsMaskLowBits);
|
||||
@@ -26845,6 +27208,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(Vop2PkFmacF16AccumulatesPackedHalvesIndependently);
|
||||
AddCase(Vop2PkFmacF16DppNegatesBothPackedHalves);
|
||||
AddCase(Vop3pOpselHiUsesArchitecturalSourceBits);
|
||||
AddCase(Vop3pIntegerNegationCapturedAndSelectedHalves);
|
||||
AddCase(CvtPkU8F32PacksSelectedByte);
|
||||
AddCase(CvtPkrtzF16F32SubnormalRoundsTowardZero);
|
||||
AddCase(CvtPkrtzF16F32SdwaAndOutputModifiers);
|
||||
@@ -26864,6 +27228,8 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(VectorPermlanex16);
|
||||
AddCase(VectorPermlane16FetchInactiveZero);
|
||||
AddCase(VectorPermlane16FetchInactiveFi);
|
||||
cases.push_back(VectorDpp8Captured(false));
|
||||
cases.push_back(VectorDpp8Captured(true));
|
||||
AddCase(VectorDppQuadPermuteReverse);
|
||||
AddCase(VectorDppRowXmask);
|
||||
AddCase(VectorDppBankMaskPreservesDestination);
|
||||
@@ -26910,6 +27276,7 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask);
|
||||
AddCase(VectorVopcCmpxLtU16CapturedSdwaExecMask);
|
||||
AddCase(VectorVopcCmpNgtF16CapturedSdwaAndEdges);
|
||||
AddCase(VectorVopcCmpNltF16CapturedSdwaAndEdges);
|
||||
AddCase(VectorVopcCmpxNgtF16CapturedSdwaExecMask);
|
||||
AddCase(VectorCompareInvertedMaskSelect);
|
||||
AddCase(BranchSelect);
|
||||
@@ -31563,6 +31930,21 @@ int main(int argc, char **argv) {
|
||||
std::setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
EnsureConfigInitialized();
|
||||
CheckLeastRecentlyUsedCacheOrdering();
|
||||
if (argc == 2 && std::strcmp(argv[1], "--packed-integer-neg-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop3pIntegerNegationCapturedAndSelectedHalves());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--dpp-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorDpp8Captured(false));
|
||||
RunCase(&vulkan, VectorDpp8Captured(true));
|
||||
RunCase(&vulkan, VectorDppQuadPermuteReverse());
|
||||
RunCase(&vulkan, VectorDppRowXmask());
|
||||
RunCase(&vulkan, VectorDppBankMaskPreservesDestination());
|
||||
RunCase(&vulkan, VectorDppBoundsControlZeroPreservesDestination());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--buffer-snorm-store-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
|
||||
@@ -31607,6 +31989,11 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, VectorVop3FloatCompareNegSourceModifier());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cmp-nlt-f16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorVopcCmpNltF16CapturedSdwaAndEdges());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--fract-f16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorFractF16CapturedAndEdges());
|
||||
@@ -31808,6 +32195,16 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, Vop3Med3I16Captured());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--mul-lo-u16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop3MulLoU16CapturedAndSelectors());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--mad-i16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, Vop3MadI16CapturedSelectorsAndSaturation());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--waitcnt-depctr-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, ScalarWaitcntDepctrCapturedVmVsrc());
|
||||
|
||||
Reference in New Issue
Block a user