Compare commits

...

16 Commits

Author SHA1 Message Date
Pipe530 d2413fc2eb trophies: add viewer inspector and wrap toast titles 2026-10-07 17:22:44 +04:00
nmzik 96eb681b90 shader: add V_MIN3_U16 opcode 2026-10-07 16:22:30 +04:00
nmzik 26cce9909a shader: add BUFFER_LOAD_FORMAT_D16_X opcode 2026-10-07 16:22:30 +04:00
nmzik 5af370c257 shader: add BUFFER_STORE_FORMAT_D16_X opcode 2026-10-07 16:22:30 +04:00
nmzik 67d7db685a agc: new abi 2026-10-07 16:22:30 +04:00
nmzik bb98f26433 shader: add missing I64/U64 compare variants 2026-10-07 05:03:09 +04:00
dcave14 cc44a43ebc systemservice: add an option to report the notice screen as skipped (#1127)
* systemservice: add an option to report the notice screen as skipped

sceSystemServiceGetNoticeScreenSkipFlag always returned false. Return the
new skip_notice_screen option instead. Games that check this flag skip
their boot logo movies when it is set; Marvel's Wolverine, for example,
then does not queue movies/BootSequence/BootSequence.movie.

The option defaults to off, so behaviour is unchanged unless it is
enabled with --skip-notice-screen true or the launcher's "Skip boot
logos" checkbox. The launcher stores it with the other per-game settings,
so it is included in per-game config import and export.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Shorten skip notice screen wording

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-07 04:21:27 +04:00
nmzik 2a18e66a4c gpu: validate depth copy state when drawing instead of decoding 2026-10-07 04:14:49 +04:00
Henrique Reis Neves e7cfe5a2f7 libkernel: implement sceKernelAioWaitRequests (#1109)
kernel: implement batch AIO completion waits

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-07 04:13:06 +04:00
nmzik 7415febd93 SDL title fix
Co-authored-by: netwarex <mail@pety.me>
2026-10-07 03:15:16 +04:00
NeySlim 7492cb9577 shader: emulate DS_ORDERED_COUNT as a GDS atomic (#1117)
* feat(shader): emulate DS_ORDERED_COUNT as a GDS atomic

* fix(shader): issue one ordered-count atomic per wave

* shader: correct ordered-count addressing and wave results

Use the M0 base and pixel packer offset for the GDS counter. Broadcast the
previous value to every lane and preserve the counter when EXEC is empty.

Add GPU regressions for add/exchange with full, partial, and empty EXEC in
wave32 and wave64. The targeted DS and buffer atomic suite passes 63 cases.

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-07 01:45:37 +04:00
NeySlim 60e53a39cc decoder: accept clamp on V_MIN3/MAX3/MED3_F16 (#1116)
fix(decoder): accept clamp on V_MIN3/MAX3/MED3_F16
2026-10-07 01:24:18 +04:00
Nawid3333 1b21161aac kernel: fix the macOS build of host thread naming (#1122)
* kernel: apply clang-format to pthread.cpp

Output of the repository's pre-commit clang-format hook (v22.1.3) on
this file; no code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* kernel: fix the macOS build of host thread naming

199d0a9d (#1106) names guest threads on the host with the two-argument
glibc pthread_setname_np(thread, name). KYTY_PLATFORM is
KYTY_PLATFORM_LINUX on macOS too, so the macOS job now fails with "no
matching function for call to 'pthread_setname_np'": Apple's version
takes only the name and renames the calling thread. This code already
runs on the new thread itself (it passes pthread_self()), so use the
one-argument form on Apple with Apple's 63-character limit and keep the
Linux call unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Remove formatting

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: nmzik <Nmzik@mail.ru>
2026-10-07 01:18:48 +04:00
NeySlim 1195af2510 decoder: add V_CMPX_U_F32 (#1120)
feat(decoder): add V_CMPX_U_F32
2026-10-07 01:16:23 +04:00
nmzik 9a5658c038 launcher: add per-game config import and export 2026-10-07 01:02:33 +04:00
Oberd 199d0a9d92 kernel: name guest threads on the host (#1106)
PthreadCreate carries a guest thread name, but RunThread never handed it to
the host, so every guest thread showed up as the same generic host thread
name in core dumps, perf samples and htop.

Call pthread_setname_np() on Linux so host-side diagnostics can attribute
work to the guest thread that caused it.
2026-10-06 23:43:36 +04:00
39 changed files with 1143 additions and 248 deletions
+4
View File
@@ -164,6 +164,10 @@ bool PlayGoHackEnabled() {
return g_config->playgo_hack_enabled;
}
bool SkipNoticeScreen() {
return g_config->skip_notice_screen;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled() {
return g_config->red_zone_protection_enabled;
+2
View File
@@ -76,6 +76,7 @@ struct ConfigOptions {
bool tessellation_enabled = false;
bool trophy_enabled = true;
bool playgo_hack_enabled = false;
bool skip_notice_screen = false;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
#endif
@@ -126,6 +127,7 @@ bool ReadbackLinearImagesEnabled();
bool TessellationEnabled();
bool TrophyEnabled();
bool PlayGoHackEnabled();
bool SkipNoticeScreen();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
@@ -184,7 +184,6 @@ static HW::RenderControl DecodeRenderControl(uint32_t value) {
KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_STENCIL_TO_COLOR) != 0;
r.copy_centroid = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_CENTROID) != 0;
r.copy_sample = KYTY_PM4_GET(value, DB_RENDER_CONTROL, COPY_SAMPLE);
EXIT_NOT_IMPLEMENTED(r.copy_depth_to_color || r.copy_stencil_to_color);
return r;
}
+64 -18
View File
@@ -195,6 +195,37 @@ std::mutex g_trophy_notification_mutex;
std::deque<TrophyNotification> g_trophy_notifications;
std::atomic<bool> g_trophy_notification_active {false};
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
std::vector<std::string_view> lines;
auto* const font = ImGui::GetFont();
for (;;) {
const auto newline = text.find('\n');
const auto paragraph = text.substr(0, newline);
auto line = paragraph.substr(0, 0);
for (auto start = paragraph.find_first_not_of(" \t\r"); start != std::string_view::npos;) {
const auto end = std::min(paragraph.find_first_of(" \t\r", start), paragraph.size());
const auto word = paragraph.substr(start, end - start);
const std::string_view candidate(line.empty() ? word.begin() : line.begin(),
word.end());
// Keep an oversized word intact; the toast's clip rectangle handles overflow.
if (!line.empty() && font->CalcTextSizeA(font_size, std::numeric_limits<float>::max(),
0.0f, candidate.data(),
candidate.data() + candidate.size()).x > width) {
lines.push_back(line);
line = word;
} else {
line = candidate;
}
start = paragraph.find_first_not_of(" \t\r", end);
}
lines.push_back(line);
if (newline == std::string_view::npos) {
return lines;
}
text.remove_prefix(newline + 1);
}
}
void ClearInputEvents() {
std::scoped_lock lock(g_input_mutex);
g_input_events.clear();
@@ -1101,7 +1132,26 @@ struct SystemOverlay::Impl {
0.65f, 1.25f);
const float width = std::max(
0.0f, std::min(520.0f * screen_scale, static_cast<float>(frame_extent.width) - 32.0f));
const float height = 104.0f * screen_scale;
constexpr std::array GRADE_COLORS {
IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255), IM_COL32(211, 220, 232, 255),
IM_COL32(211, 142, 91, 255)};
const int grade = trophy_notification->grade;
const bool has_grade = grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size());
const float pad = 10.0f * screen_scale;
const float base_height = 104.0f * screen_scale;
const float icon_size = base_height - 2.0f * pad;
const float text_offset = trophy_notification_image != nullptr
? pad + icon_size + 18.0f * screen_scale
: 20.0f * screen_scale;
const float title_offset = text_offset + (has_grade ? 30.0f * screen_scale : 0.0f);
const float title_size = 26.0f * screen_scale;
const float line_height = 30.0f * screen_scale;
const float text_padding = 16.0f * screen_scale;
const auto title_lines = WrapTrophyTitle(trophy_notification->name, title_size,
width - title_offset - text_padding);
const float block_height =
static_cast<float>(title_lines.size()) * line_height + 28.0f * screen_scale;
const float height = std::max(base_height, block_height + 2.0f * text_padding);
const float rounding = 14.0f * screen_scale;
const float fade_in = std::clamp(age / 0.22f, 0.0f, 1.0f);
const float fade_out = std::clamp((TOAST_LIFETIME - age) / 0.4f, 0.0f, 1.0f);
@@ -1121,26 +1171,19 @@ struct SystemOverlay::Impl {
draw->AddRect(top_left, bottom_right, fade(255, 255, 255, 28), rounding,
ImDrawFlags_RoundCornersAll, 1.0f);
const float pad = 10.0f * screen_scale;
const float icon_size = height - 2.0f * pad;
float text_left = top_left.x + 20.0f * screen_scale;
const float text_left = top_left.x + text_offset;
if (trophy_notification_image != nullptr) {
const ImVec2 icon_min {top_left.x + pad, top_left.y + pad};
const ImVec2 icon_min {top_left.x + pad, top_left.y + (height - icon_size) * 0.5f};
const ImVec2 icon_max {icon_min.x + icon_size, icon_min.y + icon_size};
draw->AddImageRounded(trophy_notification_image->GetTexRef(), icon_min, icon_max,
{0, 0}, {1, 1}, fade(255, 255, 255, 255), 8.0f * screen_scale);
draw->AddRect(icon_min, icon_max, fade(255, 255, 255, 40), 8.0f * screen_scale,
ImDrawFlags_RoundCornersAll, 1.0f);
text_left = icon_max.x + 18.0f * screen_scale;
}
constexpr std::array GRADE_COLORS {IM_COL32(161, 225, 245, 255), IM_COL32(255, 207, 82, 255),
IM_COL32(211, 220, 232, 255),
IM_COL32(211, 142, 91, 255)};
const int grade = trophy_notification->grade;
const float title_y = top_left.y + height * 0.5f - 25.0f * screen_scale;
float title_x = text_left;
if (grade > 0 && grade <= static_cast<int>(GRADE_COLORS.size())) {
const float title_y = top_left.y + (height - block_height) * 0.5f;
const float title_x = top_left.x + title_offset;
if (has_grade) {
// Small cup glyph tinted with the trophy grade.
const ImU32 cup = ImGui::GetColorU32(GRADE_COLORS[grade - 1], alpha);
const float u = screen_scale;
@@ -1155,12 +1198,15 @@ struct SystemOverlay::Impl {
draw->AddRectFilled({cx - 1.5f * u, cy + 2.0f * u}, {cx + 1.5f * u, cy + 7.0f * u}, cup);
draw->AddRectFilled({cx - 5.0f * u, cy + 7.0f * u}, {cx + 5.0f * u, cy + 10.0f * u}, cup,
1.5f * u);
title_x += 30.0f * u;
}
draw->PushClipRect(top_left, {bottom_right.x - 16.0f * screen_scale, bottom_right.y}, true);
draw->AddText(nullptr, 26.0f * screen_scale, {title_x, title_y},
fade(255, 255, 255, 255), trophy_notification->name.c_str());
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, title_y + 38.0f * screen_scale},
draw->PushClipRect(top_left, {bottom_right.x - text_padding, bottom_right.y}, true);
float line_y = title_y;
for (const auto line : title_lines) {
draw->AddText(nullptr, title_size, {title_x, line_y}, fade(255, 255, 255, 255),
line.data(), line.data() + line.size());
line_y += line_height;
}
draw->AddText(nullptr, 20.0f * screen_scale, {text_left, line_y + 8.0f * screen_scale},
fade(176, 180, 192, 255), "Trophy earned!");
draw->PopClipRect();
}
@@ -454,7 +454,7 @@ void HostInputToggleMouseToJoystick() {
LOGF("Mouse to right stick: enabled (F7 to release)\n");
}
bool HostInputWaitEvent(SDL_Event* event) {
bool HostInputWaitEvent(SDL_Event* event, int max_wait_ms) {
int timeout = -1;
if (!g_mouse.enabled || SDL_GetKeyboardFocus() != g_mouse_window) {
g_mouse.next_poll = 0;
@@ -473,6 +473,7 @@ bool HostInputWaitEvent(SDL_Event* event) {
now_ms < g_cursor_hide_at ? static_cast<int>(g_cursor_hide_at - now_ms) : 0;
timeout = timeout < 0 ? cursor_timeout : std::min(timeout, cursor_timeout);
}
timeout = timeout < 0 ? max_wait_ms : std::min(timeout, max_wait_ms);
const bool has_event = SDL_WaitEventTimeout(event, timeout);
if (Config::HideCursorEnabled()) {
+1 -1
View File
@@ -13,7 +13,7 @@ void HostInputShutdown();
void HostInputKey(int key_code, bool down);
void HostInputMouseButton(uint8_t mouse_button, bool down);
void HostInputToggleMouseToJoystick();
[[nodiscard]] bool HostInputWaitEvent(SDL_Event* event);
[[nodiscard]] bool HostInputWaitEvent(SDL_Event* event, int max_wait_ms);
} // namespace Libs::Graphics
@@ -998,7 +998,7 @@ void Presenter::Impl::Present() {
}
presented_overlay_revision.store(overlay_visual.revision, std::memory_order_release);
window.UpdateTitle();
window.loop.presented_frames.fetch_add(1, std::memory_order_relaxed);
return;
}
LOGF("Vulkan presentation retry exhausted; dropping frame\n");
+20 -27
View File
@@ -722,6 +722,10 @@ void WindowContext::Run() {
loop.need_exit = false;
loop.paused.store(false, std::memory_order_release);
constexpr uint64_t title_interval_ms = 1000;
auto title_time = SDL_GetTicks();
auto title_frames = loop.presented_frames.load(std::memory_order_relaxed);
while (!loop.need_exit) {
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
@@ -731,7 +735,20 @@ void WindowContext::Run() {
timer.Resume();
}
if (!HostInputWaitEvent(&loop.event)) {
// Refresh the title on the main thread without making presentation wait for it.
const auto now = SDL_GetTicks();
const auto elapsed = now - title_time;
if (elapsed >= title_interval_ms) {
const auto frames = loop.presented_frames.load(std::memory_order_relaxed);
if (frames != 0) {
UpdateTitle(frames, static_cast<double>(frames - title_frames) * 1000.0 /
static_cast<double>(elapsed));
}
title_time = now;
title_frames = frames;
}
const auto wait_ms = static_cast<int>(title_interval_ms - (now - title_time));
if (!HostInputWaitEvent(&loop.event, wait_ms)) {
continue;
}
ProcessEvent(timer.GetTimeS());
@@ -913,7 +930,7 @@ void WindowContext::UpdateIcon() {
}
}
void WindowContext::UpdateTitle() {
void WindowContext::UpdateTitle(uint64_t frame_num, double current_fps) {
static char title[128];
static char title_id[12];
static char app_ver[12];
@@ -923,10 +940,6 @@ void WindowContext::UpdateTitle() {
static bool has_app_ver =
Loader::SystemContentParamSfoGetString("APP_VER", app_ver, sizeof(app_ver));
static const std::string processor_name = Common::GetSystemInfo().ProcessorName;
static uint64_t fps_start = Common::Timer::QueryPerformanceCounter();
static uint64_t frame_num = 0;
static uint64_t fps_frames = 0;
static double current_fps = 0.0;
#if KYTY_BUILD == KYTY_BUILD_DEBUG
static constexpr auto build_type = "Debug";
@@ -936,17 +949,6 @@ void WindowContext::UpdateTitle() {
static constexpr auto build_type = "Unknown";
#endif
const auto now = Common::Timer::QueryPerformanceCounter();
const auto frequency = Common::Timer::QueryPerformanceFrequency();
frame_num++;
fps_frames++;
if (now - fps_start >= frequency) {
current_fps = static_cast<double>(fps_frames) * static_cast<double>(frequency) /
static_cast<double>(now - fps_start);
fps_start = now;
fps_frames = 0;
}
const auto* device_name = graphic_ctx.GetPhysicalDeviceProperties().deviceName.data();
auto text = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:.0f}", KYTY_BUILD_LABEL, build_type,
@@ -954,16 +956,7 @@ void WindowContext::UpdateTitle() {
(has_title_id ? ", " : ""), (has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""),
device_name, processor_name, frame_num, current_fps);
struct TitleUpdate {
SDL_Window* window;
std::string* text;
} update {window, &text};
EXIT_IF(!SDL_RunOnMainThread(
[](void* data) {
auto& title = *static_cast<TitleUpdate*>(data);
SDL_SetWindowTitle(title.window, title.text->c_str());
},
&update, true));
SDL_SetWindowTitle(window, text.c_str());
}
} // namespace Libs::Graphics
@@ -27,6 +27,7 @@ struct WindowLoopState {
SDL_Event event {};
bool need_exit = false;
std::atomic_bool paused = false;
std::atomic<uint64_t> presented_frames {0};
};
struct WindowContext {
@@ -42,7 +43,7 @@ struct WindowContext {
void RecreateSurface();
void RefreshSurfaceCapabilities();
void UpdateIcon();
void UpdateTitle();
void UpdateTitle(uint64_t frame_num, double current_fps);
void Resize(int width, int height);
void ProcessWindowEvent(const SDL_WindowEvent& event);
void ProcessDisplayEvent(const SDL_DisplayEvent& event);
@@ -716,18 +716,38 @@ uint32_t FormattedLoad(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Me
return LoadWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource);
}
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info);
return EmitValueOrDefaultIfCondition(
const auto value = EmitValueOrDefaultIfCondition(
ctx.state, plan.in_bounds, TypeU32(ctx.state),
FormattedOutOfBoundsValue(ctx, mem, plan, 1u),
[&]() { return LoadFormattedInBounds(ctx, mem, plan, 0u); });
if (mem.data_bits != 16u) {
return value;
}
if (info.type == Format::ComponentType::Uint) {
return EmitUMin32(ctx.state, value, ConstantU32(ctx.state, 0xffffu));
}
if (info.type == Format::ComponentType::Sint) {
const auto lower = EmitSMax32(ctx.state, value, ConstantU32(ctx.state, 0xffff8000u));
return EmitSMin32(ctx.state, lower, ConstantU32(ctx.state, 0x7fffu));
}
return EmitConvertF16F32(ctx.state, EmitBitCastF32U32(ctx.state, value));
});
}
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
const Format::BufferFormatInfo& info,
uint32_t component, uint32_t data) {
uint32_t component, uint32_t data, uint32_t data_bits) {
auto& state = ctx.state;
const auto bits = info.component_bits[component];
if (data_bits == 16u) {
if (info.type == Format::ComponentType::Uint) {
data = EmitBitFieldUExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
} else if (info.type == Format::ComponentType::Sint) {
data = EmitBitFieldSExtract(state, data, ConstantU32(state, 0), ConstantU32(state, 16));
} else {
data = EmitBitCastU32F32(state, EmitF16BitsToF32(state, data));
}
}
const bool normalized = info.type == Format::ComponentType::Unorm ||
info.type == Format::ComponentType::Snorm;
const bool scaled = info.type == Format::ComponentType::Uscaled ||
@@ -779,7 +799,7 @@ void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
value, data, component);
}
}
value = EncodeFormattedStoreComponent(ctx, info, component, value);
value = EncodeFormattedStoreComponent(ctx, info, component, value, mem.data_bits);
const auto bits = info.component_bits[component];
if (info.packed_bitfield) {
packed = EmitBitFieldInsert(
@@ -63,6 +63,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = {
{0x50u, Opcode::BUFFER_ATOMIC_SWAP_X2, 2, 32},
{0x59u, Opcode::BUFFER_ATOMIC_AND_X2, 2, 32},
{0x5au, Opcode::BUFFER_ATOMIC_OR_X2, 2, 32},
{0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true},
{0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true},
};
constexpr MemoryOpcodeInfo MTBUF_OPCODE_LIST[] = {
@@ -109,6 +111,7 @@ constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = {
{0x39u, Opcode::DS_READ_I8, 1, 8, true}, {0x3au, Opcode::DS_READ_U8, 1, 8},
{0x3bu, Opcode::DS_READ_I16, 1, 16, true}, {0x3cu, Opcode::DS_READ_U16, 1, 16},
{0x3du, Opcode::DS_CONSUME, 1, 32}, {0x3eu, Opcode::DS_APPEND, 1, 32},
{0x3fu, Opcode::DS_ORDERED_COUNT, 1, 32},
{0x40u, Opcode::DS_ADD_U64, 2, 32}, {0x4au, Opcode::DS_OR_B64, 2, 32},
{0x4du, Opcode::DS_WRITE_B64, 2, 32}, {0x4eu, Opcode::DS_WRITE2_B64, 4, 32},
{0x4fu, Opcode::DS_WRITE2ST64_B64, 4, 32}, {0x76u, Opcode::DS_READ_B64, 2, 32},
@@ -214,6 +217,7 @@ uint32_t DsSourceCount(Opcode opcode) {
case Opcode::DS_READ_ADDTID_B32:
case Opcode::DS_CONSUME:
case Opcode::DS_APPEND: return 0u;
case Opcode::DS_ORDERED_COUNT: return 1u;
default: return IsDsWriteOpcode(opcode) || IsDsAtomicOpcode(opcode) ? 2u : 1u;
}
}
@@ -294,6 +298,9 @@ void DecodeMubuf(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
}
DecodeVectorGpr(vdata, inst.dst);
if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X) {
inst.dst.sdwa_sel = 4u;
}
DecodeVectorGpr(vaddr, inst.src0);
DecodeScalarSource(srsrc * 4u, pc, inst.src1);
DecodeScalarSource(soffset, pc, inst.src2);
@@ -412,6 +419,14 @@ void DecodeDs(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
if (inst.opcode == Opcode::UNSUPPORTED) {
SetUnsupported(inst, Family::DS, opcode, "DS opcode is not implemented");
}
if (inst.opcode == Opcode::DS_ORDERED_COUNT) {
// Keep OFFSET1's wave type as well as its add/swap selector for GDS addressing.
inst.offset = offset0 & 0xfcu;
inst.secondary_offset = offset1;
if (!inst.gds || ((offset1 >> 4u) & 3u) > 1u || ((offset1 >> 6u) & 3u) != 0u) {
SetUnsupported(inst, Family::DS, opcode, "DS ordered count variant is not implemented");
}
}
if (inst.opcode == Opcode::DS_SWIZZLE_B32 && inst.offset >= 0xe000u) {
SetUnsupported(inst, Family::DS, opcode, "DS swizzle FFT mode is not implemented");
}
@@ -618,10 +618,12 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BUFFER_LOAD_FORMAT_XY:
case Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Opcode::BUFFER_STORE_FORMAT_X:
case Opcode::BUFFER_STORE_FORMAT_XY:
case Opcode::BUFFER_STORE_FORMAT_XYZ:
case Opcode::BUFFER_STORE_FORMAT_XYZW:
case Opcode::BUFFER_STORE_FORMAT_D16_X:
case Opcode::BUFFER_LOAD_UBYTE:
case Opcode::BUFFER_LOAD_USHORT:
case Opcode::BUFFER_LOAD_DWORD:
@@ -269,6 +269,7 @@ enum class Opcode {
V_MIN3_I32,
V_MIN3_U32,
V_MIN3_F16,
V_MIN3_U16,
V_MAX3_F32,
V_MAX3_I32,
V_MAX3_U32,
@@ -382,6 +383,7 @@ enum class Opcode {
V_CMPX_LG_F32,
V_CMPX_GE_F32,
V_CMPX_O_F32,
V_CMPX_U_F32,
V_CMPX_NGE_F32,
V_CMPX_NLG_F32,
V_CMPX_NGT_F32,
@@ -454,19 +456,34 @@ enum class Opcode {
V_CMP_NE_U32,
V_CMP_GE_U32,
V_CMP_T_U32,
V_CMP_F_I64,
V_CMP_EQ_I64,
V_CMP_LT_I64,
V_CMP_LE_I64,
V_CMP_NE_I64,
V_CMP_T_I64,
V_CMP_F_U64,
V_CMP_LT_U64,
V_CMP_EQ_U64,
V_CMP_LE_U64,
V_CMP_GT_U64,
V_CMP_NE_U64,
V_CMP_GE_U64,
V_CMP_T_U64,
V_CMPX_F_I64,
V_CMPX_LT_I64,
V_CMPX_EQ_I64,
V_CMPX_LE_I64,
V_CMPX_NE_I64,
V_CMPX_T_I64,
V_CMPX_F_U64,
V_CMPX_LT_U64,
V_CMPX_EQ_U64,
V_CMPX_LE_U64,
V_CMPX_GT_U64,
V_CMPX_NE_U64,
V_CMPX_GE_U64,
V_CMPX_T_U64,
V_CMPX_LT_U32,
V_CMPX_EQ_U32,
V_CMPX_LE_U32,
@@ -489,10 +506,12 @@ enum class Opcode {
BUFFER_LOAD_FORMAT_XY,
BUFFER_LOAD_FORMAT_XYZ,
BUFFER_LOAD_FORMAT_XYZW,
BUFFER_LOAD_FORMAT_D16_X,
BUFFER_STORE_FORMAT_X,
BUFFER_STORE_FORMAT_XY,
BUFFER_STORE_FORMAT_XYZ,
BUFFER_STORE_FORMAT_XYZW,
BUFFER_STORE_FORMAT_D16_X,
BUFFER_LOAD_UBYTE,
BUFFER_LOAD_SBYTE,
BUFFER_LOAD_USHORT,
@@ -579,6 +598,7 @@ enum class Opcode {
DS_BPERMUTE_B32,
DS_CONSUME,
DS_APPEND,
DS_ORDERED_COUNT,
DS_READ_I8,
DS_READ_U8,
DS_READ_I16,
@@ -232,7 +232,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0x11u, Opcode::V_CMPX_LT_F32}, {0x12u, Opcode::V_CMPX_EQ_F32},
{0x13u, Opcode::V_CMPX_LE_F32}, {0x14u, Opcode::V_CMPX_GT_F32},
{0x15u, Opcode::V_CMPX_LG_F32}, {0x16u, Opcode::V_CMPX_GE_F32},
{0x17u, Opcode::V_CMPX_O_F32},
{0x17u, Opcode::V_CMPX_O_F32}, {0x18u, Opcode::V_CMPX_U_F32},
{0x19u, Opcode::V_CMPX_NGE_F32}, {0x1au, Opcode::V_CMPX_NLG_F32},
{0x1bu, Opcode::V_CMPX_NGT_F32}, {0x1cu, Opcode::V_CMPX_NLE_F32},
{0x1du, Opcode::V_CMPX_NEQ_F32}, {0x1eu, Opcode::V_CMPX_NLT_F32},
@@ -265,10 +265,18 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
{0xc7u, Opcode::V_CMP_T_U32}, {0xa2u, Opcode::V_CMP_EQ_I64, false},
{0xa1u, Opcode::V_CMP_LT_I64, false}, {0xa3u, Opcode::V_CMP_LE_I64, false},
{0xa5u, Opcode::V_CMP_NE_I64, false},
{0xa0u, Opcode::V_CMP_F_I64, false}, {0xa7u, Opcode::V_CMP_T_I64, false},
{0xb0u, Opcode::V_CMPX_F_I64, false}, {0xb1u, Opcode::V_CMPX_LT_I64, false},
{0xb2u, Opcode::V_CMPX_EQ_I64, false}, {0xb3u, Opcode::V_CMPX_LE_I64, false},
{0xb7u, Opcode::V_CMPX_T_I64, false},
{0xb5u, Opcode::V_CMPX_NE_I64, false}, {0xd1u, Opcode::V_CMPX_LT_U32},
{0xd2u, Opcode::V_CMPX_EQ_U32}, {0xd3u, Opcode::V_CMPX_LE_U32},
{0xd4u, Opcode::V_CMPX_GT_U32}, {0xd5u, Opcode::V_CMPX_NE_U32},
{0xd6u, Opcode::V_CMPX_GE_U32}, {0xe1u, Opcode::V_CMP_LT_U64, false},
{0xe0u, Opcode::V_CMP_F_U64, false}, {0xe7u, Opcode::V_CMP_T_U64, false},
{0xf0u, Opcode::V_CMPX_F_U64, false}, {0xf1u, Opcode::V_CMPX_LT_U64, false},
{0xf2u, Opcode::V_CMPX_EQ_U64, false}, {0xf4u, Opcode::V_CMPX_GT_U64, false},
{0xf6u, Opcode::V_CMPX_GE_U64, false}, {0xf7u, Opcode::V_CMPX_T_U64, false},
{0xe2u, Opcode::V_CMP_EQ_U64, false}, {0xe3u, Opcode::V_CMP_LE_U64, false},
{0xe4u, Opcode::V_CMP_GT_U64, false}, {0xe5u, Opcode::V_CMP_NE_U64, false},
{0xe6u, Opcode::V_CMP_GE_U64, false}, {0xf3u, Opcode::V_CMPX_LE_U64, false},
@@ -310,6 +318,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x152u, Opcode::V_MIN3_I32},
{0x153u, Opcode::V_MIN3_U32},
{0x351u, Opcode::V_MIN3_F16},
{0x353u, Opcode::V_MIN3_U16},
{0x154u, Opcode::V_MAX3_F32},
{0x155u, Opcode::V_MAX3_I32},
{0x156u, Opcode::V_MAX3_U32},
@@ -460,8 +469,9 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
opcode == Opcode::V_MED3_F16 || opcode == Opcode::V_FMA_F16;
}
bool IsNativeVop3I16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16;
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 ||
opcode == Opcode::V_MAD_I16;
}
bool IsNativeVop3B16BinaryOpcode(Opcode opcode) {
@@ -892,6 +902,7 @@ bool IsVopcFloatCompareOpcode(Opcode opcode) {
case Opcode::V_CMPX_LG_F32:
case Opcode::V_CMPX_GE_F32:
case Opcode::V_CMPX_O_F32:
case Opcode::V_CMPX_U_F32:
case Opcode::V_CMPX_NGE_F32:
case Opcode::V_CMPX_NLG_F32:
case Opcode::V_CMPX_NGT_F32:
@@ -1366,7 +1377,7 @@ void ApplyNativeVop3TernaryModifiers(Instruction& inst, uint32_t op_sel, uint32_
inst.dst.sdwa_sel = ((op_sel & 0x8u) != 0) ? 5u : 4u;
}
void ApplyNativeVop3I16TernarySelectors(Instruction& inst, uint32_t op_sel) {
void ApplyNativeVop3B16TernarySelectors(Instruction& inst, uint32_t op_sel) {
Operand* sources[] = {&inst.src0, &inst.src1, &inst.src2};
for (uint32_t i = 0; i < 3u; i++) {
sources[i]->op_sel = ((op_sel >> i) & 1u) != 0;
@@ -1449,6 +1460,9 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
case Opcode::V_MAD_F32:
case Opcode::V_FMA_F32:
case Opcode::V_FMA_F16:
case Opcode::V_MIN3_F16:
case Opcode::V_MAX3_F16:
case Opcode::V_MED3_F16:
case Opcode::V_CUBEID_F32:
case Opcode::V_CVT_PKRTZ_F16_F32:
case Opcode::V_LDEXP_F32:
@@ -1479,9 +1493,10 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
return clamp != 0u || omod != 0u;
}
if (IsNativeVop3F16TernaryOpcode(opcode)) {
return opcode != Opcode::V_FMA_F16 && (clamp != 0u || omod != 0u);
// Clamp is applied by WriteF16 for every f16 ternary; omod only for V_FMA_F16.
return opcode != Opcode::V_FMA_F16 && omod != 0u;
}
if (IsNativeVop3I16TernaryOpcode(opcode)) {
if (IsNativeVop3B16TernaryOpcode(opcode)) {
return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u;
}
if (IsNativeVop3B16BinaryOpcode(opcode)) {
@@ -1529,6 +1544,7 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_LG_F32:
case Opcode::V_CMPX_GE_F32:
case Opcode::V_CMPX_O_F32:
case Opcode::V_CMPX_U_F32:
case Opcode::V_CMPX_NGE_F32:
case Opcode::V_CMPX_NLG_F32:
case Opcode::V_CMPX_NGT_F32:
@@ -1549,9 +1565,20 @@ bool IsVopcCompareExec(Opcode opcode) {
case Opcode::V_CMPX_GT_U32:
case Opcode::V_CMPX_NE_U32:
case Opcode::V_CMPX_GE_U32:
case Opcode::V_CMPX_F_I64:
case Opcode::V_CMPX_LT_I64:
case Opcode::V_CMPX_EQ_I64:
case Opcode::V_CMPX_LE_I64:
case Opcode::V_CMPX_NE_I64:
case Opcode::V_CMPX_T_I64:
case Opcode::V_CMPX_F_U64:
case Opcode::V_CMPX_LT_U64:
case Opcode::V_CMPX_EQ_U64:
case Opcode::V_CMPX_LE_U64:
case Opcode::V_CMPX_GT_U64:
case Opcode::V_CMPX_NE_U64:
case Opcode::V_CMPX_GE_U64:
case Opcode::V_CMPX_T_U64:
case Opcode::V_CMPX_LT_I16:
case Opcode::V_CMPX_EQ_I16:
case Opcode::V_CMPX_LE_I16:
@@ -1706,7 +1733,7 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
const bool vop3b_uses_sdst = carry_in_out || vop3b_carry_out || vop3b_mad_u64;
const bool mad_mix = false;
const bool f16_ternary = IsNativeVop3F16TernaryOpcode(inst.opcode);
const bool i16_ternary = IsNativeVop3I16TernaryOpcode(inst.opcode);
const bool b16_ternary = IsNativeVop3B16TernaryOpcode(inst.opcode);
const bool b16_binary = IsNativeVop3B16BinaryOpcode(inst.opcode);
const bool pack_b32_f16 = inst.opcode == Opcode::V_PACK_B32_F16;
const bool permlane = IsPermlaneOpcode(inst.opcode);
@@ -1817,8 +1844,8 @@ void DecodeVop3(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
} else if (f16_ternary) {
ApplyNativeVop3TernaryModifiers(inst, op_sel, abs, neg);
} else if (i16_ternary) {
ApplyNativeVop3I16TernarySelectors(inst, op_sel);
} else if (b16_ternary) {
ApplyNativeVop3B16TernarySelectors(inst, op_sel);
} else if (b16_binary) {
ApplyNativeVop3B16BinaryModifiers(inst, op_sel);
} else if (pack_b32_f16) {
@@ -31,6 +31,7 @@ void Translator::EmitIntegerCompare(const Decoder::Instruction& inst, IR::ValueO
const bool signed_64 = opcode == IR::ValueOpcode::SLessThan64 ||
opcode == IR::ValueOpcode::SLessThanEqual64 ||
inst.opcode == Decoder::Opcode::V_CMP_EQ_I64 ||
inst.opcode == Decoder::Opcode::V_CMPX_EQ_I64 ||
inst.opcode == Decoder::Opcode::V_CMP_NE_I64 ||
inst.opcode == Decoder::Opcode::V_CMPX_NE_I64;
const auto read = [&](const Decoder::Operand& operand) {
@@ -31,11 +31,12 @@ void Translator::V_MAD_I16(const Decoder::Instruction& inst) {
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
}
void Translator::V_MED3_I16(const Decoder::Instruction& inst) {
void Translator::Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool sign) {
const auto result = IR::U32(ir.Emit(
IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true),
ReadU16AsU32(inst.src2, true)}));
Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu))));
opcode, {ReadU16AsU32(inst.src0, sign), ReadU16AsU32(inst.src1, sign),
ReadU16AsU32(inst.src2, sign)}));
Write16Bits(DestinationOperand(inst), result);
}
void Translator::PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
@@ -187,7 +187,7 @@ Decoder::Operand MemorySourceAt(const Decoder::Instruction& decoded, uint32_t in
if (decoded.family == Decoder::Family::MUBUF || decoded.family == Decoder::Family::MTBUF) {
const bool store_or_atomic =
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_FORMAT_X &&
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW) ||
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X) ||
(decoded.opcode >= Decoder::Opcode::BUFFER_STORE_BYTE &&
decoded.opcode <= Decoder::Opcode::BUFFER_STORE_DWORDX4) ||
(decoded.opcode >= Decoder::Opcode::TBUFFER_STORE_FORMAT_X &&
@@ -427,7 +427,7 @@ void Translator::S_LOAD(const Decoder::Instruction& inst, bool raw) {
void Translator::BUFFER_LOAD(const Decoder::Instruction& inst) {
const auto memory = MemoryInfoFromDecoded(inst);
IR::ValueOpcode opcode;
const auto bits = memory.data_bits;
const auto bits = memory.formatted ? 32u : memory.data_bits;
const auto sign = memory.data_signed;
switch (bits) {
case 8u: opcode = IR::ValueOpcode::LoadBufferU8; break;
@@ -473,7 +473,8 @@ void Translator::BUFFER_STORE(const Decoder::Instruction& inst) {
const auto data = ReadU32(data_src);
IR::ValueOpcode opcode;
IR::Value value;
switch (memory.data_bits) {
// Formatted stores carry packed VGPR bits; the format determines their conversion.
switch (memory.formatted ? 32u : memory.data_bits) {
case 8u:
opcode = IR::ValueOpcode::StoreBufferU8;
value = NarrowSubdword(data, 8u);
@@ -883,6 +884,50 @@ void Translator::DS_SWIZZLE_B32(const Decoder::Instruction& inst) {
ReadU32(MemorySourceAt(inst, 1)), ir.GetExec()}));
}
void Translator::DS_ORDERED_COUNT(const Decoder::Instruction& inst) {
// Approximate ordered counting with one GDS atomic per wave. Wave-launch ordering and
// release/done synchronization are not emulated.
const auto memory = MemoryInfoFromDecoded(inst);
const auto m0 = ir.GetM0();
auto address = ir.BitwiseAnd(ir.ShiftRightLogical(m0, IR::U32(IR::Value(16u))),
IR::U32(IR::Value(0xfffcu)));
if (((inst.secondary_offset >> 2u) & 3u) == 1u) {
const auto packer = ir.BitwiseAnd(m0, IR::U32(IR::Value(0xfu)));
address = ir.IAdd(address, ir.ShiftLeftLogical(packer, IR::U32(IR::Value(2u))));
}
const auto exec = ir.GetExec();
const auto ballot = ir.Emit(IR::ValueOpcode::Ballot, {exec});
const auto low = ir.CompositeExtract(ballot, 0u);
const auto high = ir.CompositeExtract(ballot, 1u);
const auto active = ir.INotEqual(ir.BitwiseOr(low, high), IR::U32(IR::Value(0u)));
const auto first = ir.Select(
ir.INotEqual(low, IR::U32(IR::Value(0u))),
IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {low})),
ir.IAdd(IR::U32(IR::Value(32u)), IR::U32(ir.Emit(IR::ValueOpcode::FindILsb32, {high}))));
// An empty EXEC still returns the counter. Elect lane zero and perform an add of zero.
const auto selected = ir.Select(active, first, IR::U32(IR::Value(0u)));
const auto lane = IR::U32(ir.Emit(IR::ValueOpcode::LaneId));
const auto is_first = ir.IEqual(lane, selected);
const auto source = IR::U32(ir.Emit(IR::ValueOpcode::ReadFirstLane,
{ReadU32(inst.src0), is_first}));
const auto value = ir.Select(active, source, IR::U32(IR::Value(0u)));
const auto flags = AddMemoryInfo(memory, inst.pc);
IR::U32 result;
if (((inst.secondary_offset >> 4u) & 3u) == 1u) {
const auto swapped = IR::U32(ir.Emit(IR::ValueOpcode::SharedAtomicSwap32,
{address, value, ir.LogicalAnd(is_first, active)}, flags));
const auto unchanged = IR::U32(ir.Emit(IR::ValueOpcode::SharedAtomicIAdd32,
{address, IR::Value(0u), ir.LogicalAnd(is_first, ir.LogicalNot(active))}, flags));
result = ir.Select(active, swapped, unchanged);
} else {
result = IR::U32(ir.Emit(IR::ValueOpcode::SharedAtomicIAdd32,
{address, value, is_first}, flags));
}
// DS_ORDERED_COUNT writes every destination lane, regardless of EXEC.
ir.SetVectorReg(static_cast<IR::VectorReg>(inst.dst.reg),
IR::U32(ir.Emit(IR::ValueOpcode::ReadFirstLane, {result, is_first})));
}
void Translator::DS_PERMUTE(const Decoder::Instruction& inst, bool backward) {
const auto address = ir.IAdd(ReadU32(inst.src0), IR::U32(IR::Value(inst.offset)));
WriteOperand(inst.dst, ir.Emit(backward ? IR::ValueOpcode::BpermuteU32
@@ -936,6 +981,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XY:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY:
case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XYZ:
@@ -951,6 +997,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::BUFFER_STORE_FORMAT_XY:
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZ:
case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW:
case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_X:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY:
case Decoder::Opcode::TBUFFER_STORE_FORMAT_XYZ:
@@ -1114,6 +1161,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) {
case Decoder::Opcode::DS_SWIZZLE_B32: return DS_SWIZZLE_B32(inst);
case Decoder::Opcode::DS_PERMUTE_B32: return DS_PERMUTE(inst, false);
case Decoder::Opcode::DS_BPERMUTE_B32: return DS_PERMUTE(inst, true);
case Decoder::Opcode::DS_ORDERED_COUNT: return DS_ORDERED_COUNT(inst);
case Decoder::Opcode::DS_CONSUME:
return DS_APPEND_CONSUME(inst, IR::ValueOpcode::DataConsume);
case Decoder::Opcode::DS_APPEND:
@@ -889,9 +889,16 @@ void IncludeInstructionVectorRegisters(const Decoder::Instruction& inst, uint32_
case Decoder::Opcode::V_CMP_GT_U64:
case Decoder::Opcode::V_CMP_NE_U64:
case Decoder::Opcode::V_CMP_GE_U64:
case Decoder::Opcode::V_CMPX_LT_I64:
case Decoder::Opcode::V_CMPX_EQ_I64:
case Decoder::Opcode::V_CMPX_LE_I64:
case Decoder::Opcode::V_CMPX_NE_I64:
case Decoder::Opcode::V_CMPX_LT_U64:
case Decoder::Opcode::V_CMPX_EQ_U64:
case Decoder::Opcode::V_CMPX_LE_U64:
case Decoder::Opcode::V_CMPX_GT_U64:
case Decoder::Opcode::V_CMPX_NE_U64:
case Decoder::Opcode::V_CMPX_GE_U64:
include_vector(inst.src0, 2u);
include_vector(inst.src1, 2u);
break;
@@ -109,6 +109,7 @@ private:
void DS_ADDTID(const Decoder::Instruction& inst, bool write);
void DS_SWIZZLE_B32(const Decoder::Instruction& inst);
void DS_PERMUTE(const Decoder::Instruction& inst, bool backward);
void DS_ORDERED_COUNT(const Decoder::Instruction& inst);
IR::F32 SelectF32(IR::U1 condition, IR::F32 true_value, IR::F32 false_value);
IR::U32 ConvertF32ToU32Saturated(IR::F32 value, float upper_bound, float safe_upper,
@@ -165,8 +166,8 @@ private:
void FloatCube(const Decoder::Instruction& inst, uint32_t result_kind);
void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic);
void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign);
void V_MAD_I16(const Decoder::Instruction& inst);
void V_MED3_I16(const Decoder::Instruction& inst);
void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode,
bool arithmetic);
void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode);
@@ -96,16 +96,34 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMPX_LE_I32:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual32, IR::Type::U32, false, true);
return;
case O::V_CMP_F_I64:
case O::V_CMP_F_U64: EmitCompareConstant(inst, false, false, false); return;
case O::V_CMP_T_I64:
case O::V_CMP_T_U64: EmitCompareConstant(inst, true, false, false); return;
case O::V_CMPX_F_I64:
case O::V_CMPX_F_U64: EmitCompareConstant(inst, false, false, true); return;
case O::V_CMPX_T_I64:
case O::V_CMPX_T_U64: EmitCompareConstant(inst, true, false, true); return;
case O::V_CMP_EQ_I64:
case O::V_CMP_EQ_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, false);
return;
case O::V_CMPX_EQ_I64:
case O::V_CMPX_EQ_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::IEqual64, IR::Type::U64, false, true);
return;
case O::V_CMP_LT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, false);
return;
case O::V_CMPX_LT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThan64, IR::Type::U64, false, true);
return;
case O::V_CMP_GT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, false);
return;
case O::V_CMPX_GT_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThan64, IR::Type::U64, false, true);
return;
case O::V_CMP_NE_I64:
case O::V_CMP_NE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::INotEqual64, IR::Type::U64, false, false);
@@ -118,9 +136,15 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMP_LT_I64:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, false);
return;
case O::V_CMPX_LT_I64:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThan64, IR::Type::U64, false, true);
return;
case O::V_CMP_LE_I64:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, false);
return;
case O::V_CMPX_LE_I64:
EmitIntegerCompare(inst, IR::ValueOpcode::SLessThanEqual64, IR::Type::U64, false, true);
return;
case O::V_CMP_LE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::ULessThanEqual64, IR::Type::U64, false, false);
return;
@@ -130,6 +154,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMP_GE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, false);
return;
case O::V_CMPX_GE_U64:
EmitIntegerCompare(inst, IR::ValueOpcode::UGreaterThanEqual64, IR::Type::U64, false, true);
return;
case O::V_CMP_EQ_U16:
EmitInteger16Compare(inst, IR::ValueOpcode::IEqual32, false, false);
@@ -344,6 +371,7 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_CMP_O_F32: EmitFloatOrderedCompare(inst, true, false); return;
case O::V_CMP_U_F32: EmitFloatOrderedCompare(inst, false, false); return;
case O::V_CMPX_O_F32: EmitFloatOrderedCompare(inst, true, true); return;
case O::V_CMPX_U_F32: EmitFloatOrderedCompare(inst, false, true); return;
case O::V_CMP_CLASS_F32: EmitFloatClassCompare(inst, false); return;
case O::V_CMPX_CLASS_F32: EmitFloatClassCompare(inst, true); return;
case O::V_CMPX_CLASS_F16: EmitFloatClassCompare(inst, true, true); return;
@@ -394,7 +422,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
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_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
+9
View File
@@ -3200,6 +3200,15 @@ static void* RunThread(void* arg) {
os_thread_id = GetHostThreadId();
#endif
thread->host_thread_id = os_thread_id;
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
if (!thread->name.empty()) {
#if defined(__APPLE__)
pthread_setname_np(thread->name.substr(0, 63).c_str());
#else
pthread_setname_np(pthread_self(), thread->name.substr(0, 15).c_str());
#endif
}
#endif
LOGF("\tPthread run begin: %s, id = %d, os_thread_id = %" PRIu64 ", entry = 0x%016" PRIx64
", arg = 0x%016" PRIx64 ", stack_addr = 0x%016" PRIx64 ", stack_size = %" PRIu64 "\n",
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>840</width>
<width>900</width>
<height>620</height>
</rect>
</property>
@@ -179,6 +179,12 @@
<property name="text"><string>Auto-hide cursor</string></property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="checkBox_skip_notice_screen">
<property name="toolTip"><string>Skip startup logos and notices in supported games.</string></property>
<property name="text"><string>Skip boot logos</string></property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -400,6 +406,13 @@ To use your own sound, replace the emulator's assets/sounds/trophy-unlock.wav wi
<property name="toolTip"><string>Restore default settings</string></property>
</widget>
</item>
<item>
<widget class="QPushButton" name="settings_button">
<property name="minimumSize"><size><width>80</width><height>25</height></size></property>
<property name="text"><string>Game Configs</string></property>
<property name="toolTip"><string>Import or export game configs</string></property>
</widget>
</item>
<item>
<spacer name="buttonSpacer">
<property name="orientation"><enum>Qt::Horizontal</enum></property>
@@ -125,7 +125,7 @@
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Edit configuration&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Edit game config</string>
</property>
<property name="text">
<string/>
@@ -150,7 +150,7 @@
</size>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Delete configuration&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>Clear game config</string>
</property>
<property name="styleSheet">
<string notr="true"/>
+13 -87
View File
@@ -6,6 +6,7 @@
#include <QByteArray>
#include <QChar>
#include <QColor>
#include <QJsonObject>
#include <QMetaEnum>
#include <QMetaType>
#include <QObject>
@@ -14,9 +15,6 @@
#include <QStringList>
#include <QVariant>
#define KYTY_CFG_SET(n) s->setValue(#n, QVariant::fromValue(n).toString());
#define KYTY_CFG_GET(n) n = s->value(#n).value<decltype(n)>();
template <class T>
inline QStringList EnumToList() {
QStringList ret;
@@ -123,10 +121,11 @@ public:
bool readback_linear_images = false;
bool tessellation_enabled = false;
bool trophy_enabled = true;
bool skip_notice_screen = false;
int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = false;
bool shader_validation_enabled = true;
bool shader_validation_enabled = false;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::Performance;
LogDirection shader_log_direction = LogDirection::Silent;
QString shader_log_folder = "_Shaders";
@@ -156,6 +155,7 @@ public:
readback_linear_images = other.readback_linear_images;
tessellation_enabled = other.tessellation_enabled;
trophy_enabled = other.trophy_enabled;
skip_notice_screen = other.skip_notice_screen;
vblank_frequency = other.vblank_frequency;
console_language = other.console_language;
vulkan_validation_enabled = other.vulkan_validation_enabled;
@@ -188,90 +188,16 @@ public:
game_comment = other.game_comment;
}
void WriteSettings(QSettings* s) const {
KYTY_CFG_SET(name);
KYTY_CFG_SET(basedir);
KYTY_CFG_SET(game_path);
KYTY_CFG_SET(custom_settings);
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(user_name);
KYTY_CFG_SET(user_id);
KYTY_CFG_SET(audio_input_device);
KYTY_CFG_SET(present_mode);
KYTY_CFG_SET(gpu_index);
KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(hide_cursor_enabled);
KYTY_CFG_SET(readback_linear_images);
KYTY_CFG_SET(tessellation_enabled);
KYTY_CFG_SET(trophy_enabled);
KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language);
KYTY_CFG_SET(vulkan_validation_enabled);
KYTY_CFG_SET(shader_validation_enabled);
KYTY_CFG_SET(shader_optimization_type);
KYTY_CFG_SET(shader_log_direction);
KYTY_CFG_SET(shader_log_folder);
KYTY_CFG_SET(command_buffer_dump_enabled);
KYTY_CFG_SET(command_buffer_dump_folder);
KYTY_CFG_SET(printf_direction);
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_enabled);
KYTY_CFG_SET(renderdoc_enabled);
KYTY_CFG_SET(amd_cpu_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
#endif
s->setValue("host_input_mapping", host_input_mapping);
KYTY_CFG_SET(elf);
}
void WriteSettings(QSettings* s) const;
void ReadSettings(QSettings* s);
void ReadSettings(QSettings* s) {
KYTY_CFG_GET(name);
KYTY_CFG_GET(basedir);
KYTY_CFG_GET(game_path);
KYTY_CFG_GET(custom_settings);
KYTY_CFG_GET(screen_resolution);
user_name = s->value("user_name", user_name).toString();
bool user_id_ok = false;
auto saved_user_id = s->value("user_id", user_id).toInt(&user_id_ok);
user_id = user_id_ok && Config::IsConfiguredUserIdValid(saved_user_id)
? saved_user_id
: Config::DEFAULT_USER_ID;
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
KYTY_CFG_GET(present_mode);
gpu_index = s->value("gpu_index", -1).toInt();
if (EnumToText(present_mode).isEmpty()) {
present_mode = PresentMode::Mailbox;
}
KYTY_CFG_GET(fullscreen_enabled);
KYTY_CFG_GET(hide_cursor_enabled);
KYTY_CFG_GET(readback_linear_images);
KYTY_CFG_GET(tessellation_enabled);
trophy_enabled = s->value("trophy_enabled", trophy_enabled).toBool();
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) {
console_language = DEFAULT_CONSOLE_LANGUAGE;
}
KYTY_CFG_GET(vulkan_validation_enabled);
KYTY_CFG_GET(shader_validation_enabled);
KYTY_CFG_GET(shader_optimization_type);
KYTY_CFG_GET(shader_log_direction);
KYTY_CFG_GET(shader_log_folder);
KYTY_CFG_GET(command_buffer_dump_enabled);
KYTY_CFG_GET(command_buffer_dump_folder);
KYTY_CFG_GET(printf_direction);
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_enabled);
KYTY_CFG_GET(renderdoc_enabled);
amd_cpu_enabled = s->value("amd_cpu_enabled", false).toBool();
#if defined(_WIN32)
red_zone_protection_enabled =
s->value("red_zone_protection_enabled", red_zone_protection_enabled).toBool();
#endif
host_input_mapping = s->value("host_input_mapping", host_input_mapping).toStringList();
elf = s->value("elf", elf).toString();
}
[[nodiscard]] QVariantMap GameSettings() const;
// Call on a temporary configuration: validation can fail after loading values.
bool SetGameSettings(const QJsonObject& settings, QString& error);
private:
template <class Settings>
void ReadGameSettingsValues(const Settings& s);
};
Q_DECLARE_METATYPE(Configuration*)
@@ -33,6 +33,8 @@ public:
signals:
void PreviewControllerColor(const QString& color);
void ImportGameSettings();
void ExportGameSettings();
private:
Ui::ConfigurationEditDialog* m_ui = nullptr;
@@ -74,6 +74,8 @@ private:
void SelectItem(QTreeWidgetItem* witem);
void ApplyCompatibility();
void UpdateToolbarIcons();
void ImportGameSettings(QWidget* parent);
void ExportGameSettings(QWidget* parent) const;
[[nodiscard]] bool HasValidGameDirectory() const;
ConfigurationItem* m_selected_item = nullptr;
+147
View File
@@ -0,0 +1,147 @@
#include "configuration.h"
QVariantMap Configuration::GameSettings() const {
QVariantMap values;
#define KYTY_CFG_SET(n) values.insert(QStringLiteral(#n), QVariant::fromValue(n).toString())
KYTY_CFG_SET(screen_resolution);
KYTY_CFG_SET(user_name);
KYTY_CFG_SET(user_id);
KYTY_CFG_SET(audio_input_device);
KYTY_CFG_SET(present_mode);
KYTY_CFG_SET(gpu_index);
KYTY_CFG_SET(fullscreen_enabled);
KYTY_CFG_SET(hide_cursor_enabled);
KYTY_CFG_SET(readback_linear_images);
KYTY_CFG_SET(tessellation_enabled);
KYTY_CFG_SET(trophy_enabled);
KYTY_CFG_SET(skip_notice_screen);
KYTY_CFG_SET(vblank_frequency);
KYTY_CFG_SET(console_language);
KYTY_CFG_SET(vulkan_validation_enabled);
KYTY_CFG_SET(shader_validation_enabled);
KYTY_CFG_SET(shader_optimization_type);
KYTY_CFG_SET(shader_log_direction);
KYTY_CFG_SET(shader_log_folder);
KYTY_CFG_SET(command_buffer_dump_enabled);
KYTY_CFG_SET(command_buffer_dump_folder);
KYTY_CFG_SET(printf_direction);
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_enabled);
KYTY_CFG_SET(renderdoc_enabled);
KYTY_CFG_SET(amd_cpu_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
#endif
KYTY_CFG_SET(elf);
#undef KYTY_CFG_SET
return values;
}
template <class Settings>
void Configuration::ReadGameSettingsValues(const Settings& s) {
#define KYTY_CFG_GET(n) n = s.value(QStringLiteral(#n)).template value<decltype(n)>()
KYTY_CFG_GET(screen_resolution);
user_name = s.value("user_name", user_name).toString();
bool user_id_ok = false;
auto saved_user_id = s.value("user_id", user_id).toInt(&user_id_ok);
user_id = user_id_ok && Config::IsConfiguredUserIdValid(saved_user_id)
? saved_user_id
: Config::DEFAULT_USER_ID;
audio_input_device = s.value("audio_input_device", audio_input_device).toString();
KYTY_CFG_GET(present_mode);
gpu_index = s.value("gpu_index", -1).toInt();
if (EnumToText(present_mode).isEmpty()) {
present_mode = PresentMode::Mailbox;
}
KYTY_CFG_GET(fullscreen_enabled);
KYTY_CFG_GET(hide_cursor_enabled);
KYTY_CFG_GET(readback_linear_images);
KYTY_CFG_GET(tessellation_enabled);
trophy_enabled = s.value("trophy_enabled", trophy_enabled).toBool();
KYTY_CFG_GET(skip_notice_screen);
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) {
console_language = DEFAULT_CONSOLE_LANGUAGE;
}
KYTY_CFG_GET(vulkan_validation_enabled);
KYTY_CFG_GET(shader_validation_enabled);
KYTY_CFG_GET(shader_optimization_type);
KYTY_CFG_GET(shader_log_direction);
KYTY_CFG_GET(shader_log_folder);
KYTY_CFG_GET(command_buffer_dump_enabled);
KYTY_CFG_GET(command_buffer_dump_folder);
KYTY_CFG_GET(printf_direction);
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_enabled);
KYTY_CFG_GET(renderdoc_enabled);
amd_cpu_enabled = s.value("amd_cpu_enabled", false).toBool();
#if defined(_WIN32)
red_zone_protection_enabled =
s.value("red_zone_protection_enabled", red_zone_protection_enabled).toBool();
#endif
elf = s.value("elf", elf).toString();
#undef KYTY_CFG_GET
}
void Configuration::WriteSettings(QSettings* s) const {
const auto values = GameSettings();
for (auto it = values.constBegin(); it != values.constEnd(); ++it) {
s->setValue(it.key(), it.value());
}
s->setValue("name", name);
s->setValue("basedir", basedir);
s->setValue("game_path", game_path);
s->setValue("custom_settings", QVariant::fromValue(custom_settings).toString());
s->setValue("host_input_mapping", host_input_mapping);
}
void Configuration::ReadSettings(QSettings* s) {
ReadGameSettingsValues(*s);
name = s->value("name").toString();
basedir = s->value("basedir").toString();
game_path = s->value("game_path").toString();
custom_settings = s->value("custom_settings").toBool();
host_input_mapping = s->value("host_input_mapping", host_input_mapping).toStringList();
}
bool Configuration::SetGameSettings(const QJsonObject& settings, QString& error) {
const auto fail = [&error](const QString& message) {
error = message;
return false;
};
auto values = GameSettings();
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
if (!values.contains(it.key())) {
return fail(tr("Unknown game setting: %1").arg(it.key()));
}
if (!it.value().isString()) {
return fail(tr("Game setting %1 must be a string.").arg(it.key()));
}
values.insert(it.key(), it.value().toString());
}
ReadGameSettingsValues(values);
const auto canonical = GameSettings();
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
if (canonical.value(it.key()) != values.value(it.key())) {
return fail(tr("Invalid value for game setting: %1").arg(it.key()));
}
}
if (user_name.trimmed().isEmpty() || user_name.toUtf8().size() > Config::MAX_USER_NAME_LENGTH) {
return fail(tr("User name must contain 1-16 UTF-8 bytes."));
}
if (gpu_index < -1) {
return fail(tr("GPU index must be -1 or greater."));
}
if (vblank_frequency < 30 || vblank_frequency > 360) {
return fail(tr("Vblank frequency must be between 30 and 360."));
}
if ((shader_log_direction == LogDirection::File && shader_log_folder.isEmpty()) ||
(printf_direction == LogDirection::File && printf_output_file.isEmpty()) ||
(command_buffer_dump_enabled && command_buffer_dump_folder.isEmpty())) {
return fail(tr("Enabled logging and command buffer dumps require an output path."));
}
return true;
}
@@ -19,6 +19,7 @@
#include <QLineEdit>
#include <QListView>
#include <QListWidget>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QResizeEvent>
@@ -126,6 +127,7 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
setMinimumWidth(width());
InitGameDirectories();
m_ui->controller_group->setVisible(false);
m_ui->settings_button->setVisible(false);
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
@@ -274,6 +276,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_hide_cursor->setChecked(info.hide_cursor_enabled);
m_ui->checkBox_readback->setChecked(info.readback_linear_images);
m_ui->checkBox_tessellation->setChecked(info.tessellation_enabled);
m_ui->checkBox_skip_notice_screen->setChecked(info.skip_notice_screen);
m_ui->checkBox_trophy_notifications->setChecked(info.trophy_enabled);
m_ui->spinBox_vblank_frequency->setValue(info.vblank_frequency);
m_ui->comboBox_console_language->clear();
@@ -354,6 +357,11 @@ void ConfigurationEditDialog::InitGameDirectories() {
void ConfigurationEditDialog::SetGlobalSettings(const QStringList& dirs) {
m_global_settings = true;
auto* menu = new QMenu(m_ui->settings_button);
menu->addAction(tr("Import..."), this, &ConfigurationEditDialog::ImportGameSettings);
menu->addAction(tr("Export..."), this, &ConfigurationEditDialog::ExportGameSettings);
m_ui->settings_button->setMenu(menu);
m_ui->settings_button->setVisible(true);
m_game_dirs_list->clear();
m_game_dirs_group->setMinimumWidth(GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH);
@@ -428,6 +436,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui, boo
info.hide_cursor_enabled = ui.checkBox_hide_cursor->isChecked();
info.readback_linear_images = ui.checkBox_readback->isChecked();
info.tessellation_enabled = ui.checkBox_tessellation->isChecked();
info.skip_notice_screen = ui.checkBox_skip_notice_screen->isChecked();
info.trophy_enabled = ui.checkBox_trophy_notifications->isChecked();
info.vblank_frequency = ui.spinBox_vblank_frequency->value();
info.console_language = ui.comboBox_console_language->currentIndex();
+139 -6
View File
@@ -24,9 +24,11 @@
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QHeaderView>
#include <QIcon>
#include <QInputDialog>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
@@ -40,6 +42,7 @@
#include <QProcess>
#include <QPushButton>
#include <QRegularExpression>
#include <QSaveFile>
#include <QSet>
#include <QSettings>
#include <QSignalBlocker>
@@ -765,7 +768,7 @@ void ConfigurationListWidget::edit_configuration() {
auto info = CreateConfiguration(*item);
ConfigurationEditDialog dlg(*info, this);
dlg.setWindowTitle(tr("Edit game settings"));
dlg.setWindowTitle(tr("Edit game config"));
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
&ConfigurationListWidget::PreviewControllerColor);
@@ -789,8 +792,8 @@ void ConfigurationListWidget::delete_configuartion() {
return;
}
if (QMessageBox::Yes == QMessageBox::question(this, tr("Clear custom settings"),
tr("Do you want to clear custom settings?"))) {
if (QMessageBox::Yes == QMessageBox::question(this, tr("Clear game config"),
tr("Clear this game's config and use global settings?"))) {
delete m_custom_infos.take(item->GetInfo().game_path);
item->GetInfo().custom_settings = false;
WriteSettings();
@@ -810,6 +813,10 @@ void ConfigurationListWidget::edit_global_settings() {
dlg.SetGlobalSettings(m_game_dirs);
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
&ConfigurationListWidget::PreviewControllerColor);
connect(&dlg, &ConfigurationEditDialog::ImportGameSettings, this,
[this, &dlg]() { ImportGameSettings(&dlg); });
connect(&dlg, &ConfigurationEditDialog::ExportGameSettings, this,
[this, &dlg]() { ExportGameSettings(&dlg); });
if (dlg.exec() == QDialog::Accepted) {
m_global_info.CopyEmulatorSettingsFrom(info);
@@ -822,7 +829,133 @@ void ConfigurationListWidget::edit_global_settings() {
ScanGameDirectory();
}
}
emit Select();
SelectItem(m_ui->cfgs_list->currentItem());
}
static bool IsGameSettingsTitleId(const QString& title_id) {
static const QRegularExpression pattern(QStringLiteral("\\APPSA[0-9]{5}\\z"));
return pattern.match(title_id).hasMatch();
}
void ConfigurationListWidget::ImportGameSettings(QWidget* parent) {
const auto path = QFileDialog::getOpenFileName(parent, tr("Import game configs"), {},
tr("JSON files (*.json)"));
if (path.isEmpty()) {
return;
}
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(parent, tr("Import failed"), file.errorString());
return;
}
const auto document = QJsonDocument::fromJson(file.readAll());
if (!document.isObject() || document.object().isEmpty()) {
QMessageBox::warning(
parent, tr("Import failed"),
tr("Expected a JSON object containing game configs keyed by PPSA code."));
return;
}
const auto settings = document.object();
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
Configuration info;
QString error;
if (!IsGameSettingsTitleId(it.key()) || !it.value().isObject() ||
it.value().toObject().isEmpty() ||
!info.SetGameSettings(it.value().toObject(), error)) {
QMessageBox::warning(parent, tr("Import failed"),
tr("Invalid game config for %1. %2").arg(it.key(), error));
return;
}
}
struct ImportedGame {
ConfigurationItem* item;
std::unique_ptr<Configuration> info;
};
std::vector<ImportedGame> imported;
auto unmatched = settings.keys();
for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); ++index) {
auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
const auto title_id = item->GetInfo().title_id.trimmed().toUpper();
if (!settings.contains(title_id)) {
continue;
}
auto info = CreateConfiguration(*item);
QString error;
if (!info->SetGameSettings(settings.value(title_id).toObject(), error)) {
QMessageBox::warning(parent, tr("Import failed"),
tr("Invalid game config for %1. %2").arg(title_id, error));
return;
}
info->custom_settings = true;
imported.push_back({item, std::move(info)});
unmatched.removeAll(title_id);
}
auto message = imported.empty() ? tr("No matching games found in the library.")
: tr("Configs will be updated for the following games:\n");
for (const auto& game: imported) {
message += tr("\n%1 — %2").arg(game.info->title_id.trimmed().toUpper(), game.info->name);
}
if (!unmatched.isEmpty()) {
message += tr("\nGames not found: %1").arg(unmatched.join(QStringLiteral(", ")));
}
if (imported.empty()) {
QMessageBox::information(parent, tr("Import game configs"), message);
return;
}
QMessageBox confirmation(QMessageBox::Question, tr("Import game configs"), message,
QMessageBox::Apply | QMessageBox::Cancel, parent);
confirmation.setTextFormat(Qt::PlainText);
confirmation.setDefaultButton(QMessageBox::Cancel);
if (confirmation.exec() != QMessageBox::Apply) {
return;
}
for (auto& [item, info]: imported) {
const auto game_path = info->game_path;
delete m_custom_infos.take(game_path);
m_custom_infos.insert(game_path, info.release());
item->GetInfo().custom_settings = true;
item->Update();
}
WriteSettings();
}
void ConfigurationListWidget::ExportGameSettings(QWidget* parent) const {
QMap<QString, const ConfigurationItem*> games;
for (int index = 0; index < m_ui->cfgs_list->topLevelItemCount(); ++index) {
const auto* item = static_cast<ConfigurationItem*>(m_ui->cfgs_list->topLevelItem(index));
const auto& game = item->GetInfo();
const auto title_id = game.title_id.trimmed().toUpper();
if (m_custom_infos.contains(game.game_path) && IsGameSettingsTitleId(title_id)) {
games.insert(tr("%1 — %2 (%3)").arg(title_id, game.name, game.game_path), item);
}
}
if (games.isEmpty()) {
QMessageBox::information(parent, tr("Export game config"),
tr("No game configs with a PPSA code were found."));
return;
}
bool accepted = false;
const auto selected = QInputDialog::getItem(parent, tr("Export game config"), tr("Game:"),
games.keys(), 0, false, &accepted);
if (!accepted) {
return;
}
const auto& game = games.value(selected)->GetInfo();
const auto title_id = game.title_id.trimmed().toUpper();
const auto path =
QFileDialog::getSaveFileName(parent, tr("Export game config"),
title_id + QStringLiteral(".json"), tr("JSON files (*.json)"));
if (path.isEmpty()) {
return;
}
const auto settings =
QJsonObject::fromVariantMap(m_custom_infos.value(game.game_path)->GameSettings());
const auto data =
QJsonDocument(QJsonObject {{title_id, settings}}).toJson(QJsonDocument::Indented);
QSaveFile file(path);
if (!file.open(QIODevice::WriteOnly) || file.write(data) != data.size() || !file.commit()) {
QMessageBox::warning(parent, tr("Export failed"), file.errorString());
}
}
void ConfigurationListWidget::edit_input_mapping() {
@@ -1040,11 +1173,11 @@ void ConfigurationListWidget::show_context_menu(const QPoint& pos) {
tr("Remove save data..."), this, SLOT(remove_save_data()));
menu.addSeparator();
QAction* action_edit =
menu.addAction(style()->standardIcon(QStyle::SP_FileIcon), tr("Edit game settings..."),
menu.addAction(style()->standardIcon(QStyle::SP_FileIcon), tr("Edit game config..."),
this, SLOT(edit_configuration()));
QAction* action_delete =
menu.addAction(style()->standardIcon(QStyle::SP_DialogDiscardButton),
tr("Clear custom settings"), this, SLOT(delete_configuartion()));
tr("Clear game config"), this, SLOT(delete_configuartion()));
if (item == nullptr) {
menu.addSeparator();
+1
View File
@@ -246,6 +246,7 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
}
args << "--readback-linear-images" << BoolArg(info.readback_linear_images);
args << "--trophy-notifications" << BoolArg(info.trophy_enabled);
args << "--skip-notice-screen" << BoolArg(info.skip_notice_screen);
if (info.tessellation_enabled) {
args << "--tessellation";
}
+130 -39
View File
@@ -9,6 +9,7 @@
#include <QDateTime>
#include <QDialogButtonBox>
#include <QFileInfo>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QImage>
#include <QLabel>
@@ -18,6 +19,7 @@
#include <QPainterPath>
#include <QPixmap>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
#include <QSize>
#include <QStringList>
@@ -25,6 +27,8 @@
#include <QTabWidget>
#include <QVBoxLayout>
#include <utility>
namespace {
static const QRegularExpression TrophyFilePattern(QStringLiteral("^trophy(\\d+)\\.ucp$"));
@@ -54,17 +58,115 @@ static QString GradeToText(int grade) {
}
}
static QString TrophyTooltip(const Common::Trophies::Trophy& trophy) {
QStringList lines {QString::fromStdString(trophy.name),
QString::fromStdString(trophy.description),
QObject::tr("Grade: %1").arg(GradeToText(trophy.grade))};
if (trophy.hidden) {
lines.append(QObject::tr("Hidden trophy"));
QString TrophyName(int id, const Common::Trophies::Trophy& trophy, bool masked) {
return masked ? QObject::tr("Hidden trophy")
: trophy.name.empty() ? QObject::tr("Trophy %1").arg(id)
: QString::fromStdString(trophy.name);
}
QPixmap TrophyIcon(const Common::Trophies::Trophy& trophy, bool unlocked, bool masked, int size) {
QPixmap pixmap;
if (masked) {
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
static_cast<uint>(trophy.icon_png.size())) &&
!unlocked) {
auto image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32);
for (int y = 0; y < image.height(); ++y) {
auto* line = reinterpret_cast<QRgb*>(image.scanLine(y));
for (int x = 0; x < image.width(); ++x) {
const int gray = qGray(line[x]);
line[x] = qRgba(gray, gray, gray, qAlpha(line[x]));
}
}
pixmap = QPixmap::fromImage(image);
}
if (trophy.has_reward && !trophy.reward.empty()) {
lines.append(QObject::tr("Reward: %1").arg(QString::fromStdString(trophy.reward)));
return pixmap.isNull()
? pixmap
: pixmap.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
QString TrophyEarnedDate(int id, const Common::Trophies::UnlockData& unlocks) {
const auto found = unlocks.timestamps.find(id);
if (found == unlocks.timestamps.end()) {
return {};
}
return lines.join(QLatin1Char('\n'));
return QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>((found->second - Common::Trophies::UnixEpochTick) / 1000))
.toLocalTime()
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
}
void ShowTrophyInspector(QWidget* parent, int id, const Common::Trophies::Trophy& trophy,
bool unlocked, const QString& earned_date) {
QDialog dialog(parent);
dialog.setWindowTitle(QObject::tr("Trophy Details"));
dialog.resize(640, 380);
auto* layout = new QVBoxLayout(&dialog);
auto* top = new QHBoxLayout;
auto* icon = new QLabel(&dialog);
icon->setFixedSize(148, 148);
icon->setAlignment(Qt::AlignCenter);
auto* text_box = new QVBoxLayout;
auto* name = new QLabel(&dialog);
name->setTextFormat(Qt::PlainText);
name->setStyleSheet(QStringLiteral("font-size: 26px;"));
name->setWordWrap(true);
auto* subtitle = new QLabel(QObject::tr("Hidden trophy"), &dialog);
subtitle->setStyleSheet(QStringLiteral("font-size: 16px;"));
auto* reveal = new QPushButton(QObject::tr("Show Hidden Trophy"), &dialog);
text_box->addWidget(name);
text_box->addWidget(subtitle);
text_box->addWidget(reveal, 0, Qt::AlignLeft);
text_box->addStretch(1);
top->addWidget(icon);
top->addSpacing(16);
top->addLayout(text_box, 1);
layout->addLayout(top);
auto* grid = new QGridLayout;
grid->setColumnStretch(1, 1);
const auto make_row = [&](const QString& label, const QString& text = {}) {
auto* key = new QLabel(label, &dialog);
auto* value = new QLabel(text, &dialog);
key->setStyleSheet(QStringLiteral("font-size: 17px;"));
value->setStyleSheet(QStringLiteral("font-size: 17px;"));
value->setTextFormat(Qt::PlainText);
value->setWordWrap(true);
const int row = grid->rowCount();
grid->addWidget(key, row, 0);
grid->addWidget(value, row, 1);
return value;
};
auto* grade = make_row(QObject::tr("Grade"));
make_row(QObject::tr("Status"), unlocked ? QObject::tr("Earned") : QObject::tr("Not earned"));
make_row(QObject::tr("Earned date"),
unlocked && !earned_date.isEmpty() ? earned_date : QStringLiteral("-"));
auto* details = make_row(QObject::tr("Details"));
auto* reward =
trophy.has_reward && !trophy.reward.empty() ? make_row(QObject::tr("Reward")) : nullptr;
layout->addLayout(grid);
layout->addStretch(1);
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout->addWidget(buttons);
const auto refresh = [&](bool masked) {
icon->setPixmap(TrophyIcon(trophy, unlocked, masked, 148));
name->setText(TrophyName(id, trophy, masked));
subtitle->setVisible(trophy.hidden && !masked);
reveal->setVisible(masked);
grade->setText(masked ? QObject::tr("Hidden trophy") : GradeToText(trophy.grade));
details->setText(masked || trophy.description.empty()
? QStringLiteral("-")
: QString::fromStdString(trophy.description));
if (reward != nullptr) {
reward->setText(masked ? QStringLiteral("-") : QString::fromStdString(trophy.reward));
}
};
QObject::connect(reveal, &QPushButton::clicked, &dialog, [&] { refresh(false); });
refresh(trophy.hidden && !unlocked);
dialog.exec();
}
} // namespace
@@ -337,13 +439,13 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
}
QStringList errors;
for (const auto& file: trophy_files) {
const auto package =
auto package =
Common::Trophies::LoadPackage(GameContent::ToPath(file), info.console_language);
if (package.trophies.empty()) {
errors.append(tr("Could not read trophy package %1.").arg(QFileInfo(file).fileName()));
continue;
}
const auto unlocks = LoadUnlocks(info, runtime_directory, file);
auto unlocks = LoadUnlocks(info, runtime_directory, file);
const auto counts = Common::Trophies::GetProgress(package, unlocks);
auto* page = new QWidget(m_tabs);
auto* page_layout = new QVBoxLayout(page);
@@ -395,8 +497,10 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
const auto hidden_locked = trophy.hidden && !trophy_unlocked;
auto* item = new QListWidgetItem(list);
item->setSizeHint(QSize(900, 96));
item->setData(Qt::UserRole, id);
auto* card = new QWidget(list);
card->setAttribute(Qt::WA_TransparentForMouseEvents);
card->setStyleSheet(
QStringLiteral("QWidget#card { background: palette(base); border-radius: 12px; }"));
card->setObjectName(QStringLiteral("card"));
@@ -404,26 +508,12 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
auto* icon = new QLabel(card);
icon->setFixedSize(76, 76);
icon->setAlignment(Qt::AlignCenter);
QPixmap pixmap;
if (hidden_locked) {
pixmap = QPixmap(QStringLiteral(":/icons/hidden-trophy.png"));
} else if (pixmap.loadFromData(reinterpret_cast<const uchar*>(trophy.icon_png.data()),
static_cast<uint>(trophy.icon_png.size())) &&
!trophy_unlocked) {
pixmap =
QPixmap::fromImage(pixmap.toImage().convertToFormat(QImage::Format_Grayscale8));
}
if (!pixmap.isNull()) {
icon->setPixmap(
pixmap.scaled(76, 76, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
icon->setPixmap(TrophyIcon(trophy, trophy_unlocked, hidden_locked, 76));
row->addWidget(icon);
auto* left = new QVBoxLayout;
const auto name_text = hidden_locked ? tr("Hidden trophy")
: trophy.name.empty() ? tr("Trophy %1").arg(id)
: QString::fromStdString(trophy.name);
auto* name = new QLabel(name_text, card);
auto* left = new QVBoxLayout;
auto* name = new QLabel(TrophyName(id, trophy, hidden_locked), card);
name->setTextFormat(Qt::PlainText);
name->setStyleSheet(QStringLiteral("font-size: 20px; font-weight: bold;"));
left->addWidget(name);
if (!hidden_locked) {
@@ -440,15 +530,9 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
row->addLayout(left, 2);
auto* right = new QVBoxLayout;
QString date_text;
if (const auto found = unlocks.timestamps.find(id); found != unlocks.timestamps.end()) {
date_text = QDateTime::fromMSecsSinceEpoch(
static_cast<qint64>(
(found->second - Common::Trophies::UnixEpochTick) / 1000))
.toLocalTime()
.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"));
} else if (trophy_unlocked) {
auto* right = new QVBoxLayout;
auto date_text = TrophyEarnedDate(id, unlocks);
if (date_text.isEmpty() && trophy_unlocked) {
date_text = tr("Earned");
}
auto* date = new QLabel(date_text, card);
@@ -456,15 +540,22 @@ bool TrophyViewerDialog::LoadGame(const Configuration& info, const QString& runt
date->setStyleSheet(QStringLiteral("font-size: 15px;"));
auto* detail = new QLabel(
hidden_locked ? QString {} : QString::fromStdString(trophy.description), card);
detail->setTextFormat(Qt::PlainText);
detail->setWordWrap(true);
detail->setAlignment(Qt::AlignLeft | Qt::AlignTop);
detail->setStyleSheet(QStringLiteral("font-size: 17px;"));
right->addWidget(date);
right->addWidget(detail, 1);
row->addLayout(right, 3);
card->setToolTip(hidden_locked ? name_text : TrophyTooltip(trophy));
list->setItemWidget(item, card);
}
connect(list, &QListWidget::itemClicked, this,
[this, trophies = std::move(package.trophies),
unlocks = std::move(unlocks)](QListWidgetItem* item) {
const int id = item->data(Qt::UserRole).toInt();
ShowTrophyInspector(this, id, trophies.at(id), unlocks.unlocked.contains(id),
TrophyEarnedDate(id, unlocks));
});
m_tabs->addTab(page, QString::fromStdString(package.title));
}
m_tabs->tabBar()->setVisible(m_tabs->count() > 1);
+6
View File
@@ -2388,6 +2388,12 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer*
return cmd;
}
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize() {
PRINT_NAME();
return 5u * sizeof(uint32_t);
}
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
const volatile ShaderRegister* regs,
uint32_t num_regs) {
+1
View File
@@ -137,6 +137,7 @@ uint32_t* KYTY_SYSV_ABI AgcDcbSetCxRegistersIndirect(CommandBuffer*
uint32_t* KYTY_SYSV_ABI AgcDcbSetShRegistersIndirect(CommandBuffer* buf,
const volatile ShaderRegister* regs,
uint32_t num_regs);
uint64_t KYTY_SYSV_ABI AgcDcbSetShRegistersIndirectGetSize();
uint32_t* KYTY_SYSV_ABI AgcDcbSetUcRegistersIndirect(CommandBuffer* buf,
const volatile ShaderRegister* regs,
uint32_t num_regs);
+1
View File
@@ -95,6 +95,7 @@ LIB_DEFINE(InitAgcDriver_1) {
LIB_FUNC("w4-d0n60hdo", Gen5::AgcDcbSetUcRegisterDirect);
LIB_FUNC("ZvwO9euwYzc", Gen5::AgcDcbSetCxRegistersIndirect);
LIB_FUNC("-HOOCn0JY48", Gen5::AgcDcbSetShRegistersIndirect);
LIB_FUNC("nNlUtdDDvZ0", Gen5::AgcDcbSetShRegistersIndirectGetSize);
LIB_FUNC("hvUfkUIQcOE", Gen5::AgcDcbSetUcRegistersIndirect);
LIB_FUNC("GIIW2J37e70", Gen5::AgcDcbSetIndexSize);
LIB_FUNC("l4fM9K-Lyks", Gen5::AgcDcbSetIndexBuffer);
+56 -29
View File
@@ -2831,6 +2831,8 @@ constexpr int32_t KERNEL_AIO_STATE_ABORTED = 4;
constexpr int32_t KERNEL_AIO_STATE_NOTIFIED = 0x10000;
constexpr int32_t KERNEL_AIO_MAX_QUEUE = 512;
constexpr int32_t KERNEL_AIO_MAX_REQUESTS = 128;
constexpr uint32_t KERNEL_AIO_WAIT_AND = 1;
constexpr uint32_t KERNEL_AIO_WAIT_OR = 2;
struct KernelAioResult {
int64_t return_value;
@@ -2933,26 +2935,28 @@ int KYTY_SYSV_ABI KernelAioSubmitWriteCommands(KernelAioRwRequest* req, int32_t
return OK;
}
static int32_t kernel_aio_get_state(int32_t id, bool notify) {
if (!kernel_aio_is_valid_id(id)) {
return LibKernel::KERNEL_ERROR_ESRCH;
}
auto current = g_kernel_aio_state[id].load(std::memory_order_acquire);
while (notify && (current == KERNEL_AIO_STATE_COMPLETED || current == KERNEL_AIO_STATE_ABORTED)) {
if (g_kernel_aio_state[id].compare_exchange_weak(current, current | KERNEL_AIO_STATE_NOTIFIED,
std::memory_order_acq_rel)) {
break;
}
}
return current == 0 ? LibKernel::KERNEL_ERROR_ESRCH : current;
}
int KYTY_SYSV_ABI KernelAioPollRequest(int32_t id, int32_t* state) {
PRINT_NAME();
if (state == nullptr) {
return LibKernel::KERNEL_ERROR_EFAULT;
}
if (!kernel_aio_is_valid_id(id)) {
*state = LibKernel::KERNEL_ERROR_ESRCH;
return OK;
}
auto current = g_kernel_aio_state[id].load(std::memory_order_acquire);
while (current == KERNEL_AIO_STATE_COMPLETED || current == KERNEL_AIO_STATE_ABORTED) {
if (g_kernel_aio_state[id].compare_exchange_weak(current, current | KERNEL_AIO_STATE_NOTIFIED,
std::memory_order_acq_rel)) {
break;
}
}
*state = current == 0 ? LibKernel::KERNEL_ERROR_ESRCH : current;
*state = kernel_aio_get_state(id, true);
return OK;
}
@@ -2969,31 +2973,53 @@ int KYTY_SYSV_ABI KernelAioPollRequests(int32_t* ids, int32_t num, int32_t* stat
return OK;
}
int KYTY_SYSV_ABI KernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) {
int KYTY_SYSV_ABI KernelAioWaitRequests(int32_t* ids, int32_t num, int32_t* states, uint32_t mode,
uint32_t* usec) {
PRINT_NAME();
if (state == nullptr) {
if (ids == nullptr || states == nullptr) {
return LibKernel::KERNEL_ERROR_EFAULT;
}
if (!kernel_aio_is_valid_id(id)) {
return KernelAioPollRequest(id, state);
if (num <= 0 || num > KERNEL_AIO_MAX_REQUESTS ||
(num != 1 && mode != KERNEL_AIO_WAIT_AND && mode != KERNEL_AIO_WAIT_OR)) {
return LibKernel::KERNEL_ERROR_EINVAL;
}
uint32_t waited = 0;
auto current = g_kernel_aio_state[id].load(std::memory_order_acquire);
while (current == KERNEL_AIO_STATE_PROCESSING) {
if (usec != nullptr && *usec != 0 && waited >= *usec) {
*state = current;
return LibKernel::KERNEL_ERROR_ETIMEDOUT;
const auto collect_states = [&](bool notify) {
int32_t finished = 0;
bool newly_complete = false;
for (int32_t i = 0; i < num; i++) {
const auto current = kernel_aio_get_state(ids[i], notify);
states[i] = current;
finished += current != KERNEL_AIO_STATE_PROCESSING;
newly_complete |= current == KERNEL_AIO_STATE_COMPLETED || current == KERNEL_AIO_STATE_ABORTED;
}
return finished == num || (mode == KERNEL_AIO_WAIT_OR && newly_complete);
};
const auto start = std::chrono::steady_clock::now();
for (;;) {
const bool complete = collect_states(false);
const auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start).count();
const bool timed_out = usec != nullptr && elapsed >= *usec;
if (complete || timed_out) {
// A competing waiter can claim the newly completed request after the first scan.
if (collect_states(true)) {
if (usec != nullptr) {
*usec = elapsed < *usec ? *usec - static_cast<uint32_t>(elapsed) : 0;
}
return OK;
}
if (timed_out) {
return LibKernel::KERNEL_ERROR_ETIMEDOUT;
}
}
Common::Thread::SleepMicro(10);
waited += 10;
current = g_kernel_aio_state[id].load(std::memory_order_acquire);
}
}
return KernelAioPollRequest(id, state);
int KYTY_SYSV_ABI KernelAioWaitRequest(int32_t id, int32_t* state, uint32_t* usec) {
return KernelAioWaitRequests(&id, 1, state, KERNEL_AIO_WAIT_AND, usec);
}
int KYTY_SYSV_ABI KernelAioDeleteRequest(int32_t id, int32_t* ret) {
@@ -3376,6 +3402,7 @@ LIB_DEFINE(InitLibKernel_1) {
LIB_FUNC("2pOuoWoCxdk", KernelAioPollRequest);
LIB_FUNC("o7O4z3jwKzo", KernelAioPollRequests);
LIB_FUNC("KOF-oJbQVvc", KernelAioWaitRequest);
LIB_FUNC("lgK+oIWkJyA", KernelAioWaitRequests);
LIB_FUNC("XQ8C8y+de+E", KernelAioSubmitWriteCommands);
LIB_FUNC("nu4a0-arQis", KernelAioInitializeParam);
LIB_FUNC("il03nluKfMk", LibKernel::KernelRaiseException);
+1 -1
View File
@@ -206,7 +206,7 @@ static int KYTY_SYSV_ABI SystemServiceGetNoticeScreenSkipFlag(bool* value) {
return SYSTEM_SERVICE_ERROR_PARAMETER;
}
*value = false;
*value = Config::SkipNoticeScreen();
return OK;
}
+8
View File
@@ -87,6 +87,9 @@ static void PrintUsage() {
::printf(
" --trophy-notifications <true|false> Show trophy unlock toasts and play their sound.\n");
::printf(" --playgo-hack Use the supplied PlayGo stub fallback.\n");
::printf(
" --skip-notice-screen <true|false> Skip startup logos and notices in supported games.\n"
" Default: false.\n");
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
::printf(" --redzone Protect the guest SysV red zone.\n");
#endif
@@ -428,6 +431,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
return false;
}
} else if (arg == "--skip-notice-screen") {
if (!ParseBool(value, options.config.skip_notice_screen)) {
::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str());
return false;
}
} else if (arg == "--keymap") {
const auto split = value.find('=');
if (split == std::string::npos || split == 0 || split + 1 == value.size()) {
+28 -2
View File
@@ -169,12 +169,14 @@ void TestAioBatches() {
using Batch = int (KYTY_SYSV_ABI *)(int32_t *, int32_t, int32_t *);
using Single = int (KYTY_SYSV_ABI *)(int32_t, int32_t *);
using Wait = int (KYTY_SYSV_ABI *)(int32_t, int32_t *, uint32_t *);
using WaitBatch = int (KYTY_SYSV_ABI *)(int32_t *, int32_t, int32_t *, uint32_t, uint32_t *);
const auto submit = reinterpret_cast<Submit>(find("HgX7+AORI58"));
const auto poll = reinterpret_cast<Batch>(find("o7O4z3jwKzo"));
const auto erase = reinterpret_cast<Batch>(find("Ft3EtsZzAoY"));
const auto poll_one = reinterpret_cast<Single>(find("2pOuoWoCxdk"));
const auto erase_one = reinterpret_cast<Single>(find("5TgME6AYty4"));
const auto wait = reinterpret_cast<Wait>(find("KOF-oJbQVvc"));
const auto wait_batch = reinterpret_cast<WaitBatch>(find("lgK+oIWkJyA"));
constexpr char Payload[] = "AIO payload";
const int fd = FileSystem::KernelOpen("/savedata0/aio.dat", 0x602, 0777);
Check(fd >= 3 && FileSystem::KernelWrite(fd, Payload, sizeof(Payload)) == sizeof(Payload),
@@ -191,14 +193,22 @@ void TestAioBatches() {
}
ids[2] = -1;
std::array<int32_t, 3> states {-1, -1, -1};
Check(poll(ids.data(), ids.size(), states.data()) == OK &&
uint32_t timeout = 0;
Check(wait_batch(ids.data(), ids.size(), states.data(), 2, &timeout) == OK &&
states[0] == 3 && states[1] == 3 && states[2] == Kernel::KERNEL_ERROR_ESRCH,
"batch poll writes every state and per-request invalid-ID error");
"OR wait observes completed reads and invalid-ID errors even with a zero timeout");
Check(poll_one(ids[0], &states[0]) == OK && states[0] == (3 | 0x10000) &&
poll(ids.data(), 2, states.data()) == OK &&
states[0] == (3 | 0x10000) && states[1] == (3 | 0x10000) &&
wait(ids[0], &states[0], nullptr) == OK && states[0] == (3 | 0x10000),
"single and batch polls and waits share completion notification state");
timeout = 1000000;
Check(wait_batch(ids.data(), ids.size(), states.data(), 1, &timeout) == OK &&
states[0] == (3 | 0x10000) && states[1] == (3 | 0x10000) &&
states[2] == Kernel::KERNEL_ERROR_ESRCH && timeout <= 1000000 &&
wait_batch(ids.data(), ids.size(), states.data(), 2, nullptr) == OK &&
wait_batch(ids.data(), 1, states.data(), 0, nullptr) == OK,
"AND and OR waits return when all IDs are terminal and one-ID waits ignore mode");
Check(erase(ids.data(), ids.size(), states.data()) == OK &&
states[0] == OK && states[1] == OK && states[2] == Kernel::KERNEL_ERROR_ESRCH,
"batch deletion writes per-request results");
@@ -221,6 +231,22 @@ void TestAioBatches() {
}),
"maximum-sized batch returns all invalid-ID errors");
}
states.fill(42);
Check(wait_batch(nullptr, 1, states.data(), 1, nullptr) == Kernel::KERNEL_ERROR_EFAULT &&
wait_batch(ids.data(), 1, nullptr, 1, nullptr) == Kernel::KERNEL_ERROR_EFAULT &&
wait_batch(ids.data(), 0, states.data(), 1, nullptr) == Kernel::KERNEL_ERROR_EINVAL &&
wait_batch(ids.data(), 129, states.data(), 1, nullptr) == Kernel::KERNEL_ERROR_EINVAL &&
wait_batch(ids.data(), 2, states.data(), 0, nullptr) == Kernel::KERNEL_ERROR_EINVAL &&
wait_batch(ids.data(), 2, states.data(), 3, nullptr) == Kernel::KERNEL_ERROR_EINVAL &&
states == std::array<int32_t, 3> {42, 42, 42},
"invalid wait arguments do not modify outputs");
std::array<int32_t, 128> invalid_ids {};
std::array<int32_t, 128> errors {};
Check(wait_batch(invalid_ids.data(), invalid_ids.size(), errors.data(), 1, nullptr) == OK &&
std::all_of(errors.begin(), errors.end(), [](int32_t error) {
return error == Kernel::KERNEL_ERROR_ESRCH;
}),
"maximum-sized wait returns every invalid-ID error");
Check(FileSystem::KernelClose(fd) == OK, "close AIO read fixture");
}
+286 -10
View File
@@ -712,7 +712,7 @@ constexpr u32 EncodeVopcSdwa(u32 src0, u32 sdst = 0, u32 sd = 0,
constexpr u32 EncodeMubuf0(u32 opcode, u32 offset = 0, bool idxen = false,
bool offen = true, bool glc = false) {
return (0x38u << 26u) | ((opcode & 0x7fu) << 18u) |
return (0x38u << 26u) | ((opcode & 0xffu) << 18u) |
(offen ? (1u << 12u) : 0u) | (idxen ? (1u << 13u) : 0u) |
(glc ? (1u << 14u) : 0u) | (offset & 0xfffu);
}
@@ -22148,6 +22148,65 @@ TestCase Vop3MadI16CapturedSelectorsAndSaturation() {
return test;
}
TestCase Vop3Min3U16CapturedAndSelectors() {
using O = ShaderOpcode;
struct MinCase {
u32 lhs, rhs, third, expected;
u32 selectors = 0;
u32 dst = 17;
bool active = true;
};
constexpr u32 a = 0x0009ea60u, b = 0x0007c350u, c = 0x00059c40u;
const std::array<MinCase, 10> cases{{
{a, b, c, 0x0005beefu, 14}, // Captured: min(60000, 7, 5) into v17.hi.
{a, b, c, 0xa5a59c40u}, // Unsigned values above INT16_MAX.
{a, b, c, 0xa5a50009u, 1},
{a, b, c, 0xa5a50007u, 2},
{a, b, c, 0xa5a50005u, 4},
{a, b, c, 0x0005beefu, 15},
{a, b, c, 0x9c40ea60u, 8, 4}, // Destination aliases source 0.
{0xabcdffffu, 0xffff0000u, 0x98768000u, 0xa5a50000u},
{0x0000ffffu, 0x7fffffffu, 0x8000ffffu, 0xa5a5ffffu},
{a, b, c, 0xa5a5beefu, 14, 17, false},
}};
TestCase test;
test.name = "Vop3Min3U16CapturedAndSelectors";
for (const auto &entry : cases) {
test.initial.insert(test.initial.end(), {entry.lhs, entry.rhs, entry.third});
}
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 : {4u, 0u, 7u}) {
AppendVMovU32(&code, 30, offset);
AppendBufferLoadDword(&code, reg, 30); // Runtime inputs prevent folding.
offset += 4u;
}
AppendVMovLiteral(&code, 17, 0xa5a5beefu);
if (!entry.active) {
code.push_back(EncodeSop1(0x04, 24, 126)); // Preserve the initial active lanes.
code.push_back(EncodeSop1(0x04, 126, 128));
}
if (i == 0u) {
code.insert(code.end(), {0xd7537011u, 0x041e0104u});
} else {
AppendVop3(&code, 0x353, entry.dst, Vgpr(4), Vgpr(0), Vgpr(7), 0,
entry.selectors);
}
if (!entry.active) code.push_back(EncodeSop1(0x04, 126, 24));
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_MIN3_U16,
O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.decoded_counts = {{"V_MIN3_U16", cases.size()}};
test.required_spirv = {"OpULessThan"};
return test;
}
TestCase Vop3Med3I16Captured() {
using O = ShaderOpcode;
@@ -24729,10 +24788,10 @@ TestCase VectorCompareInteger64Edges() {
const auto lhs = pairs[i][0], rhs = pairs[i][1];
const auto signed_lhs = std::bit_cast<int64_t>(lhs);
const auto signed_rhs = std::bit_cast<int64_t>(rhs);
for (const auto [opcode, value] : std::array<std::pair<u32, bool>, 5>{{
{0xa1, signed_lhs < signed_rhs}, {0xa3, signed_lhs <= signed_rhs},
{0xa5, lhs != rhs},
{0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}}}) {
for (const auto [opcode, value] : std::array<std::pair<u32, bool>, 9>{{
{0xa0, false}, {0xa1, signed_lhs < signed_rhs},
{0xa3, signed_lhs <= signed_rhs}, {0xa5, lhs != rhs}, {0xa7, true},
{0xe0, false}, {0xe3, lhs <= rhs}, {0xe6, lhs >= rhs}, {0xe7, true}}}) {
test.code.push_back(EncodeVopc(opcode, Vgpr(1), 3));
store_mask(106, value);
AppendVop3(&test.code, opcode, 20, Vgpr(1), 8);
@@ -24750,6 +24809,12 @@ TestCase VectorCompareInteger64Edges() {
AppendVop3(&test.code, 0xa3, 20, Vgpr(1), 255);
test.code.push_back(0xffffffffu);
store_mask(20, signed_lhs <= -1);
test.code.push_back(EncodeSop1(0x04, 10, 126));
AppendVop3(&test.code, 0xb2, 126, 255, Vgpr(3)); // CMPX_EQ_I64 sign-extends -1.
test.code.push_back(0xffffffffu);
test.code.push_back(EncodeSop1(0x04, 20, 126));
test.code.push_back(EncodeSop1(0x04, 126, 10));
store_mask(20, -1 == signed_rhs);
// Captured 8395e43f382309df PC 0x20cc: s[2:3] = (s[0:1] != 0) & EXEC.
// Save the test buffer descriptor while its SGPRs hold the captured operands.
test.code.push_back(EncodeSop1(0x04, 32, 0));
@@ -24765,8 +24830,9 @@ TestCase VectorCompareInteger64Edges() {
AppendEnd(&test.code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32,
O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64, O::V_CMP_NE_I64,
O::V_CMP_LE_U64, O::V_CMP_GE_U64,
O::V_CMP_F_I64, O::V_CMP_LT_I64, O::V_CMP_LE_I64, O::V_CMP_EQ_I64,
O::V_CMP_NE_I64, O::V_CMP_T_I64, O::V_CMPX_EQ_I64,
O::V_CMP_F_U64, O::V_CMP_LE_U64, O::V_CMP_GE_U64, O::V_CMP_T_U64,
O::S_MOV_B64, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpSLessThan", "OpULessThanEqual", "OpUGreaterThanEqual"};
return test;
@@ -24774,7 +24840,7 @@ TestCase VectorCompareInteger64Edges() {
TestCase VectorCompareExecWaveMasks(u32 wave_size) {
using O = ShaderOpcode;
// Rows exercise signed 16-bit truncation, full-width unsigned ordering, and
// Rows exercise signed 16-bit truncation, full-width integer ordering, and
// ordered FP64 comparisons (including NaN, signed zero, and subnormals).
constexpr std::array<std::array<std::array<uint64_t, 2>, 8>, 3> inputs{{
{{{0x1111ffff, 0x22220000}, {0x33338000, 0x44447fff},
@@ -24795,8 +24861,13 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) {
Compare{0x99, O::V_CMPX_LT_I16, 0}, Compare{0x9a, O::V_CMPX_EQ_I16, 0},
Compare{0x9b, O::V_CMPX_LE_I16, 0}, Compare{0x9c, O::V_CMPX_GT_I16, 0},
Compare{0x9d, O::V_CMPX_NE_I16, 0}, Compare{0x9e, O::V_CMPX_GE_I16, 0},
Compare{0xf3, O::V_CMPX_LE_U64, 1}, Compare{0x33, O::V_CMPX_LE_F64, 2},
Compare{0x36, O::V_CMPX_GE_F64, 2}};
Compare{0xb0, O::V_CMPX_F_I64, 1}, Compare{0xb1, O::V_CMPX_LT_I64, 1},
Compare{0xb2, O::V_CMPX_EQ_I64, 1}, Compare{0xb3, O::V_CMPX_LE_I64, 1},
Compare{0xb7, O::V_CMPX_T_I64, 1}, Compare{0xf0, O::V_CMPX_F_U64, 1},
Compare{0xf1, O::V_CMPX_LT_U64, 1}, Compare{0xf2, O::V_CMPX_EQ_U64, 1},
Compare{0xf3, O::V_CMPX_LE_U64, 1}, Compare{0xf4, O::V_CMPX_GT_U64, 1},
Compare{0xf6, O::V_CMPX_GE_U64, 1}, Compare{0xf7, O::V_CMPX_T_U64, 1},
Compare{0x33, O::V_CMPX_LE_F64, 2}, Compare{0x36, O::V_CMPX_GE_F64, 2}};
constexpr u32 low_exec = 0xeeeeeeeeu, high_exec = 0x77777777u;
constexpr u32 vcc_lo = 0x12345678u, vcc_hi = 0x89abcdefu;
TestCase test;
@@ -24832,7 +24903,15 @@ TestCase VectorCompareExecWaveMasks(u32 wave_size) {
case 0x9c: result = a > b; break;
case 0x9d: result = a != b; break;
case 0x9e: result = a >= b; break;
case 0xb0: case 0xf0: result = false; break;
case 0xb1: result = std::bit_cast<int64_t>(pair[0]) < std::bit_cast<int64_t>(pair[1]); break;
case 0xb2: case 0xf2: result = pair[0] == pair[1]; break;
case 0xb3: result = std::bit_cast<int64_t>(pair[0]) <= std::bit_cast<int64_t>(pair[1]); break;
case 0xb7: case 0xf7: result = true; break;
case 0xf1: result = pair[0] < pair[1]; break;
case 0xf3: result = pair[0] <= pair[1]; break;
case 0xf4: result = pair[0] > pair[1]; break;
case 0xf6: result = pair[0] >= pair[1]; break;
case 0x33: result = std::bit_cast<double>(pair[0]) <= std::bit_cast<double>(pair[1]); break;
case 0x36: result = std::bit_cast<double>(pair[0]) >= std::bit_cast<double>(pair[1]); break;
}
@@ -28696,6 +28775,108 @@ TestCase BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords() {
return test;
}
std::vector<TestCase> BufferLoadFormatD16XCases() {
using F = Prospero::BufferFormat;
using O = ShaderOpcode;
struct LoadCase {
const char *name;
F format;
u32 input;
u32 expected;
u32 selector = 4;
u32 index = 0;
bool active = true;
bool overlap = false;
};
constexpr LoadCase inputs[] = {
{"BufferLoadFormatD16XFloat16", F::k16Float, 0x7e63c000u, 0xabcdc000u},
{"BufferLoadFormatD16XFloat32", F::k32Float, 0x3f000000u, 0xabcd3800u},
{"BufferLoadFormatD16XUint32", F::k32UInt, 0x1234fffeu, 0xabcdffffu},
{"BufferLoadFormatD16XUintAtMax", F::k32UInt, 0x0000ffffu, 0xabcdffffu},
{"BufferLoadFormatD16XUintMax32", F::k32UInt, 0xffffffffu, 0xabcdffffu},
{"BufferLoadFormatD16XSint32", F::k32SInt, 0xffff8001u, 0xabcd8001u},
{"BufferLoadFormatD16XSintAboveMax", F::k32SInt, 0x00008000u, 0xabcd7fffu},
{"BufferLoadFormatD16XSintBelowMin", F::k32SInt, 0xffff7fffu, 0xabcd8000u},
{"BufferLoadFormatD16XSintAtMax", F::k32SInt, 0x00007fffu, 0xabcd7fffu},
{"BufferLoadFormatD16XSintAtMin", F::k32SInt, 0xffff8000u, 0xabcd8000u},
{"BufferLoadFormatD16XSintMax32", F::k32SInt, 0x7fffffffu, 0xabcd7fffu},
{"BufferLoadFormatD16XSintMin32", F::k32SInt, 0x80000000u, 0xabcd8000u},
{"BufferLoadFormatD16XUnorm8", F::k8UNorm, 0xffu, 0xabcd3c00u},
{"BufferLoadFormatD16XSnorm16", F::k16SNorm, 0x8001u, 0xabcdbc00u},
{"BufferLoadFormatD16XFloatOne", F::k32Float, 0u, 0xabcd3c00u, 1},
{"BufferLoadFormatD16XOobFloatOne", F::k32Float, 0u, 0xabcd3c00u, 1, 4},
{"BufferLoadFormatD16XOobUintOne", F::k32UInt, 0u, 0xabcd0001u, 1, 4},
{"BufferLoadFormatD16XOobZero", F::k32Float, 0u, 0xabcd0000u, 4, 4},
{"BufferLoadFormatD16XInactive", F::k32Float, 0u, 0xabcd1234u, 4, 0, false},
{"BufferLoadFormatD16XOverlappingAddress", F::k16UInt, 7u, 7u, 4, 0, true, true},
};
std::vector<TestCase> cases;
for (const auto &input : inputs) {
TestCase test;
test.name = input.name;
AppendVMovLiteral(&test.code, 0, input.overlap ? input.index : 0xabcd1234u);
AppendVMovU32(&test.code, 1, input.index);
if (!input.active) {
test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0)));
}
test.code.push_back(EncodeMubuf0(0x80u, 0, true, false));
test.code.push_back(EncodeMubuf1(0, 0, input.overlap ? 0 : 1));
if (!input.active) {
test.code.push_back(EncodeSMovB32(126, InlineU32(1)));
test.code.push_back(EncodeSMovB32(127, InlineU32(0)));
}
AppendStoreVgpr(&test.code, 0, 2);
AppendEnd(&test.code);
test.initial = {input.input, 0xdeadbeefu, 0u, 0xcafef00du};
test.expected = {input.input, 0xdeadbeefu, input.expected, 0xcafef00du};
test.user_data = MakeStructuredStorageBufferData(4, 4, false, BufferFormat(input.format));
test.user_data[3] = (test.user_data[3] & ~7u) | input.selector;
test.has_user_data = true;
test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_D16_X,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
cases.push_back(std::move(test));
}
return cases;
}
std::vector<TestCase> BufferStoreFormatD16XCases() {
using F = Prospero::BufferFormat;
using O = ShaderOpcode;
struct StoreCase {
const char *name;
F format;
u32 input;
u32 expected;
};
constexpr StoreCase inputs[] = {
{"BufferStoreFormatD16XFloat16", F::k16Float, 0x7e63c000u, 0xffffc000u},
{"BufferStoreFormatD16XFloat32", F::k32Float, 0x7e633800u, 0x3f000000u},
{"BufferStoreFormatD16XUint32", F::k32UInt, 0x7e63fffeu, 0x0000fffeu},
{"BufferStoreFormatD16XSint32", F::k32SInt, 0x7e63fffeu, 0xfffffffeu},
{"BufferStoreFormatD16XUnorm8", F::k8UNorm, 0x7e633800u, 0xffffff80u},
{"BufferStoreFormatD16XSnorm16", F::k16SNorm, 0x7e63b800u, 0xffffc000u},
{"BufferStoreFormatD16XZeroFill", F::k16_16Float, 0x7e633800u, 0x00003800u},
};
std::vector<TestCase> cases;
for (const auto &input : inputs) {
TestCase test;
test.name = input.name;
AppendVMovLiteral(&test.code, 66, input.input);
AppendVMovU32(&test.code, 65, 1);
// Match the captured CS824586f35db5c80d instruction at pc 0x484.
test.code.push_back(EncodeMubuf0(0x84u, 0, true, false));
test.code.push_back(EncodeMubuf1(66, 0, 65));
AppendEnd(&test.code);
test.initial = {0xdeadbeefu, 0xffffffffu, 0xcafef00du};
test.expected = {0xdeadbeefu, input.expected, 0xcafef00du};
test.user_data = MakeStructuredStorageBufferData(4, 3, false, BufferFormat(input.format));
test.has_user_data = true;
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_D16_X, O::S_ENDPGM};
cases.push_back(std::move(test));
}
return cases;
}
std::vector<TestCase> FormattedStoreConversionCases() {
using F = Prospero::BufferFormat;
using O = ShaderOpcode;
@@ -32243,6 +32424,66 @@ TestCase DsAppendUsesEncodedGdsSelector() {
return test;
}
TestCase DsOrderedCountAddressAndExec(u32 wave_size, bool pixel_counter) {
using O = ShaderOpcode;
TestCase test;
test.name = pixel_counter
? (wave_size == 64 ? "DsOrderedCountPixelAddressWave64"
: "DsOrderedCountPixelAddressWave32")
: (wave_size == 64 ? "DsOrderedCountAddressAndExecWave64"
: "DsOrderedCountAddressAndExecWave32");
auto &code = test.code;
// Both the base and OFFSET0 are aligned independently; M0's low bits are
// a wave index, or a pixel packer ID, rather than a byte address or size.
AppendSMovLiteral(&code, 124, 0x01030003u);
code.push_back(EncodeSop1(0x04, 10, 126)); // Save the full EXEC mask.
code.push_back(EncodeVop2(0x25, 2, InlineU32(1), 0)); // ADDR value = lane + 1.
AppendVMovU32(&code, 9, 99); // DATA0 must not supply the count.
const u32 selected_lane = wave_size == 64 ? 47u : 7u;
const std::array<uint64_t, 3> masks = {
wave_size == 64 ? UINT64_MAX : uint64_t{UINT32_MAX},
uint64_t{1} << selected_lane, 0};
u32 counter = 1000;
for (bool exchange : {false, true}) {
for (size_t mode = 0; mode < masks.size(); ++mode) {
AppendVMovU32(&code, 3, 0xdeadbeefu);
AppendSMovLiteral(&code, 126, static_cast<u32>(masks[mode]));
AppendSMovLiteral(&code, 127, static_cast<u32>(masks[mode] >> 32u));
const u32 release_done = exchange && mode == 2 ? 3u : 0u;
const u32 offset1 = release_done | (pixel_counter ? 4u : 0u) |
(exchange ? 0x10u : 0u);
code.push_back(EncodeDs0(0x3f, (offset1 << 8u) | 7u, true));
code.push_back(EncodeDs1(3, 9, 2));
code.push_back(EncodeSopp(0x0c, 0)); // Wait for the returned GDS value.
code.push_back(EncodeSop1(0x04, 126, 10)); // Restore EXEC before reading all lanes.
AppendStoreVgprAtLaneDwordOffset(&code, 3, 0,
static_cast<u32>(test.expected.size()));
test.expected.insert(test.expected.end(), wave_size, counter);
if (mode != 2) {
const u32 value = mode == 0 ? 1u : selected_lane + 1u;
counter = exchange ? value : counter + value;
}
}
}
AppendEnd(&code);
test.initial.assign(test.expected.size(), 0xdeadbeefu);
const u32 counter_address = pixel_counter ? 0x110u : 0x104u;
test.gds_initial.assign(0x120u / sizeof(u32), 100);
test.gds_initial[counter_address / sizeof(u32)] = 1000;
test.expected_gds = test.gds_initial;
test.expected_gds[counter_address / sizeof(u32)] = counter;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::V_ADD_NC_U32, O::V_MOV_B32,
O::DS_ORDERED_COUNT, O::S_WAITCNT, O::V_LSHLREV_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
return test;
}
TestCase DsAppendConsumeGdsRegionBounds() {
using O = ShaderOpcode;
struct Access {
@@ -35277,6 +35518,7 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop3CvtPkI16I32Captured);
AddCase(Vop3MulLoU16CapturedAndSelectors);
AddCase(Vop3MadI16CapturedSelectorsAndSaturation);
AddCase(Vop3Min3U16CapturedAndSelectors);
AddCase(Vop3Med3I16Captured);
AddCase(Vop2SdwaMinU32PreservesWordDestination);
AddCase(VectorShiftCountsMaskLowBits);
@@ -35467,6 +35709,12 @@ std::vector<TestCase> MakeCases() {
AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents);
AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors);
AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords);
for (auto &test : BufferLoadFormatD16XCases()) {
cases.push_back(std::move(test));
}
for (auto &test : BufferStoreFormatD16XCases()) {
cases.push_back(std::move(test));
}
for (auto &test : FormattedStoreConversionCases()) {
cases.push_back(std::move(test));
}
@@ -35538,6 +35786,10 @@ std::vector<TestCase> MakeCases() {
cases.push_back(DsAppendAllocatesAcrossWaves(32));
cases.push_back(DsAppendAllocatesAcrossWaves(64));
AddCase(DsAppendUsesEncodedGdsSelector);
for (u32 wave_size : {32, 64}) {
cases.push_back(DsOrderedCountAddressAndExec(wave_size, false));
cases.push_back(DsOrderedCountAddressAndExec(wave_size, true));
}
AddCase(DsAppendConsumeGdsRegionBounds);
AddCase(DsGdsSubdwordAndAtomicWrites);
AddCase(DsReadWrite2Variants);
@@ -40651,6 +40903,15 @@ int main(int argc, char **argv) {
vulkan.CheckNativeIndirectDispatch();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--integer64-compare-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorCompareInteger64Edges());
RunCase(&vulkan, VectorCompareExecWaveMasks(32));
RunCase(&vulkan, VectorCompareExecWaveMasks(64));
RunCase(&vulkan, VectorVopcCmpxNeU64CapturedExecMask());
RunCase(&vulkan, VectorVop3CmpxNeI64CapturedExecMask());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--new-opcodes-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorCompareInteger64Edges());
@@ -40716,6 +40977,12 @@ int main(int argc, char **argv) {
RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents());
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords());
for (const auto &test : BufferLoadFormatD16XCases()) {
RunCase(&vulkan, test);
}
for (const auto &test : BufferStoreFormatD16XCases()) {
RunCase(&vulkan, test);
}
for (const auto &test : FormattedStoreConversionCases()) {
RunCase(&vulkan, test);
}
@@ -41059,6 +41326,14 @@ int main(int argc, char **argv) {
}
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--ds-ordered-count-only") == 0) {
VulkanHarness vulkan;
for (u32 wave_size : {32, 64}) {
RunCase(&vulkan, DsOrderedCountAddressAndExec(wave_size, false));
RunCase(&vulkan, DsOrderedCountAddressAndExec(wave_size, true));
}
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--ds-atomics-only") == 0) {
VulkanHarness vulkan;
for (const auto &test : MakeCases()) {
@@ -41188,6 +41463,7 @@ int main(int argc, char **argv) {
}
if (argc == 2 && std::strcmp(argv[1], "--med3-i16-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop3Min3U16CapturedAndSelectors());
RunCase(&vulkan, Vop3Med3I16Captured());
return 0;
}