mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 20:36:58 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2413fc2eb | |||
| 96eb681b90 | |||
| 26cce9909a | |||
| 5af370c257 | |||
| 67d7db685a | |||
| bb98f26433 | |||
| cc44a43ebc | |||
| 2a18e66a4c | |||
| e7cfe5a2f7 | |||
| 7415febd93 | |||
| 7492cb9577 | |||
| 60e53a39cc | |||
| 1b21161aac | |||
| 1195af2510 | |||
| 9a5658c038 | |||
| 199d0a9d92 |
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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><html><head/><body><p>Edit configuration</p></body></html></string>
|
||||
<string>Edit game config</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@@ -150,7 +150,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Delete configuration</p></body></html></string>
|
||||
<string>Clear game config</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -206,7 +206,7 @@ static int KYTY_SYSV_ABI SystemServiceGetNoticeScreenSkipFlag(bool* value) {
|
||||
return SYSTEM_SERVICE_ERROR_PARAMETER;
|
||||
}
|
||||
|
||||
*value = false;
|
||||
*value = Config::SkipNoticeScreen();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user