mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-29 08:41:20 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7a1fac898 | ||
|
|
bc193f5272 | ||
|
|
17100ec968 | ||
|
|
8ba2caf58a | ||
|
|
48b52d31b5 | ||
|
|
840b9f5753 | ||
|
|
05057c9441 |
No files matched your search
Vendored
+1724
File diff suppressed because it is too large.
Load diff
@@ -500,6 +500,10 @@ add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(png_enc_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/PngEncTests.cpp")
|
||||
target_link_libraries(png_enc_tests common fmt::fmt)
|
||||
target_include_directories(png_enc_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(ces_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/CesTests.cpp")
|
||||
target_link_libraries(ces_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(ces_tests PRIVATE ${inc_headers})
|
||||
@@ -605,6 +609,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME png_enc COMMAND $<TARGET_FILE:png_enc_tests>)
|
||||
add_test(NAME ces COMMAND $<TARGET_FILE:ces_tests>)
|
||||
add_test(NAME save_data_dialog COMMAND $<TARGET_FILE:save_data_dialog_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
@@ -680,6 +685,7 @@ if(BUILD_TESTING)
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
save_data_memory_tests
|
||||
png_enc_tests
|
||||
ces_tests
|
||||
save_data_dialog_tests
|
||||
http_uri_parse_tests
|
||||
|
||||
@@ -47,6 +47,10 @@ const std::string& GetAudioInputDevice() {
|
||||
return g_config->audio_input_device;
|
||||
}
|
||||
|
||||
const std::optional<ControllerColor>& GetControllerColor() {
|
||||
return g_config->controller_color;
|
||||
}
|
||||
|
||||
PresentMode GetPresentMode() {
|
||||
return g_config->present_mode;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,6 +28,7 @@ enum class LogDirection { Silent, Console, File };
|
||||
enum class PresentMode { Fifo, Mailbox, Immediate };
|
||||
|
||||
using Keymap = std::vector<std::string>;
|
||||
using ControllerColor = std::array<uint8_t, 3>;
|
||||
|
||||
constexpr uint32_t DEFAULT_CONSOLE_LANGUAGE = 1;
|
||||
constexpr uint32_t MAX_CONSOLE_LANGUAGE = 29;
|
||||
@@ -44,6 +47,7 @@ struct ConfigOptions {
|
||||
std::string user_name = "Kyty";
|
||||
int32_t user_id = DEFAULT_USER_ID;
|
||||
std::string audio_input_device;
|
||||
std::optional<ControllerColor> controller_color;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
@@ -81,6 +85,7 @@ uint32_t GetScreenHeight();
|
||||
const std::string& GetUserName();
|
||||
int32_t GetUserId();
|
||||
const std::string& GetAudioInputDevice();
|
||||
const std::optional<ControllerColor>& GetControllerColor();
|
||||
PresentMode GetPresentMode();
|
||||
int32_t GetGpuIndex();
|
||||
bool FullscreenEnabled();
|
||||
|
||||
@@ -62,7 +62,6 @@ public:
|
||||
|
||||
void BufferInit();
|
||||
void BufferFlush();
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
HW::Context& GetCtx() { return m_ctx; }
|
||||
HW::UserConfig& GetUcfg() { return m_ucfg; }
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Libs::Graphics {
|
||||
|
||||
static thread_local CommandProcessor* g_current_processor = nullptr;
|
||||
static thread_local Pm4Execution* g_current_execution = nullptr;
|
||||
static thread_local bool g_gpu_mutex_owned = false;
|
||||
static thread_local bool g_gpu_thread = false;
|
||||
static thread_local GuestGpu* g_gpu_state = nullptr;
|
||||
|
||||
@@ -54,27 +53,6 @@ struct DrawIndexedIndirectArgs {
|
||||
uint32_t start_instance_location;
|
||||
};
|
||||
|
||||
class GpuMutexLock final {
|
||||
public:
|
||||
explicit GpuMutexLock(Common::Mutex& mutex): m_mutex(mutex) {
|
||||
if (g_gpu_mutex_owned) {
|
||||
EXIT("recursive GPU mutex acquisition\n");
|
||||
}
|
||||
g_gpu_mutex_owned = true;
|
||||
m_mutex.Lock();
|
||||
}
|
||||
~GpuMutexLock() {
|
||||
if (!g_gpu_mutex_owned) {
|
||||
EXIT("invalid GPU mutex release\n");
|
||||
}
|
||||
m_mutex.Unlock();
|
||||
g_gpu_mutex_owned = false;
|
||||
}
|
||||
|
||||
private:
|
||||
Common::Mutex& m_mutex;
|
||||
};
|
||||
|
||||
static bool GraphicsRunDebugDumpEnabled() {
|
||||
return Config::GraphicsDebugDumpEnabled() &&
|
||||
Config::GetPrintfDirection() != Config::LogDirection::Silent;
|
||||
@@ -84,6 +62,7 @@ GuestGpu::GuestGpu(RenderContext& renderer): m_renderer(renderer) {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
GraphicsInitJmpTables();
|
||||
m_gfx_cp = std::make_unique<CommandProcessor>(renderer, 0);
|
||||
m_gfx_cp->Reset();
|
||||
m_thread = std::jthread(ThreadRun, this);
|
||||
}
|
||||
|
||||
@@ -160,20 +139,16 @@ void GuestGpu::Submit(std::span<const uint32_t> draw_commands,
|
||||
if (draw_commands.empty()) {
|
||||
return;
|
||||
}
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
Submission submission;
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::Graphics;
|
||||
submission.queue_id = 0;
|
||||
submission.commands = draw_commands;
|
||||
submission.constant_commands = constant_commands;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
m_graphics_done = false;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands) {
|
||||
EXIT_IF(commands.empty());
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(queue < ComputeQueueBase || queue >= ComputeQueueBase + ComputeQueueCount);
|
||||
|
||||
@@ -186,22 +161,21 @@ void GuestGpu::SubmitCompute(uint32_t queue, std::span<const uint32_t> commands)
|
||||
}
|
||||
|
||||
void GuestGpu::SubmitFlipPreparation(uint64_t request_id) {
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
Submission submission;
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::FlipPreparation;
|
||||
submission.queue_id = 0;
|
||||
submission.reset_processor = m_graphics_done;
|
||||
submission.flip_request_id = request_id;
|
||||
m_graphics_done = false;
|
||||
Enqueue(std::move(submission));
|
||||
}
|
||||
|
||||
void GuestGpu::Done() {
|
||||
GpuMutexLock lock(m_submission_mutex);
|
||||
if (!IsGpuThread()) {
|
||||
WaitForIdle();
|
||||
}
|
||||
m_graphics_done = true;
|
||||
void GuestGpu::SuspendPoint() {
|
||||
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||
// Do not hold a queue lock while waiting: asynchronous work may be needed to
|
||||
// finish the preceding graphics frame. The first point returns immediately.
|
||||
m_suspend_point_ready->acquire();
|
||||
Submission submission;
|
||||
submission.type = SubmissionType::SuspendPoint;
|
||||
Enqueue(std::move(submission));
|
||||
m_done_num++;
|
||||
}
|
||||
|
||||
@@ -229,6 +203,9 @@ void CommandProcessor::Reset() {
|
||||
m_context_state_pushed = false;
|
||||
m_index_type_and_size = 0;
|
||||
m_index_buffer_size = 0;
|
||||
m_index_base_addr = 0;
|
||||
m_num_instances = 1;
|
||||
m_predicate_skip = false;
|
||||
m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
m_draw_indirect_args_base_addr = 0;
|
||||
m_dispatch_indirect_args_base_addr = 0;
|
||||
@@ -268,10 +245,6 @@ void CommandProcessor::BufferFlush() {
|
||||
GetScheduler().Flush();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferFlushAndWait() {
|
||||
GetScheduler().FlushAndWait();
|
||||
}
|
||||
|
||||
void CommandProcessor::BufferWait() {
|
||||
BufferInit();
|
||||
GetScheduler().Finish();
|
||||
@@ -459,6 +432,7 @@ void GuestGpu::Enqueue(Submission submission) {
|
||||
}
|
||||
|
||||
void GuestGpu::WaitForIdle() {
|
||||
EXIT_IF(IsGpuThread() || CommandScheduler::InDeferredOperation());
|
||||
Common::LockGuard lock(m_queue_mutex);
|
||||
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
|
||||
m_idle.Wait(&m_queue_mutex);
|
||||
@@ -564,11 +538,7 @@ void GuestGpu::ThreadRun(void* data) {
|
||||
|
||||
bool GuestGpu::Process(Submission& submission) {
|
||||
const bool first_slice = !submission.started;
|
||||
auto& cp = GetProcessor(submission.queue_id);
|
||||
|
||||
if (first_slice && submission.reset_processor) {
|
||||
cp.Reset();
|
||||
}
|
||||
auto& cp = GetProcessor(submission.queue_id);
|
||||
|
||||
if (first_slice) {
|
||||
submission.started = true;
|
||||
@@ -645,6 +615,13 @@ bool GuestGpu::Process(Submission& submission) {
|
||||
m_renderer.RunGarbageCollector();
|
||||
cp.PrepareCpuFlip(submission.flip_request_id);
|
||||
break;
|
||||
case SubmissionType::SuspendPoint:
|
||||
cp.EmitGlobalBarrier();
|
||||
m_renderer.GetCommandScheduler().DeferPriorityOperation(
|
||||
[ready = m_suspend_point_ready] { ready->release(); });
|
||||
cp.BufferFlush();
|
||||
cp.Reset();
|
||||
break;
|
||||
}
|
||||
|
||||
return complete;
|
||||
@@ -849,9 +826,7 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t
|
||||
}
|
||||
} break;
|
||||
case 0x03:
|
||||
if (wait_op != 0) {
|
||||
BufferFlushAndWait();
|
||||
}
|
||||
// The wait selector applies only to Z-pass query readiness.
|
||||
EXIT_NOT_IMPLEMENTED(address == nullptr);
|
||||
value = *reinterpret_cast<const volatile uint64_t*>(address);
|
||||
break;
|
||||
@@ -1034,9 +1009,6 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
m_sh_ctx.SetCsWaveSize(Pm4::ComputeWaveSize(mode));
|
||||
|
||||
uint32_t frame_num = 0;
|
||||
// uint32_t local_x = 1;
|
||||
// uint32_t local_y = 1;
|
||||
// uint32_t local_z = 1;
|
||||
|
||||
{
|
||||
frame_num = m_renderer.GetGpu().GetFrameNum();
|
||||
@@ -1058,35 +1030,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
|
||||
}
|
||||
}
|
||||
|
||||
const auto& cs = m_sh_ctx.GetCs().cs_regs;
|
||||
// local_x = std::max(cs.num_thread_x, 1u);
|
||||
// local_y = std::max(cs.num_thread_y, 1u);
|
||||
// local_z = std::max(cs.num_thread_z, 1u);
|
||||
m_renderer.GetRenderExecutor().DispatchDirect(m_submit_id, CurrentBuffer(), thread_group_x,
|
||||
thread_group_y, thread_group_z, mode);
|
||||
}
|
||||
|
||||
/*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
|
||||
auto group_count = [](uint32_t threads, uint32_t group_size) {
|
||||
return (threads == 0
|
||||
? 0u
|
||||
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
|
||||
};
|
||||
|
||||
auto groups_x = thread_group_x;
|
||||
auto groups_y = thread_group_y;
|
||||
auto groups_z = thread_group_z;
|
||||
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
|
||||
groups_x = group_count(thread_group_x, local_x);
|
||||
groups_y = group_count(thread_group_y, local_y);
|
||||
groups_z = group_count(thread_group_z, local_z);
|
||||
}
|
||||
|
||||
const uint64_t invocations =
|
||||
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
|
||||
if (invocations != 0) {
|
||||
BufferFlushAndWait();
|
||||
}*/
|
||||
}
|
||||
|
||||
void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <semaphore>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
@@ -32,11 +33,14 @@ public:
|
||||
void SendCommandSync(Common::UniqueFunction<void>&& command);
|
||||
|
||||
// Submitted command memory is borrowed and must remain valid until GPU execution completes.
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
void Done();
|
||||
void Submit(std::span<const uint32_t> draw_commands,
|
||||
std::span<const uint32_t> constant_commands);
|
||||
void SubmitCompute(uint32_t queue, std::span<const uint32_t> commands);
|
||||
void SubmitFlipPreparation(uint64_t request_id);
|
||||
// Insert an ordered graphics drain; only a previous suspend point can block the caller.
|
||||
void SuspendPoint();
|
||||
// Wait for guest command processing, including all compute queues (not native GPU completion).
|
||||
void WaitForIdle();
|
||||
[[nodiscard]] int GetFrameNum() const;
|
||||
|
||||
[[nodiscard]] static bool IsGpuThread() noexcept;
|
||||
@@ -48,7 +52,7 @@ private:
|
||||
static constexpr uint32_t ComputeQueueBase = 0x20;
|
||||
static constexpr uint32_t QueueCount = 1 + ComputeQueueCount;
|
||||
|
||||
enum class SubmissionType { Graphics, Compute, FlipPreparation };
|
||||
enum class SubmissionType { Graphics, Compute, FlipPreparation, SuspendPoint };
|
||||
|
||||
struct Submission {
|
||||
SubmissionType type = SubmissionType::Graphics;
|
||||
@@ -57,7 +61,6 @@ private:
|
||||
std::span<const uint32_t> constant_commands;
|
||||
Pm4Execution command_execution;
|
||||
Pm4Execution constant_execution;
|
||||
bool reset_processor = false;
|
||||
bool started = false;
|
||||
bool command_complete = false;
|
||||
bool constant_complete = false;
|
||||
@@ -66,14 +69,12 @@ private:
|
||||
};
|
||||
|
||||
void Enqueue(Submission submission);
|
||||
void WaitForIdle();
|
||||
void ProcessCommands();
|
||||
bool Process(Submission& submission);
|
||||
static void ThreadRun(void* data);
|
||||
CommandProcessor& GetProcessor(uint32_t queue_id);
|
||||
|
||||
RenderContext& m_renderer;
|
||||
Common::Mutex m_submission_mutex;
|
||||
Common::Mutex m_queue_mutex;
|
||||
std::mutex m_shutdown_mutex;
|
||||
Common::CondVar m_work_available;
|
||||
@@ -84,10 +85,12 @@ private:
|
||||
uint32_t m_next_queue = 0;
|
||||
uint32_t m_submission_count = 0;
|
||||
bool m_processing = false;
|
||||
bool m_graphics_done = true;
|
||||
bool m_accepting = true;
|
||||
bool m_stopping = false;
|
||||
bool m_shutdown_complete = false;
|
||||
// Completion callbacks can outlive GuestGpu during renderer shutdown.
|
||||
std::shared_ptr<std::binary_semaphore> m_suspend_point_ready =
|
||||
std::make_shared<std::binary_semaphore>(1);
|
||||
|
||||
std::unique_ptr<CommandProcessor> m_gfx_cp;
|
||||
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
|
||||
|
||||
@@ -1814,6 +1814,38 @@ KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* stat
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutAllowOutputResolutionWqhdDetection(int handle) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
return VIDEO_OUT_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2) {
|
||||
PRINT_NAME();
|
||||
|
||||
static std::atomic_bool logged {false};
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("\t handle = %d\n"
|
||||
"\t arg1 = 0x%016" PRIx64 "\n"
|
||||
"\t arg2 = 0x%016" PRIx64 "\n",
|
||||
handle, arg1, arg2);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
KYTY_SYSV_ABI int VideoOutVrrUnpegFromFixedRate(int handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
static std::atomic_bool logged {false};
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("\t handle = %d\n", handle);
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
static int ValidateOutputConfig(int handle, uint64_t mode, const VideoOutOutputOptions* options,
|
||||
void* reserved_ptr, uint64_t reserved) {
|
||||
if (!DriverState().IsOpened(handle)) {
|
||||
|
||||
@@ -90,6 +90,9 @@ KYTY_SYSV_ABI int VideoOutGetEventData(const LibKernel::EventQueue::KernelEvent*
|
||||
KYTY_SYSV_ABI int VideoOutGetEventCount(const LibKernel::EventQueue::KernelEvent* ev);
|
||||
KYTY_SYSV_ABI int VideoOutWaitVblank(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status);
|
||||
KYTY_SYSV_ABI int VideoOutAllowOutputResolutionWqhdDetection(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutVrrPegToFixedRate(int handle, uint64_t arg1, uint64_t arg2);
|
||||
KYTY_SYSV_ABI int VideoOutVrrUnpegFromFixedRate(int handle);
|
||||
KYTY_SYSV_ABI int VideoOutInitializeOutputOptions(VideoOutOutputOptions* options);
|
||||
KYTY_SYSV_ABI int VideoOutIsOutputSupported(int handle, uint64_t mode,
|
||||
const VideoOutOutputOptions* options,
|
||||
|
||||
@@ -384,6 +384,7 @@ enum class Opcode {
|
||||
V_CMP_GT_I16,
|
||||
V_CMP_NE_I16,
|
||||
V_CMP_GE_I16,
|
||||
V_CMPX_LT_I16,
|
||||
V_CMP_LT_F16,
|
||||
V_CMP_EQ_F16,
|
||||
V_CMP_LE_F16,
|
||||
|
||||
@@ -239,6 +239,7 @@ constexpr VopcOpcodeInfo VOPC_OPCODE_LIST[] = {
|
||||
{0x92u, Opcode::V_CMPX_EQ_I32}, {0x93u, Opcode::V_CMPX_LE_I32},
|
||||
{0x94u, Opcode::V_CMPX_GT_I32}, {0x95u, Opcode::V_CMPX_NE_I32},
|
||||
{0x96u, Opcode::V_CMPX_GE_I32}, {0x98u, Opcode::V_CMPX_CLASS_F32},
|
||||
{0x99u, Opcode::V_CMPX_LT_I16, false},
|
||||
{0xa9u, Opcode::V_CMP_LT_U16}, {0xaau, Opcode::V_CMP_EQ_U16},
|
||||
{0xabu, Opcode::V_CMP_LE_U16}, {0xacu, Opcode::V_CMP_GT_U16},
|
||||
{0xadu, Opcode::V_CMP_NE_U16}, {0xaeu, Opcode::V_CMP_GE_U16},
|
||||
@@ -1487,6 +1488,7 @@ bool IsVopcCompareExec(Opcode opcode) {
|
||||
case Opcode::V_CMPX_GE_U32:
|
||||
case Opcode::V_CMPX_NE_I64:
|
||||
case Opcode::V_CMPX_NE_U64:
|
||||
case Opcode::V_CMPX_LT_I16:
|
||||
case Opcode::V_CMPX_LT_U16:
|
||||
case Opcode::V_CMPX_EQ_U16:
|
||||
case Opcode::V_CMPX_GT_U16:
|
||||
|
||||
@@ -560,6 +560,12 @@ IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_la
|
||||
} else if (operand.sdwa_sel == 4u || operand.sdwa_sel == 5u) {
|
||||
offset = operand.sdwa_sel == 5u ? 16u : 0u;
|
||||
}
|
||||
if (width == 8u && operand.sdwa_sext) {
|
||||
// Extend the selected byte into the 16-bit operand before halfword modifiers.
|
||||
const auto value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
return ir.BitwiseAnd(value, IR::U32(IR::Value(0xffffu)));
|
||||
}
|
||||
return IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(offset), IR::Value(width)}));
|
||||
}
|
||||
@@ -567,7 +573,7 @@ IR::U32 Translator::ReadU16LaneRaw(const Decoder::Operand& operand, bool high_la
|
||||
IR::U32 Translator::ReadU16LaneAsU32(const Decoder::Operand& operand, bool high_lane,
|
||||
bool sign_extend) {
|
||||
auto value = Read16LaneBits(operand, high_lane);
|
||||
if (sign_extend || operand.sdwa_sext) {
|
||||
if (sign_extend) {
|
||||
value = IR::U32(
|
||||
ir.Emit(IR::ValueOpcode::BitFieldSExtract, {value, IR::Value(0u), IR::Value(16u)}));
|
||||
}
|
||||
|
||||
@@ -156,6 +156,9 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
|
||||
case O::V_CMP_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, false);
|
||||
return;
|
||||
case O::V_CMPX_LT_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThan32, true, true);
|
||||
return;
|
||||
case O::V_CMP_LE_I16:
|
||||
EmitInteger16Compare(inst, IR::ValueOpcode::SLessThanEqual32, true, false);
|
||||
return;
|
||||
|
||||
@@ -72,6 +72,27 @@
|
||||
<property name="toolTip"><string>Language</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_controller_color">
|
||||
<property name="text"><string>DualSense lightbar:</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="controllerColorLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_controller_color">
|
||||
<property name="toolTip"><string>Choose and preview a fixed LED color. Bluetooth DualSense may take several seconds to update.</string></property>
|
||||
<property name="text"><string>Custom</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_controller_color_reset">
|
||||
<property name="toolTip"><string>Let the game control the lightbar</string></property>
|
||||
<property name="text"><string>Default</string></property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QChar>
|
||||
#include <QColor>
|
||||
#include <QMetaEnum>
|
||||
#include <QMetaType>
|
||||
#include <QObject>
|
||||
@@ -88,6 +89,7 @@ public:
|
||||
QString user_name = "Kyty";
|
||||
int user_id = Config::DEFAULT_USER_ID;
|
||||
QString audio_input_device;
|
||||
QString controller_color;
|
||||
PresentMode present_mode = PresentMode::Mailbox;
|
||||
int gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
@@ -119,6 +121,7 @@ public:
|
||||
user_name = other.user_name;
|
||||
user_id = other.user_id;
|
||||
audio_input_device = other.audio_input_device;
|
||||
controller_color = other.controller_color;
|
||||
present_mode = other.present_mode;
|
||||
gpu_index = other.gpu_index;
|
||||
fullscreen_enabled = other.fullscreen_enabled;
|
||||
@@ -165,6 +168,7 @@ public:
|
||||
KYTY_CFG_SET(user_name);
|
||||
KYTY_CFG_SET(user_id);
|
||||
KYTY_CFG_SET(audio_input_device);
|
||||
KYTY_CFG_SET(controller_color);
|
||||
KYTY_CFG_SET(present_mode);
|
||||
KYTY_CFG_SET(gpu_index);
|
||||
KYTY_CFG_SET(fullscreen_enabled);
|
||||
@@ -204,6 +208,8 @@ public:
|
||||
? saved_user_id
|
||||
: Config::DEFAULT_USER_ID;
|
||||
audio_input_device = s->value("audio_input_device", audio_input_device).toString();
|
||||
const QColor color(s->value("controller_color", controller_color).toString());
|
||||
controller_color = color.isValid() ? color.name(QColor::HexRgb) : QString {};
|
||||
KYTY_CFG_GET(present_mode);
|
||||
gpu_index = s->value("gpu_index", -1).toInt();
|
||||
if (EnumToText(present_mode).isEmpty()) {
|
||||
|
||||
@@ -31,6 +31,9 @@ public:
|
||||
void SetGameDirectories(const QStringList& dirs);
|
||||
[[nodiscard]] QStringList GetGameDirectories() const;
|
||||
|
||||
signals:
|
||||
void PreviewControllerColor(const QString& color);
|
||||
|
||||
private:
|
||||
Ui::ConfigurationEditDialog* m_ui = nullptr;
|
||||
Configuration& m_info;
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
ConfigurationItem* GetSelectedItem() { return m_selected_item; }
|
||||
|
||||
[[nodiscard]] const QString& GetSettingsFile() const { return m_settings_file; }
|
||||
[[nodiscard]] const QString& GetGlobalControllerColor() const {
|
||||
return m_global_info.controller_color;
|
||||
}
|
||||
[[nodiscard]] std::unique_ptr<Configuration>
|
||||
CreateConfiguration(const ConfigurationItem& item) const;
|
||||
|
||||
@@ -44,6 +47,7 @@ signals:
|
||||
|
||||
void Run();
|
||||
void Select();
|
||||
void PreviewControllerColor(const QString& color);
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent* event) override;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef CONTROLLER_LIGHTBAR_H
|
||||
#define CONTROLLER_LIGHTBAR_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
|
||||
class ControllerLightbar final: public QObject {
|
||||
public:
|
||||
explicit ControllerLightbar(QObject* parent = nullptr);
|
||||
~ControllerLightbar() override;
|
||||
|
||||
void SetColor(const QString& color);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void Refresh();
|
||||
void ApplyColor(SDL_Gamepad* gamepad) const;
|
||||
|
||||
QTimer m_timer;
|
||||
QHash<SDL_JoystickID, SDL_Gamepad*> m_gamepads;
|
||||
QColor m_color;
|
||||
};
|
||||
|
||||
#endif // CONTROLLER_LIGHTBAR_H
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCheckBox>
|
||||
#include <QColorDialog>
|
||||
#include <QComboBox>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
@@ -40,6 +41,22 @@ constexpr char SETTINGS_CFG_DIALOG[] = "ConfigurationEditDialog";
|
||||
constexpr char SETTINGS_CFG_LAST_GEOMETRY[] = "geometry";
|
||||
constexpr int GLOBAL_SETTINGS_GAME_DIRS_MIN_WIDTH = 560;
|
||||
|
||||
static void UpdateControllerColorButton(QPushButton* button, const QString& hex) {
|
||||
const QColor color(hex);
|
||||
if (!color.isValid()) {
|
||||
button->setProperty("controllerColor", QString {});
|
||||
button->setText(QObject::tr("Custom"));
|
||||
button->setStyleSheet({});
|
||||
return;
|
||||
}
|
||||
const auto normalized = color.name(QColor::HexRgb);
|
||||
button->setProperty("controllerColor", normalized);
|
||||
button->setText(normalized.toUpper());
|
||||
button->setStyleSheet(QStringLiteral("background-color: %1; color: %2;")
|
||||
.arg(normalized, color.lightness() < 128 ? QStringLiteral("white")
|
||||
: QStringLiteral("black")));
|
||||
}
|
||||
|
||||
const QStringList CONSOLE_LANGUAGE_NAMES = {
|
||||
"Japanese",
|
||||
"English (United States)",
|
||||
@@ -110,6 +127,21 @@ ConfigurationEditDialog::ConfigurationEditDialog(Configuration& info, QWidget* p
|
||||
connect(m_ui->ok_button, &QPushButton::clicked, this, &ConfigurationEditDialog::save);
|
||||
connect(m_ui->cancel_button, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(m_ui->clear_button, &QPushButton::clicked, this, &ConfigurationEditDialog::clear);
|
||||
connect(m_ui->button_controller_color, &QPushButton::clicked, this, [this]() {
|
||||
const auto current = m_ui->button_controller_color->property("controllerColor").toString();
|
||||
const auto initial =
|
||||
current.isEmpty() ? QColor(QStringLiteral("#0070d1")) : QColor(current);
|
||||
const auto color = QColorDialog::getColor(initial, this, tr("DualSense lightbar color"));
|
||||
if (color.isValid()) {
|
||||
const auto hex = color.name(QColor::HexRgb);
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, hex);
|
||||
emit PreviewControllerColor(hex);
|
||||
}
|
||||
});
|
||||
connect(m_ui->button_controller_color_reset, &QPushButton::clicked, this, [this]() {
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, {});
|
||||
emit PreviewControllerColor({});
|
||||
});
|
||||
connect(m_ui->comboBox_shader_log_direction, &QComboBox::currentTextChanged, this,
|
||||
[this](const QString& text) {
|
||||
auto log = TextToEnum<Configuration::LogDirection>(text);
|
||||
@@ -169,6 +201,7 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
|
||||
m_ui->lineEdit_user_name->setMaxLength(static_cast<int>(Config::MAX_USER_NAME_LENGTH));
|
||||
m_ui->lineEdit_user_name->setText(info.user_name);
|
||||
m_ui->spinBox_user_id->setValue(info.user_id);
|
||||
UpdateControllerColorButton(m_ui->button_controller_color, info.controller_color);
|
||||
auto* microphone = m_ui->comboBox_audio_input_device;
|
||||
microphone->clear();
|
||||
microphone->addItem(tr("None"), QString {});
|
||||
@@ -370,6 +403,7 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
|
||||
info.user_name = ui.lineEdit_user_name->text().trimmed();
|
||||
info.user_id = ui.spinBox_user_id->value();
|
||||
info.audio_input_device = ui.comboBox_audio_input_device->currentData().toString();
|
||||
info.controller_color = ui.button_controller_color->property("controllerColor").toString();
|
||||
info.screen_resolution =
|
||||
TextToEnum<Configuration::Resolution>(ui.comboBox_screen_resolution->currentText());
|
||||
info.present_mode =
|
||||
@@ -427,6 +461,7 @@ void ConfigurationEditDialog::save() {
|
||||
void ConfigurationEditDialog::clear() {
|
||||
Configuration default_info;
|
||||
Init(default_info);
|
||||
emit PreviewControllerColor({});
|
||||
|
||||
if (m_show_game_dirs) {
|
||||
m_game_dirs_list->clear();
|
||||
|
||||
@@ -751,6 +751,8 @@ void ConfigurationListWidget::edit_configuration() {
|
||||
auto info = CreateConfiguration(*item);
|
||||
ConfigurationEditDialog dlg(*info, this);
|
||||
dlg.setWindowTitle(tr("Edit game settings"));
|
||||
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||
&ConfigurationListWidget::PreviewControllerColor);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
info->custom_settings = true;
|
||||
@@ -761,6 +763,8 @@ void ConfigurationListWidget::edit_configuration() {
|
||||
WriteSettings();
|
||||
item->Update();
|
||||
SelectItem(item);
|
||||
} else {
|
||||
emit Select();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,6 +792,8 @@ void ConfigurationListWidget::edit_global_settings() {
|
||||
ConfigurationEditDialog dlg(info, this);
|
||||
dlg.setWindowTitle(tr("Global settings"));
|
||||
dlg.SetGameDirectories(m_game_dirs);
|
||||
connect(&dlg, &ConfigurationEditDialog::PreviewControllerColor, this,
|
||||
&ConfigurationListWidget::PreviewControllerColor);
|
||||
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
m_global_info.CopyEmulatorSettingsFrom(info);
|
||||
@@ -799,6 +805,7 @@ void ConfigurationListWidget::edit_global_settings() {
|
||||
ScanGameDirectory();
|
||||
}
|
||||
}
|
||||
emit Select();
|
||||
}
|
||||
|
||||
void ConfigurationListWidget::edit_input_mapping() {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "controllerLightbar.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
ControllerLightbar::ControllerLightbar(QObject* parent): QObject(parent) {
|
||||
m_timer.setInterval(100);
|
||||
connect(&m_timer, &QTimer::timeout, this, &ControllerLightbar::Refresh);
|
||||
}
|
||||
|
||||
ControllerLightbar::~ControllerLightbar() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ControllerLightbar::SetColor(const QString& color) {
|
||||
const QColor parsed(color);
|
||||
if (!parsed.isValid()) {
|
||||
Stop();
|
||||
return;
|
||||
}
|
||||
if (m_color == parsed) {
|
||||
return;
|
||||
}
|
||||
if (!m_timer.isActive()) {
|
||||
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
|
||||
qWarning() << "Cannot initialize controller color preview:" << SDL_GetError();
|
||||
return;
|
||||
}
|
||||
m_timer.start();
|
||||
}
|
||||
m_color = parsed;
|
||||
for (auto* gamepad: m_gamepads) {
|
||||
ApplyColor(gamepad);
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ControllerLightbar::Stop() {
|
||||
if (!m_timer.isActive()) {
|
||||
return;
|
||||
}
|
||||
m_timer.stop();
|
||||
for (auto* gamepad: m_gamepads) {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
m_gamepads.clear();
|
||||
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
|
||||
m_color = {};
|
||||
}
|
||||
|
||||
void ControllerLightbar::Refresh() {
|
||||
// Bluetooth DualSense may defer an accepted LED command until its connection
|
||||
// animation ends. Polling events also delivers those deferred commands.
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED && !m_gamepads.contains(event.gdevice.which)) {
|
||||
auto* gamepad = SDL_OpenGamepad(event.gdevice.which);
|
||||
if (gamepad == nullptr) {
|
||||
qWarning() << "Cannot open controller color preview:" << SDL_GetError();
|
||||
continue;
|
||||
}
|
||||
if (SDL_GetBooleanProperty(SDL_GetGamepadProperties(gamepad),
|
||||
SDL_PROP_GAMEPAD_CAP_RGB_LED_BOOLEAN, false)) {
|
||||
m_gamepads.insert(event.gdevice.which, gamepad);
|
||||
ApplyColor(gamepad);
|
||||
} else {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
} else if (event.type == SDL_EVENT_GAMEPAD_REMOVED) {
|
||||
if (auto* gamepad = m_gamepads.take(event.gdevice.which); gamepad != nullptr) {
|
||||
SDL_CloseGamepad(gamepad);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerLightbar::ApplyColor(SDL_Gamepad* gamepad) const {
|
||||
if (!SDL_SetGamepadLED(gamepad, static_cast<Uint8>(m_color.red()),
|
||||
static_cast<Uint8>(m_color.green()),
|
||||
static_cast<Uint8>(m_color.blue()))) {
|
||||
qWarning() << "Cannot set controller LED:" << SDL_GetError();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "configuration.h"
|
||||
#include "configurationItem.h"
|
||||
#include "configurationListWidget.h"
|
||||
#include "controllerLightbar.h"
|
||||
#include "gameContent.h"
|
||||
#include "patchesDialog.h"
|
||||
#include "updateChecker.h"
|
||||
@@ -88,6 +89,7 @@ private:
|
||||
Ui::MainDialog* m_ui = {nullptr};
|
||||
MainDialog* m_main_dialog = nullptr;
|
||||
UpdateChecker* m_update_checker = nullptr;
|
||||
ControllerLightbar m_lightbar;
|
||||
QString m_interpreter;
|
||||
|
||||
QProcess m_process;
|
||||
@@ -103,6 +105,7 @@ MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPriv
|
||||
}
|
||||
|
||||
MainDialogPrivate::~MainDialogPrivate() {
|
||||
m_process.disconnect(this);
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
@@ -121,6 +124,12 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
connect(main_dialog, &MainDialog::Start, this, &MainDialogPrivate::FindInterpreter,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
|
||||
connect(m_ui->widget, &ConfigurationListWidget::PreviewControllerColor, this,
|
||||
[this](const QString& color) {
|
||||
if (m_process.state() == QProcess::NotRunning) {
|
||||
m_lightbar.SetColor(color);
|
||||
}
|
||||
});
|
||||
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
|
||||
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
|
||||
[this](const QString&) { m_update_checker->Check(true); });
|
||||
@@ -135,14 +144,14 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
|
||||
m_ui->widget->WriteSettings();
|
||||
});
|
||||
|
||||
connect(&m_process,
|
||||
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
[this](int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) {
|
||||
if (m_running_item != nullptr) {
|
||||
m_running_item->SetRunning(false);
|
||||
}
|
||||
Update();
|
||||
});
|
||||
connect(&m_process, &QProcess::stateChanged, this, [this](QProcess::ProcessState state) {
|
||||
if (state == QProcess::NotRunning) {
|
||||
if (m_running_item != nullptr) {
|
||||
m_running_item->SetRunning(false);
|
||||
}
|
||||
Update();
|
||||
}
|
||||
});
|
||||
|
||||
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
|
||||
|
||||
@@ -219,6 +228,9 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
|
||||
if (!info.audio_input_device.isEmpty()) {
|
||||
args << "--mic" << info.audio_input_device;
|
||||
}
|
||||
if (!info.controller_color.isEmpty()) {
|
||||
args << "--controller-color" << info.controller_color;
|
||||
}
|
||||
args << "--present-mode" << EnumToText(info.present_mode);
|
||||
if (info.gpu_index >= 0) {
|
||||
args << "--gpu" << QString::number(info.gpu_index);
|
||||
@@ -493,6 +505,7 @@ void MainDialogPrivate::Run() {
|
||||
}
|
||||
|
||||
m_running_item->SetRunning(true);
|
||||
m_lightbar.Stop();
|
||||
|
||||
auto info = m_ui->widget->CreateConfiguration(*m_running_item);
|
||||
m_main_dialog->RunInterpreter(&m_process, *info);
|
||||
@@ -512,6 +525,12 @@ void MainDialogPrivate::Update() {
|
||||
}
|
||||
|
||||
m_ui->widget->SetRunEnabled(run_enabled);
|
||||
if (m_process.state() != QProcess::NotRunning) {
|
||||
m_lightbar.Stop();
|
||||
return;
|
||||
}
|
||||
m_lightbar.SetColor(item != nullptr ? m_ui->widget->CreateConfiguration(*item)->controller_color
|
||||
: m_ui->widget->GetGlobalControllerColor());
|
||||
}
|
||||
|
||||
#include "mainDialog.moc"
|
||||
+1
-1
@@ -1552,7 +1552,7 @@ int KYTY_SYSV_ABI AgcSuspendPoint() {
|
||||
PRINT_NAME();
|
||||
|
||||
EXIT_IF(g_renderer == nullptr);
|
||||
g_renderer->GetGpu().Done();
|
||||
g_renderer->GetGpu().SuspendPoint();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
+68
-13
@@ -133,6 +133,9 @@ private:
|
||||
int m_connected_count = 0;
|
||||
bool m_motion_enabled = true;
|
||||
uint64_t m_gyro_time = 0;
|
||||
// Accelerometer gravity direction in the reset frame.
|
||||
std::array<float, 3> m_up_reference {0.0f, 1.0f, 0.0f};
|
||||
bool m_up_reference_valid = false;
|
||||
ControllerState m_state;
|
||||
ControllerState m_states[STATES_MAX];
|
||||
bool m_obtained[STATES_MAX] {};
|
||||
@@ -297,6 +300,9 @@ void GameController::Connect(int id) {
|
||||
if (id != HOST_INPUT_CONTROLLER_ID) {
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(id));
|
||||
pad != nullptr) {
|
||||
if (const auto& color = Config::GetControllerColor()) {
|
||||
(void)SDL_SetGamepadLED(pad, (*color)[0], (*color)[1], (*color)[2]);
|
||||
}
|
||||
for (auto sensor: {SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO}) {
|
||||
if (SDL_GamepadHasSensor(pad, sensor) &&
|
||||
!SDL_SetGamepadSensorEnabled(pad, sensor, true)) {
|
||||
@@ -345,6 +351,7 @@ void GameController::CheckActive() {
|
||||
m_connected = new_connected;
|
||||
m_state = {};
|
||||
m_gyro_time = 0;
|
||||
m_up_reference_valid = false;
|
||||
m_states_num = 0;
|
||||
m_first_state = 0;
|
||||
m_next_touch_id = 1;
|
||||
@@ -436,6 +443,29 @@ void GameController::TouchPad(int id, int finger, bool down, float x, float y) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using Vec3 = std::array<float, 3>;
|
||||
using Quat = std::array<float, 4>; // x, y, z, w
|
||||
|
||||
Vec3 QuatRotate(const Quat& q, const Vec3& v) {
|
||||
const Vec3 t {2.0f * (q[1] * v[2] - q[2] * v[1]), 2.0f * (q[2] * v[0] - q[0] * v[2]),
|
||||
2.0f * (q[0] * v[1] - q[1] * v[0])};
|
||||
return {v[0] + q[3] * t[0] + (q[1] * t[2] - q[2] * t[1]),
|
||||
v[1] + q[3] * t[1] + (q[2] * t[0] - q[0] * t[2]),
|
||||
v[2] + q[3] * t[2] + (q[0] * t[1] - q[1] * t[0])};
|
||||
}
|
||||
|
||||
Quat QuatConjugate(const Quat& q) {
|
||||
return {-q[0], -q[1], -q[2], q[3]};
|
||||
}
|
||||
|
||||
float Length(const Vec3& v) {
|
||||
return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t time_us) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (id != m_active_id || !m_motion_enabled) {
|
||||
@@ -447,20 +477,38 @@ void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t t
|
||||
for (int i = 0; i < 3; i++) {
|
||||
m_state.accel[i] = data[i] / SDL_STANDARD_GRAVITY;
|
||||
}
|
||||
// Capture gravity near 1 G to limit interference from linear acceleration.
|
||||
const float magnitude = Length(m_state.accel);
|
||||
if (!m_up_reference_valid && magnitude > 0.9f && magnitude < 1.1f) {
|
||||
const Vec3 up {m_state.accel[0] / magnitude, m_state.accel[1] / magnitude,
|
||||
m_state.accel[2] / magnitude};
|
||||
m_up_reference = QuatRotate(m_state.orientation, up);
|
||||
m_up_reference_valid = true;
|
||||
}
|
||||
} else {
|
||||
std::copy_n(data, 3, m_state.gyro.begin());
|
||||
// Mahony tilt correction; heading is unobservable and reported gyro values stay raw.
|
||||
auto rate = m_state.gyro;
|
||||
const float magnitude = Length(m_state.accel);
|
||||
if (m_up_reference_valid && magnitude > 0.8f && magnitude < 1.2f) {
|
||||
const Vec3 measured {m_state.accel[0] / magnitude, m_state.accel[1] / magnitude,
|
||||
m_state.accel[2] / magnitude};
|
||||
const Vec3 predicted = QuatRotate(QuatConjugate(m_state.orientation), m_up_reference);
|
||||
rate[0] += measured[1] * predicted[2] - measured[2] * predicted[1];
|
||||
rate[1] += measured[2] * predicted[0] - measured[0] * predicted[2];
|
||||
rate[2] += measured[0] * predicted[1] - measured[1] * predicted[0];
|
||||
}
|
||||
// Do not extrapolate a single sample across lost reports (e.g. loss of window focus).
|
||||
constexpr uint64_t max_gyro_interval_us = 100000;
|
||||
if (m_gyro_time != 0 && time_us > m_gyro_time &&
|
||||
time_us - m_gyro_time <= max_gyro_interval_us) {
|
||||
const float dt = static_cast<float>(time_us - m_gyro_time) * 0.000001f;
|
||||
const float speed =
|
||||
std::sqrt(data[0] * data[0] + data[1] * data[1] + data[2] * data[2]);
|
||||
const float dt = static_cast<float>(time_us - m_gyro_time) * 0.000001f;
|
||||
const float speed = Length(rate);
|
||||
const float half_angle = speed * dt * 0.5f;
|
||||
const float scale = speed > 0.0f ? std::sin(half_angle) / speed : 0.0f;
|
||||
const float x = data[0] * scale;
|
||||
const float y = data[1] * scale;
|
||||
const float z = data[2] * scale;
|
||||
const float x = rate[0] * scale;
|
||||
const float y = rate[1] * scale;
|
||||
const float z = rate[2] * scale;
|
||||
const float w = std::cos(half_angle);
|
||||
const auto q = m_state.orientation;
|
||||
// Accumulate body-local rotation relative to connection / orientation reset.
|
||||
@@ -486,19 +534,21 @@ void GameController::Motion(int id, Sensor sensor, const float* data, uint64_t t
|
||||
void GameController::SetMotionSensorState(bool enable) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (m_motion_enabled != enable) {
|
||||
m_motion_enabled = enable;
|
||||
m_gyro_time = 0;
|
||||
m_state.accel = {0.0f, 1.0f, 0.0f};
|
||||
m_state.gyro = {};
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
m_motion_enabled = enable;
|
||||
m_gyro_time = 0;
|
||||
m_state.accel = {0.0f, 1.0f, 0.0f};
|
||||
m_state.gyro = {};
|
||||
m_up_reference_valid = false;
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
AddState();
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::ResetOrientation() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_gyro_time = 0;
|
||||
m_state.orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_gyro_time = 0;
|
||||
m_up_reference_valid = false;
|
||||
m_state.time = LibKernel::KernelGetProcessTime();
|
||||
AddState();
|
||||
}
|
||||
@@ -581,6 +631,11 @@ int GameController::GetActiveControllerId() {
|
||||
|
||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (const auto& color = Config::GetControllerColor()) {
|
||||
r = (*color)[0];
|
||||
g = (*color)[1];
|
||||
b = (*color)[2];
|
||||
}
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
pad != nullptr) {
|
||||
(void)SDL_SetGamepadLED(pad, r, g, b);
|
||||
|
||||
@@ -3834,6 +3834,27 @@ namespace LibSharePlay {
|
||||
|
||||
LIB_VERSION("SharePlay", 1, "SharePlay", 1, 1);
|
||||
|
||||
constexpr int SHARE_PLAY_ERROR_INVALID_ARGS = -2129788927;
|
||||
constexpr int SHARE_PLAY_ERROR_ALREADY_INITIALIZED = -2129788925;
|
||||
constexpr int SHARE_PLAY_ERROR_NOT_INITIALIZED = -2129788924;
|
||||
|
||||
struct SharePlayConnectionInfoA {
|
||||
int32_t status;
|
||||
int32_t mode;
|
||||
char host_online_id[20];
|
||||
char visitor_online_id[20];
|
||||
uint64_t host_account_id;
|
||||
uint64_t visitor_account_id;
|
||||
int32_t host_user_id;
|
||||
int32_t visitor_user_id;
|
||||
};
|
||||
|
||||
static_assert(sizeof(SharePlayConnectionInfoA) == 72);
|
||||
static_assert(offsetof(SharePlayConnectionInfoA, host_account_id) == 48);
|
||||
static_assert(offsetof(SharePlayConnectionInfoA, host_user_id) == 64);
|
||||
|
||||
static bool g_share_play_initialized = false;
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayInitialize(void* heap, size_t heap_size) {
|
||||
PRINT_NAME();
|
||||
|
||||
@@ -3841,18 +3862,41 @@ static int KYTY_SYSV_ABI SharePlayInitialize(void* heap, size_t heap_size) {
|
||||
"\t heap_size = %" PRIu64 "\n",
|
||||
reinterpret_cast<uint64_t>(heap), static_cast<uint64_t>(heap_size));
|
||||
|
||||
if (g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
if (heap != nullptr && heap_size < 6u * 1024u) {
|
||||
return SHARE_PLAY_ERROR_INVALID_ARGS;
|
||||
}
|
||||
g_share_play_initialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayTerminate() {
|
||||
PRINT_NAME();
|
||||
|
||||
if (!g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
g_share_play_initialized = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int KYTY_SYSV_ABI SharePlayGetCurrentConnectionInfoA(SharePlayConnectionInfoA* info) {
|
||||
if (!g_share_play_initialized) {
|
||||
return SHARE_PLAY_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (info == nullptr) {
|
||||
return SHARE_PLAY_ERROR_INVALID_ARGS;
|
||||
}
|
||||
*info = {.status = 0, .host_user_id = -1, .visitor_user_id = -1};
|
||||
return 0;
|
||||
}
|
||||
|
||||
LIB_DEFINE(InitPlatform_1_SharePlay) {
|
||||
LIB_FUNC("isruqthpYcw", LibSharePlay::SharePlayInitialize);
|
||||
LIB_FUNC("UaLjloJinow", LibSharePlay::SharePlayTerminate);
|
||||
LIB_FUNC("+MCXJlWdi+s", LibSharePlay::SharePlayGetCurrentConnectionInfoA);
|
||||
}
|
||||
|
||||
} // namespace LibSharePlay
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
#include "common/abi.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "libs/libs.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_STATIC
|
||||
#define STBIWDEF static inline
|
||||
#define STBI_WRITE_NO_STDIO
|
||||
#include "stb_image_write.h"
|
||||
|
||||
namespace Libs {
|
||||
|
||||
LIB_VERSION("PngEnc", 1, "PngEnc", 1, 1);
|
||||
|
||||
namespace PngEnc {
|
||||
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_ADDR = -2140602111; // 0x80690101
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_SIZE = -2140602110; // 0x80690102
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_PARAM = -2140602109; // 0x80690103
|
||||
constexpr int32_t PNG_ENC_ERROR_INVALID_HANDLE = -2140602108; // 0x80690104
|
||||
constexpr int32_t PNG_ENC_ERROR_DATA_OVERFLOW = -2140602096; // 0x80690110
|
||||
constexpr int32_t PNG_ENC_ERROR_FATAL = -2140602080; // 0x80690120
|
||||
|
||||
constexpr uint32_t PNG_ENC_MAX_IMAGE_WIDTH = 1000000;
|
||||
constexpr uint32_t PNG_ENC_MAX_IMAGE_HEIGHT = 1000000;
|
||||
constexpr uint32_t PNG_ENC_MAX_FILTER_NUMBER = 4;
|
||||
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGB = 3;
|
||||
constexpr uint16_t PNG_ENC_COLOR_SPACE_RGBA = 19;
|
||||
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_R8G8B8A8 = 0;
|
||||
constexpr uint16_t PNG_ENC_PIXEL_FORMAT_B8G8R8A8 = 1;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_SUB = 1;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_UP = 2;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_AVERAGE = 4;
|
||||
constexpr uint16_t PNG_ENC_FILTER_TYPE_PAETH = 8;
|
||||
|
||||
struct PngEncCreateParam {
|
||||
uint32_t this_size;
|
||||
uint32_t attribute;
|
||||
uint32_t max_image_width;
|
||||
uint32_t max_filter_number;
|
||||
};
|
||||
|
||||
struct PngEncEncodeParam {
|
||||
const uint8_t* image_mem_addr;
|
||||
uint8_t* png_mem_addr;
|
||||
uint32_t image_mem_size;
|
||||
uint32_t png_mem_size;
|
||||
uint32_t image_width;
|
||||
uint32_t image_height;
|
||||
uint32_t image_pitch;
|
||||
uint16_t pixel_format;
|
||||
uint16_t color_space;
|
||||
uint16_t bit_depth;
|
||||
uint16_t clut_number;
|
||||
uint16_t filter_type;
|
||||
uint16_t compression_level;
|
||||
};
|
||||
|
||||
struct PngEncOutputInfo {
|
||||
uint32_t data_size;
|
||||
uint32_t processed_height;
|
||||
};
|
||||
|
||||
static_assert(sizeof(PngEncCreateParam) == 16);
|
||||
static_assert(sizeof(PngEncEncodeParam) == 48);
|
||||
|
||||
struct PngEncContext {
|
||||
uint64_t magic;
|
||||
uint32_t max_image_width;
|
||||
uint32_t max_filter_number;
|
||||
};
|
||||
|
||||
constexpr uint64_t PNG_ENC_CONTEXT_MAGIC = 0x4b595459504e4745ull; // KYTYPNGE
|
||||
|
||||
struct PngEncWriter {
|
||||
uint8_t* dst;
|
||||
uint32_t capacity;
|
||||
uint32_t size;
|
||||
bool overflow;
|
||||
};
|
||||
|
||||
// stb_image_write keeps its compression level and filter choice in globals
|
||||
static std::mutex g_png_enc_mutex;
|
||||
|
||||
static void PngEncWrite(void* context, void* data, int size) {
|
||||
auto* writer = static_cast<PngEncWriter*>(context);
|
||||
|
||||
if (writer->overflow || size < 0 ||
|
||||
static_cast<uint64_t>(writer->size) + static_cast<uint32_t>(size) > writer->capacity) {
|
||||
writer->overflow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(writer->dst + writer->size, data, static_cast<size_t>(size));
|
||||
writer->size += static_cast<uint32_t>(size);
|
||||
}
|
||||
|
||||
static int PngEncForcedFilter(uint16_t filter_type) {
|
||||
if (filter_type == (PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
|
||||
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH)) {
|
||||
return -1;
|
||||
}
|
||||
// stb's adaptive mode cannot restrict itself to the guest's filter mask.
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_SUB) != 0) {
|
||||
return 1;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_UP) != 0) {
|
||||
return 2;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_AVERAGE) != 0) {
|
||||
return 3;
|
||||
}
|
||||
if ((filter_type & PNG_ENC_FILTER_TYPE_PAETH) != 0) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t ValidateCreateParam(const PngEncCreateParam* param) {
|
||||
if (param == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
LOGF("\t this_size = %" PRIu32 "\n", param->this_size);
|
||||
LOGF("\t attribute = %" PRIu32 "\n", param->attribute);
|
||||
LOGF("\t max_image_width = %" PRIu32 "\n", param->max_image_width);
|
||||
LOGF("\t max_filter_number = %" PRIu32 "\n", param->max_filter_number);
|
||||
|
||||
if (param->this_size != sizeof(PngEncCreateParam) || param->attribute != 0 ||
|
||||
param->max_filter_number > PNG_ENC_MAX_FILTER_NUMBER) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->max_image_width == 0 || param->max_image_width > PNG_ENC_MAX_IMAGE_WIDTH) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncQueryMemorySize(const PngEncCreateParam* param) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (int32_t result = ValidateCreateParam(param); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return sizeof(PngEncContext);
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncCreate(const PngEncCreateParam* param, void* memory_address,
|
||||
uint32_t memory_size, void** handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
LOGF("\t memory_address = %p\n", memory_address);
|
||||
LOGF("\t memory_size = %" PRIu32 "\n", memory_size);
|
||||
|
||||
if (int32_t result = ValidateCreateParam(param); result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (memory_address == nullptr || handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (memory_size < sizeof(PngEncContext)) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
auto* ctx = static_cast<PngEncContext*>(memory_address);
|
||||
ctx->magic = PNG_ENC_CONTEXT_MAGIC;
|
||||
ctx->max_image_width = param->max_image_width;
|
||||
ctx->max_filter_number = param->max_filter_number;
|
||||
*handle = ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncEncode(void* handle, const PngEncEncodeParam* param,
|
||||
PngEncOutputInfo* output_info) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
const auto* ctx = static_cast<const PngEncContext*>(handle);
|
||||
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (param == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
LOGF("\t image_mem_addr = %p\n", static_cast<const void*>(param->image_mem_addr));
|
||||
LOGF("\t png_mem_addr = %p\n", static_cast<const void*>(param->png_mem_addr));
|
||||
LOGF("\t image_mem_size = %" PRIu32 "\n", param->image_mem_size);
|
||||
LOGF("\t png_mem_size = %" PRIu32 "\n", param->png_mem_size);
|
||||
LOGF("\t image_width = %" PRIu32 "\n", param->image_width);
|
||||
LOGF("\t image_height = %" PRIu32 "\n", param->image_height);
|
||||
LOGF("\t image_pitch = %" PRIu32 "\n", param->image_pitch);
|
||||
LOGF("\t pixel_format = %" PRIu16 "\n", param->pixel_format);
|
||||
LOGF("\t color_space = %" PRIu16 "\n", param->color_space);
|
||||
LOGF("\t bit_depth = %" PRIu16 "\n", param->bit_depth);
|
||||
LOGF("\t filter_type = %" PRIu16 "\n", param->filter_type);
|
||||
LOGF("\t compression_level = %" PRIu16 "\n", param->compression_level);
|
||||
|
||||
if (param->image_mem_addr == nullptr || param->png_mem_addr == nullptr ||
|
||||
(reinterpret_cast<uintptr_t>(param->image_mem_addr) & 3u) != 0) {
|
||||
return PNG_ENC_ERROR_INVALID_ADDR;
|
||||
}
|
||||
|
||||
if (param->pixel_format != PNG_ENC_PIXEL_FORMAT_R8G8B8A8 &&
|
||||
param->pixel_format != PNG_ENC_PIXEL_FORMAT_B8G8R8A8) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->color_space != PNG_ENC_COLOR_SPACE_RGB &&
|
||||
param->color_space != PNG_ENC_COLOR_SPACE_RGBA) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->bit_depth != 8 || param->clut_number != 0 || param->compression_level > 9) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
constexpr uint16_t filter_mask = PNG_ENC_FILTER_TYPE_SUB | PNG_ENC_FILTER_TYPE_UP |
|
||||
PNG_ENC_FILTER_TYPE_AVERAGE | PNG_ENC_FILTER_TYPE_PAETH;
|
||||
if ((param->filter_type & ~filter_mask) != 0 ||
|
||||
static_cast<uint32_t>(std::popcount(param->filter_type)) > ctx->max_filter_number) {
|
||||
return PNG_ENC_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
const uint32_t width = param->image_width;
|
||||
const uint32_t height = param->image_height;
|
||||
if (width == 0 || height == 0 || width > ctx->max_image_width ||
|
||||
width > PNG_ENC_MAX_IMAGE_WIDTH || height > PNG_ENC_MAX_IMAGE_HEIGHT) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const int components = (param->color_space == PNG_ENC_COLOR_SPACE_RGBA ? 4 : 3);
|
||||
const uint32_t row_size = width * static_cast<uint32_t>(components);
|
||||
const uint32_t min_pitch = width * 4u;
|
||||
const uint32_t pitch = param->image_pitch;
|
||||
|
||||
// Leave room for deflate expansion and stb's signed-int buffer-capacity doubling.
|
||||
constexpr uint64_t max_filtered_size = std::numeric_limits<int>::max() / 2u;
|
||||
if (static_cast<uint64_t>(row_size + 1u) * height > max_filtered_size ||
|
||||
param->png_mem_size == 0 || pitch < min_pitch || (pitch & 3u) != 0 ||
|
||||
static_cast<uint64_t>(pitch) * height > param->image_mem_size) {
|
||||
return PNG_ENC_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const bool bgr = (param->pixel_format == PNG_ENC_PIXEL_FORMAT_B8G8R8A8);
|
||||
std::unique_ptr<uint8_t[]> pixels(new (std::nothrow)
|
||||
uint8_t[static_cast<size_t>(row_size) * height]);
|
||||
if (pixels == nullptr) {
|
||||
return PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < height; y++) {
|
||||
const auto* src = param->image_mem_addr + static_cast<size_t>(y) * pitch;
|
||||
auto* dst = pixels.get() + static_cast<size_t>(y) * row_size;
|
||||
|
||||
for (uint32_t x = 0; x < width; x++) {
|
||||
dst[0] = src[bgr ? 2 : 0];
|
||||
dst[1] = src[1];
|
||||
dst[2] = src[bgr ? 0 : 2];
|
||||
if (components == 4) {
|
||||
dst[3] = src[3];
|
||||
}
|
||||
|
||||
src += 4;
|
||||
dst += components;
|
||||
}
|
||||
}
|
||||
|
||||
PngEncWriter writer {param->png_mem_addr, param->png_mem_size, 0, false};
|
||||
|
||||
int ok = 0;
|
||||
{
|
||||
std::scoped_lock lock(g_png_enc_mutex);
|
||||
|
||||
stbi_write_png_compression_level = param->compression_level;
|
||||
stbi_write_force_png_filter = PngEncForcedFilter(param->filter_type);
|
||||
|
||||
ok = stbi_write_png_to_func(PngEncWrite, &writer, static_cast<int>(width),
|
||||
static_cast<int>(height), components, pixels.get(),
|
||||
static_cast<int>(row_size));
|
||||
}
|
||||
|
||||
if (output_info != nullptr) {
|
||||
output_info->data_size = (writer.overflow ? 0 : writer.size);
|
||||
output_info->processed_height = (ok != 0 && !writer.overflow ? height : 0);
|
||||
}
|
||||
|
||||
if (ok == 0) {
|
||||
LOGF("\t encode failed\n");
|
||||
return PNG_ENC_ERROR_FATAL;
|
||||
}
|
||||
|
||||
if (writer.overflow) {
|
||||
LOGF("\t output buffer too small\n");
|
||||
return PNG_ENC_ERROR_DATA_OVERFLOW;
|
||||
}
|
||||
|
||||
LOGF("\t data_size = %" PRIu32 "\n", writer.size);
|
||||
|
||||
return static_cast<int32_t>(writer.size);
|
||||
}
|
||||
|
||||
static int32_t KYTY_SYSV_ABI PngEncDelete(void* handle) {
|
||||
PRINT_NAME();
|
||||
|
||||
if (handle == nullptr) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* ctx = static_cast<PngEncContext*>(handle);
|
||||
if (ctx->magic != PNG_ENC_CONTEXT_MAGIC) {
|
||||
return PNG_ENC_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ctx->magic = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace PngEnc
|
||||
|
||||
LIB_DEFINE(InitPngEnc_1) {
|
||||
PRINT_NAME_ENABLE(true);
|
||||
|
||||
LIB_FUNC("9030RnBDoh4", PngEnc::PngEncQueryMemorySize);
|
||||
LIB_FUNC("7aGTPfrqT9s", PngEnc::PngEncCreate);
|
||||
LIB_FUNC("xgDjJKpcyHo", PngEnc::PngEncEncode);
|
||||
LIB_FUNC("RUrWdwTWZy8", PngEnc::PngEncDelete);
|
||||
}
|
||||
|
||||
} // namespace Libs
|
||||
@@ -39,6 +39,8 @@ LIB_DEFINE(InitVideoOut_1) {
|
||||
LIB_FUNC("Mt4QHHkxkOc", VideoOut::VideoOutGetEventCount);
|
||||
LIB_FUNC("j6RaAUlaLv0", VideoOut::VideoOutWaitVblank);
|
||||
LIB_FUNC("utPrVdxio-8", VideoOut::VideoOutGetOutputStatus);
|
||||
LIB_FUNC("5tRaBjtdTzY", VideoOut::VideoOutVrrPegToFixedRate);
|
||||
LIB_FUNC("T4ucGB8CsnM", VideoOut::VideoOutVrrUnpegFromFixedRate);
|
||||
LIB_FUNC("+I4K03i3EL0", VideoOut::VideoOutInitializeOutputOptions);
|
||||
LIB_FUNC("Nv8c-Kb+DUM", VideoOut::VideoOutIsOutputSupported);
|
||||
LIB_FUNC("w0hLuNarQxY", VideoOut::VideoOutConfigureOutput);
|
||||
@@ -46,6 +48,7 @@ LIB_DEFINE(InitVideoOut_1) {
|
||||
LIB_FUNC("MCJ8SkzsQxY", VideoOut::VideoOutLatencyMeasureSetStartPoint);
|
||||
LIB_FUNC("DYhhWbJSeRg", VideoOut::VideoOutColorSettingsSetGamma);
|
||||
LIB_FUNC("pv9CI5VC+R0", VideoOut::VideoOutAdjustColor);
|
||||
LIB_FUNC("w7Ipp9Xl7hg", VideoOut::VideoOutAllowOutputResolutionWqhdDetection);
|
||||
}
|
||||
|
||||
} // namespace LibGen5
|
||||
|
||||
@@ -91,6 +91,7 @@ LIB_DEFINE(InitNet_1);
|
||||
LIB_DEFINE(InitPad_1);
|
||||
LIB_DEFINE(InitPlayGo_1);
|
||||
LIB_DEFINE(InitPngDec_1);
|
||||
LIB_DEFINE(InitPngEnc_1);
|
||||
LIB_DEFINE(InitPlatform_1);
|
||||
LIB_DEFINE(InitRudp_1);
|
||||
LIB_DEFINE(InitSaveData_1);
|
||||
@@ -129,6 +130,7 @@ void InitAll(Loader::SymbolDatabase* s) {
|
||||
InitPlayGo_1(s);
|
||||
LibPsml::InitPsml_1(s);
|
||||
InitPngDec_1(s);
|
||||
InitPngEnc_1(s);
|
||||
InitPlatform_1(s);
|
||||
InitRudp_1(s);
|
||||
LibRtc::InitRtc_1(s);
|
||||
|
||||
@@ -53,6 +53,7 @@ static void PrintUsage() {
|
||||
::printf(" --user-id <num> Local user ID. Default: %d.\n",
|
||||
Config::DEFAULT_USER_ID);
|
||||
::printf(" --mic <name> Capture from this microphone; omit for silence.\n");
|
||||
::printf(" --controller-color <#RRGGBB> Override the controller lightbar color.\n");
|
||||
::printf(
|
||||
" --present-mode <value> Fifo, Mailbox, or Immediate. Default: Mailbox.\n");
|
||||
::printf(
|
||||
@@ -145,6 +146,20 @@ static bool ParseUint32(const std::string& value, uint32_t& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseControllerColor(const std::string& value, Config::ControllerColor& out) {
|
||||
if (value.size() != 7 || value[0] != '#') {
|
||||
return false;
|
||||
}
|
||||
uint32_t rgb = 0;
|
||||
auto [end, error] = std::from_chars(value.data() + 1, value.data() + value.size(), rgb, 16);
|
||||
if (error != std::errc {} || end != value.data() + value.size()) {
|
||||
return false;
|
||||
}
|
||||
out = {static_cast<uint8_t>(rgb >> 16), static_cast<uint8_t>(rgb >> 8),
|
||||
static_cast<uint8_t>(rgb)};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ParseInt32(const std::string& value, int32_t& out) {
|
||||
int32_t number = 0;
|
||||
auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), number);
|
||||
@@ -302,6 +317,13 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
}
|
||||
} else if (arg == "--mic") {
|
||||
options.config.audio_input_device = value;
|
||||
} else if (arg == "--controller-color") {
|
||||
Config::ControllerColor color {};
|
||||
if (!ParseControllerColor(value, color)) {
|
||||
::printf("invalid controller color (expected #RRGGBB): %s\n", value.c_str());
|
||||
return false;
|
||||
}
|
||||
options.config.controller_color = color;
|
||||
} else if (arg == "--present-mode") {
|
||||
if (!ParseEnum(value, options.config.present_mode)) {
|
||||
::printf("invalid present mode: %s\n", value.c_str());
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "libs/libPngEnc.cpp"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_NO_STDIO
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
namespace Loader {
|
||||
void SymbolDatabase::Add(const SymbolResolve&, uint64_t, const std::string&) {}
|
||||
namespace Timer {
|
||||
double GetTimeMs() {
|
||||
return 0.0;
|
||||
}
|
||||
} // namespace Timer
|
||||
} // namespace Loader
|
||||
|
||||
namespace {
|
||||
using namespace Libs::PngEnc;
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::fprintf(stderr, "PngEncTests:%d: %s\n", __LINE__, #condition); \
|
||||
std::abort(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
struct Encoder {
|
||||
PngEncContext context {};
|
||||
void* handle = nullptr;
|
||||
|
||||
explicit Encoder(uint32_t max_filters = 4) {
|
||||
const PngEncCreateParam create {sizeof(PngEncCreateParam), 0, PNG_ENC_MAX_IMAGE_WIDTH,
|
||||
max_filters};
|
||||
CHECK(PngEncQueryMemorySize(&create) == sizeof(context));
|
||||
CHECK(PngEncCreate(&create, &context, sizeof(context), &handle) == 0);
|
||||
}
|
||||
|
||||
~Encoder() {
|
||||
CHECK(PngEncDelete(handle) == 0);
|
||||
CHECK(PngEncDelete(handle) == PNG_ENC_ERROR_INVALID_HANDLE);
|
||||
}
|
||||
};
|
||||
|
||||
PngEncEncodeParam EncodeParam(const uint8_t* source, uint32_t source_size, uint8_t* destination,
|
||||
uint32_t destination_size) {
|
||||
PngEncEncodeParam param {};
|
||||
param.image_mem_addr = source;
|
||||
param.png_mem_addr = destination;
|
||||
param.image_mem_size = source_size;
|
||||
param.png_mem_size = destination_size;
|
||||
param.image_width = 2;
|
||||
param.image_height = 2;
|
||||
param.image_pitch = 8;
|
||||
param.color_space = PNG_ENC_COLOR_SPACE_RGBA;
|
||||
param.bit_depth = 8;
|
||||
param.compression_level = 6;
|
||||
return param;
|
||||
}
|
||||
|
||||
uint32_t ReadBe32(const uint8_t* bytes) {
|
||||
return (uint32_t {bytes[0]} << 24u) | (uint32_t {bytes[1]} << 16u) |
|
||||
(uint32_t {bytes[2]} << 8u) | bytes[3];
|
||||
}
|
||||
|
||||
void TestPixels() {
|
||||
Encoder encoder;
|
||||
constexpr std::array<uint8_t, 16> rgba {255, 0, 13, 0, 4, 210, 6, 255,
|
||||
7, 8, 220, 64, 111, 12, 113, 128};
|
||||
for (const bool bgr: {false, true}) {
|
||||
for (const bool alpha: {false, true}) {
|
||||
for (const bool padded: {false, true}) {
|
||||
const uint32_t pitch = padded ? 12 : 8;
|
||||
std::vector<uint8_t> source(pitch * 2, 0xee);
|
||||
std::vector<uint8_t> expected;
|
||||
for (uint32_t y = 0; y < 2; ++y) {
|
||||
for (uint32_t x = 0; x < 2; ++x) {
|
||||
const auto* pixel = rgba.data() + (y * 2 + x) * 4;
|
||||
auto* input = source.data() + y * pitch + x * 4;
|
||||
input[0] = pixel[bgr ? 2 : 0];
|
||||
input[1] = pixel[1];
|
||||
input[2] = pixel[bgr ? 0 : 2];
|
||||
input[3] = pixel[3];
|
||||
expected.insert(expected.end(), pixel, pixel + (alpha ? 4 : 3));
|
||||
}
|
||||
}
|
||||
std::array<uint8_t, 1024> png {};
|
||||
auto param = EncodeParam(source.data(), source.size(), png.data(), png.size());
|
||||
param.pixel_format =
|
||||
bgr ? PNG_ENC_PIXEL_FORMAT_B8G8R8A8 : PNG_ENC_PIXEL_FORMAT_R8G8B8A8;
|
||||
param.color_space = alpha ? PNG_ENC_COLOR_SPACE_RGBA : PNG_ENC_COLOR_SPACE_RGB;
|
||||
param.image_pitch = pitch;
|
||||
PngEncOutputInfo info {};
|
||||
const auto size = PngEncEncode(encoder.handle, ¶m, &info);
|
||||
CHECK(size > 0 && info.data_size == static_cast<uint32_t>(size));
|
||||
CHECK(info.processed_height == 2);
|
||||
int width = 0, height = 0, components = 0;
|
||||
auto* decoded =
|
||||
stbi_load_from_memory(png.data(), size, &width, &height, &components, 0);
|
||||
CHECK(decoded != nullptr);
|
||||
CHECK(width == 2 && height == 2 && components == (alpha ? 4 : 3));
|
||||
CHECK(std::memcmp(decoded, expected.data(), expected.size()) == 0);
|
||||
stbi_image_free(decoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestFilterMasks() {
|
||||
Encoder encoder;
|
||||
alignas(4) std::array<uint8_t, 16> pixels {};
|
||||
std::array<uint8_t, 1024> png {};
|
||||
auto param = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size());
|
||||
for (uint16_t mask = 0; mask <= 15; ++mask) {
|
||||
param.filter_type = mask;
|
||||
const auto size = PngEncEncode(encoder.handle, ¶m, nullptr);
|
||||
CHECK(size > 0);
|
||||
CHECK(std::memcmp(png.data() + 37, "IDAT", 4) == 0);
|
||||
std::array<char, 18> filtered {};
|
||||
CHECK(stbi_zlib_decode_buffer(filtered.data(), filtered.size(),
|
||||
reinterpret_cast<const char*>(png.data() + 41),
|
||||
ReadBe32(png.data() + 33)) == filtered.size());
|
||||
for (size_t row = 0; row < 2; ++row) {
|
||||
const auto filter = static_cast<uint8_t>(filtered[row * 9]);
|
||||
CHECK(filter <= 4);
|
||||
CHECK(filter == 0 ? (mask == 0 || mask == 15) : (mask & (1u << (filter - 1u))) != 0);
|
||||
}
|
||||
}
|
||||
param.filter_type = 16;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
for (uint32_t count = 0; count <= 4; ++count) {
|
||||
Encoder limited(count);
|
||||
for (uint16_t mask = 0; mask <= 15; ++mask) {
|
||||
param.filter_type = mask;
|
||||
const auto result = PngEncEncode(limited.handle, ¶m, nullptr);
|
||||
CHECK(std::popcount(mask) <= count ? result > 0
|
||||
: result == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestOutputCapacity() {
|
||||
Encoder encoder;
|
||||
alignas(4) std::array<uint8_t, 16> pixels {};
|
||||
std::array<uint8_t, 1024> png {};
|
||||
auto param = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size());
|
||||
const auto size = PngEncEncode(encoder.handle, ¶m, nullptr);
|
||||
CHECK(size > 0);
|
||||
std::vector<uint8_t> guarded(size + 2, 0xa5);
|
||||
param.png_mem_addr = guarded.data() + 1;
|
||||
param.png_mem_size = size;
|
||||
PngEncOutputInfo info {};
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, &info) == size);
|
||||
CHECK(guarded.front() == 0xa5 && guarded.back() == 0xa5);
|
||||
CHECK(std::memcmp(guarded.data() + 1, png.data(), size) == 0);
|
||||
std::fill(guarded.begin(), guarded.end(), 0xa5);
|
||||
param.png_mem_size = size - 1;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, &info) == PNG_ENC_ERROR_DATA_OVERFLOW);
|
||||
CHECK(info.data_size == 0 && info.processed_height == 0);
|
||||
CHECK(std::all_of(guarded.begin(), guarded.end(), [](uint8_t byte) { return byte == 0xa5; }));
|
||||
}
|
||||
|
||||
void TestValidation() {
|
||||
Encoder encoder;
|
||||
PngEncCreateParam create {sizeof(PngEncCreateParam), 0, 2, 4};
|
||||
CHECK(PngEncQueryMemorySize(nullptr) == PNG_ENC_ERROR_INVALID_ADDR);
|
||||
CHECK(PngEncCreate(&create, nullptr, sizeof(PngEncContext), &encoder.handle) ==
|
||||
PNG_ENC_ERROR_INVALID_ADDR);
|
||||
CHECK(PngEncCreate(&create, &encoder.context, 0, &encoder.handle) ==
|
||||
PNG_ENC_ERROR_INVALID_SIZE);
|
||||
create.max_image_width = 0;
|
||||
CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
create.max_image_width = PNG_ENC_MAX_IMAGE_WIDTH + 1;
|
||||
CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
create.max_image_width = 2;
|
||||
create.this_size = 0;
|
||||
CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
create.this_size = sizeof(create);
|
||||
create.max_filter_number = 5;
|
||||
CHECK(PngEncQueryMemorySize(&create) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
alignas(4) std::array<uint8_t, 16> pixels {};
|
||||
std::array<uint8_t, 1024> png {};
|
||||
const auto valid = EncodeParam(pixels.data(), pixels.size(), png.data(), png.size());
|
||||
CHECK(PngEncEncode(nullptr, &valid, nullptr) == PNG_ENC_ERROR_INVALID_HANDLE);
|
||||
CHECK(PngEncEncode(encoder.handle, nullptr, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
for (const auto width: {0u, PNG_ENC_MAX_IMAGE_WIDTH + 1, 0x40000000u, 0xffffffffu}) {
|
||||
auto param = valid;
|
||||
param.image_width = width;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
}
|
||||
for (const auto height: {0u, PNG_ENC_MAX_IMAGE_HEIGHT + 1, 0xffffffffu}) {
|
||||
auto param = valid;
|
||||
param.image_width = 1;
|
||||
param.image_height = height;
|
||||
param.image_mem_size = 0xffffffffu;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
}
|
||||
for (const auto height: {262144u, 400000u, 0x7ffffu}) {
|
||||
auto param = valid;
|
||||
param.image_width = 1024;
|
||||
param.image_height = height;
|
||||
param.image_pitch = 4096;
|
||||
param.image_mem_size = 0xffffffffu;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
}
|
||||
for (const auto pitch: {0u, 7u, 9u, 0xfffffffcu}) {
|
||||
auto param = valid;
|
||||
param.image_pitch = pitch;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
}
|
||||
auto param = valid;
|
||||
param.image_pitch = 12;
|
||||
param.image_mem_size = 20;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
param = valid;
|
||||
param.image_mem_size--;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_SIZE);
|
||||
param = valid;
|
||||
param.image_mem_addr = nullptr;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_ADDR);
|
||||
param.image_mem_addr = pixels.data() + 1;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_ADDR);
|
||||
param = valid;
|
||||
param.bit_depth = 16;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
param = valid;
|
||||
param.clut_number = 1;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
param = valid;
|
||||
param.compression_level = 10;
|
||||
CHECK(PngEncEncode(encoder.handle, ¶m, nullptr) == PNG_ENC_ERROR_INVALID_PARAM);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
TestPixels();
|
||||
TestFilterMasks();
|
||||
TestOutputCapacity();
|
||||
TestValidation();
|
||||
std::puts("PNG encoder tests passed");
|
||||
}
|
||||
@@ -2402,6 +2402,232 @@ public:
|
||||
std::printf("[host] %-32s ok\n", "StreamBufferRing");
|
||||
}
|
||||
|
||||
void CheckGpuSuspendPoint() {
|
||||
constexpr const char *name = "GpuSuspendPoint";
|
||||
EnsureRuntimeContext();
|
||||
auto &context = Renderer();
|
||||
context.InitializeGpu(nullptr);
|
||||
auto &gpu = context.GetGpu();
|
||||
auto &scheduler = context.GetCommandScheduler();
|
||||
CommandProcessor setup(context, 0);
|
||||
|
||||
vk::SemaphoreTypeCreateInfo timeline_type{};
|
||||
timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
|
||||
timeline_type.semaphoreType = vk::SemaphoreType::eTimeline;
|
||||
vk::SemaphoreCreateInfo timeline_create{};
|
||||
timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo;
|
||||
timeline_create.pNext = &timeline_type;
|
||||
vk::Semaphore gate = nullptr;
|
||||
Require(name, "timeline create",
|
||||
m_runtime_context.device.createSemaphore(
|
||||
&timeline_create, nullptr, &gate) == vk::Result::eSuccess,
|
||||
"failed to create the native completion gate");
|
||||
const auto signal = [&](uint64_t value) {
|
||||
vk::SemaphoreSignalInfo info{};
|
||||
info.sType = vk::StructureType::eSemaphoreSignalInfo;
|
||||
info.semaphore = gate;
|
||||
info.value = value;
|
||||
Require(name, "timeline signal",
|
||||
m_runtime_context.device.signalSemaphore(&info) ==
|
||||
vk::Result::eSuccess,
|
||||
"failed to release native GPU work");
|
||||
};
|
||||
const auto finish = [&] {
|
||||
gpu.WaitForIdle();
|
||||
gpu.SendCommandSync([&] {
|
||||
scheduler.Finish();
|
||||
scheduler.WaitPriorityOperations(scheduler.CurrentTick() - 1);
|
||||
});
|
||||
};
|
||||
const auto write = [](uint32_t *packet, uint32_t *destination,
|
||||
uint32_t value, bool predicated = false) {
|
||||
const auto address = reinterpret_cast<uint64_t>(destination);
|
||||
packet[0] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0) | uint32_t{predicated};
|
||||
packet[1] = 0;
|
||||
packet[2] = static_cast<uint32_t>(address);
|
||||
packet[3] = static_cast<uint32_t>(address >> 32u);
|
||||
packet[4] = value;
|
||||
};
|
||||
|
||||
gpu.SendCommandSync([&] {
|
||||
setup.BufferInit();
|
||||
SubmitInfo blocked;
|
||||
blocked.AddWait(gate, 1);
|
||||
scheduler.Flush(blocked);
|
||||
});
|
||||
std::binary_semaphore first_returned{0};
|
||||
std::jthread first([&] {
|
||||
gpu.SuspendPoint();
|
||||
first_returned.release();
|
||||
});
|
||||
const bool first_nonblocking =
|
||||
first_returned.try_acquire_for(std::chrono::seconds(2));
|
||||
if (!first_nonblocking) {
|
||||
signal(1);
|
||||
}
|
||||
first.join();
|
||||
Require(name, "first call", first_nonblocking,
|
||||
"the first suspend point waited for native GPU completion");
|
||||
|
||||
// Let the marker reach Vulkan while its completion remains gated.
|
||||
std::binary_semaphore marker_parsed{0};
|
||||
std::jthread parser([&] {
|
||||
gpu.WaitForIdle();
|
||||
marker_parsed.release();
|
||||
});
|
||||
const bool parser_nonblocking =
|
||||
marker_parsed.try_acquire_for(std::chrono::seconds(2));
|
||||
if (!parser_nonblocking) {
|
||||
signal(1);
|
||||
}
|
||||
parser.join();
|
||||
Require(name, "marker insertion", parser_nonblocking,
|
||||
"inserting a suspend point blocked the GPU command thread");
|
||||
|
||||
std::binary_semaphore second_entered{0};
|
||||
std::binary_semaphore second_returned{0};
|
||||
std::jthread second([&] {
|
||||
second_entered.release();
|
||||
gpu.SuspendPoint();
|
||||
second_returned.release();
|
||||
});
|
||||
second_entered.acquire();
|
||||
const bool second_overtook_completion =
|
||||
second_returned.try_acquire_for(std::chrono::milliseconds(100));
|
||||
|
||||
uint32_t compute_value = 0;
|
||||
std::array<uint32_t, 5> compute_commands{};
|
||||
write(compute_commands.data(), &compute_value, 33);
|
||||
bool host_lane_responded = false;
|
||||
std::binary_semaphore compute_progress{0};
|
||||
std::jthread compute([&] {
|
||||
gpu.SubmitCompute(0x20, compute_commands);
|
||||
gpu.SendCommandSync([&] { host_lane_responded = true; });
|
||||
gpu.WaitForIdle();
|
||||
compute_progress.release();
|
||||
});
|
||||
const bool compute_did_not_wait =
|
||||
compute_progress.try_acquire_for(std::chrono::seconds(2));
|
||||
signal(1);
|
||||
second.join();
|
||||
compute.join();
|
||||
finish();
|
||||
Require(name, "one outstanding point", !second_overtook_completion,
|
||||
"a second suspend point returned before the first native completion");
|
||||
Require(name, "independent queue progress",
|
||||
compute_did_not_wait && host_lane_responded && compute_value == 33,
|
||||
"waiting for a previous suspend point blocked compute submissions "
|
||||
"or the host command lane");
|
||||
Require(name, "frame count", gpu.GetFrameNum() == 2,
|
||||
"suspend-point insertion did not advance the frame counter once");
|
||||
|
||||
// A marker must stay behind a stalled graphics stream, then clear its
|
||||
// predicate before the next stream. Async compute releases that stream.
|
||||
alignas(16) uint64_t predicate = 0;
|
||||
uint32_t label = 0;
|
||||
uint32_t prefix = 0;
|
||||
uint32_t skipped = 0;
|
||||
uint32_t after_reset = 0;
|
||||
const auto predicate_address = reinterpret_cast<uint64_t>(&predicate);
|
||||
const auto label_address = reinterpret_cast<uint64_t>(&label);
|
||||
std::array<uint32_t, 21> before{};
|
||||
before[0] = KYTY_PM4(4, Pm4::IT_SET_PREDICATION, 0);
|
||||
before[1] = (3u << 16u) | (1u << 8u);
|
||||
before[2] = static_cast<uint32_t>(predicate_address);
|
||||
before[3] = static_cast<uint32_t>(predicate_address >> 32u);
|
||||
write(before.data() + 4, &skipped, 11, true);
|
||||
write(before.data() + 9, &prefix, 22);
|
||||
before[14] = KYTY_PM4(7, Pm4::IT_WAIT_REG_MEM, 0);
|
||||
before[15] = 0x10u | 3u;
|
||||
before[16] = static_cast<uint32_t>(label_address);
|
||||
before[17] = static_cast<uint32_t>(label_address >> 32u);
|
||||
before[18] = 1;
|
||||
before[19] = UINT32_MAX;
|
||||
std::array<uint32_t, 5> after{};
|
||||
write(after.data(), &after_reset, 44, true);
|
||||
std::binary_semaphore lane_entered{0};
|
||||
std::binary_semaphore release_lane{0};
|
||||
gpu.SendCommand([&] {
|
||||
lane_entered.release();
|
||||
release_lane.acquire();
|
||||
});
|
||||
lane_entered.acquire();
|
||||
gpu.Submit(before, {});
|
||||
std::binary_semaphore queued_returned{0};
|
||||
std::jthread queued([&] {
|
||||
gpu.SuspendPoint();
|
||||
queued_returned.release();
|
||||
});
|
||||
const bool queued_nonblocking =
|
||||
queued_returned.try_acquire_for(std::chrono::seconds(2));
|
||||
if (!queued_nonblocking) {
|
||||
release_lane.release();
|
||||
gpu.SendCommandSync([&] { label = 1; });
|
||||
queued.join();
|
||||
finish();
|
||||
Require(name, "queued insertion", false,
|
||||
"suspend-point insertion drained pending guest graphics");
|
||||
}
|
||||
queued.join();
|
||||
gpu.Submit(after, {});
|
||||
release_lane.release();
|
||||
bool prefix_reached = false;
|
||||
bool graphics_ordered = false;
|
||||
const auto deadline = std::chrono::steady_clock::now() +
|
||||
std::chrono::seconds(2);
|
||||
do {
|
||||
gpu.SendCommandSync([&] {
|
||||
prefix_reached = prefix == 22;
|
||||
graphics_ordered = skipped == 0 && after_reset == 0;
|
||||
});
|
||||
std::this_thread::yield();
|
||||
} while (!prefix_reached && std::chrono::steady_clock::now() < deadline);
|
||||
std::array<uint32_t, 5> release_graphics{};
|
||||
write(release_graphics.data(), &label, 1);
|
||||
gpu.SubmitCompute(0x20, release_graphics);
|
||||
finish();
|
||||
Require(name, "graphics FIFO",
|
||||
prefix_reached && graphics_ordered && skipped == 0,
|
||||
"suspend point or later graphics overtook a stalled graphics stream");
|
||||
Require(name, "graphics reset", after_reset == 44,
|
||||
"suspend point preserved the previous frame's predicate");
|
||||
|
||||
// Delay the completion callback beyond the owner's shutdown when possible.
|
||||
// Its credit must remain alive independently of the GuestGpu object.
|
||||
std::binary_semaphore priority_entered{0};
|
||||
std::binary_semaphore release_priority{0};
|
||||
gpu.SendCommandSync([&] {
|
||||
scheduler.DeferPriorityOperation([&] {
|
||||
priority_entered.release();
|
||||
release_priority.acquire();
|
||||
});
|
||||
SubmitInfo blocked;
|
||||
blocked.AddWait(gate, 2);
|
||||
scheduler.Flush(blocked);
|
||||
});
|
||||
gpu.SuspendPoint();
|
||||
gpu.WaitForIdle();
|
||||
std::binary_semaphore shutdown_returned{0};
|
||||
std::jthread shutdown([&] {
|
||||
context.ShutdownGpu();
|
||||
shutdown_returned.release();
|
||||
});
|
||||
const bool shutdown_overtook_native =
|
||||
shutdown_returned.try_acquire_for(std::chrono::milliseconds(100));
|
||||
signal(2);
|
||||
priority_entered.acquire();
|
||||
if (!shutdown_overtook_native) {
|
||||
(void)shutdown_returned.try_acquire_for(std::chrono::seconds(2));
|
||||
}
|
||||
release_priority.release();
|
||||
shutdown.join();
|
||||
scheduler.DrainPriorityOperations();
|
||||
m_runtime_context.device.destroySemaphore(gate, nullptr);
|
||||
Require(name, "shutdown native completion", !shutdown_overtook_native,
|
||||
"shutdown returned while the suspend point was still executing");
|
||||
std::printf("[host] %-32s ok\n", name);
|
||||
}
|
||||
|
||||
void CheckGpuCommandLane() {
|
||||
EnsureRuntimeContext();
|
||||
auto &context = Renderer();
|
||||
@@ -2647,7 +2873,7 @@ public:
|
||||
graphics_gate_entered.acquire();
|
||||
live_graphics_commands[4] = 22;
|
||||
graphics_gate_release.release();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
Require("GpuCommandLane", "borrowed graphics commands",
|
||||
live_graphics_value == 22,
|
||||
"graphics submission executed a copied PM4 stream");
|
||||
@@ -2665,7 +2891,7 @@ public:
|
||||
compute_gate_entered.acquire();
|
||||
live_compute_commands[4] = 44;
|
||||
compute_gate_release.release();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
Require("GpuCommandLane", "borrowed compute commands",
|
||||
live_compute_value == 44,
|
||||
"compute submission executed a copied PM4 stream");
|
||||
@@ -2721,7 +2947,7 @@ public:
|
||||
write_packet(no_interrupt_graphics_commands.data(),
|
||||
&no_interrupt_graphics_value, 55);
|
||||
gpu.Submit(no_interrupt_graphics_commands, {});
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
LibKernel::EventQueue::KernelEvent interrupt_event{};
|
||||
@@ -2745,7 +2971,7 @@ public:
|
||||
interrupt_gate_entered.acquire();
|
||||
live_interrupt_commands[2] = 0;
|
||||
interrupt_gate_release.release();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
interrupt_count = 0;
|
||||
@@ -2761,7 +2987,7 @@ public:
|
||||
auto graphics_interrupt_commands = make_interrupt_packet(
|
||||
1, 1, &graphics_interrupt_label, 0x11223344u, 0);
|
||||
gpu.Submit(graphics_interrupt_commands, {});
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
interrupt_count = 0;
|
||||
@@ -2787,7 +3013,7 @@ public:
|
||||
compute_interrupt_gate_entered.acquire();
|
||||
compute_interrupt_commands[2] |= 1u << 24u;
|
||||
compute_interrupt_gate_release.release();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
interrupt_count = 0;
|
||||
@@ -2803,7 +3029,7 @@ public:
|
||||
auto compute_clock_commands = make_interrupt_packet(
|
||||
3, 1, &compute_clock_label, 0, 0x567u);
|
||||
gpu.SubmitCompute(0x20, compute_clock_commands);
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
interrupt_count = 0;
|
||||
@@ -2821,7 +3047,7 @@ public:
|
||||
2, 2, &compute_done_label, compute_done_value, 0x678u);
|
||||
compute_done_commands[1] = 0x62fu; // CS_DONE, shader-done index, no GCR action.
|
||||
gpu.SubmitCompute(0x20, compute_done_commands);
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
finish_gpu();
|
||||
|
||||
interrupt_count = 0;
|
||||
@@ -2896,7 +3122,7 @@ public:
|
||||
});
|
||||
std::this_thread::yield();
|
||||
}
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
Require("GpuCommandLane", "packet-boundary command polling",
|
||||
packet_marker_a_at_callback == 11 &&
|
||||
packet_marker_b_at_callback == 0 && packet_marker_a == 11 &&
|
||||
@@ -2934,21 +3160,21 @@ public:
|
||||
uint32_t ordered_suffix = 0;
|
||||
std::jthread ordered([&] {
|
||||
ordered_started.release();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
ordered_suffix = suffix;
|
||||
ordered_finished = true;
|
||||
});
|
||||
ordered_started.acquire();
|
||||
gpu.SendCommandSync([&] {
|
||||
Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(),
|
||||
"submit done returned before a queued submission");
|
||||
Require("GpuCommandLane", "guest queue idle fence", !ordered_finished.load(),
|
||||
"idle fence returned before a queued submission");
|
||||
label = 1;
|
||||
});
|
||||
ordered.join();
|
||||
Require("GpuCommandLane", "ordered completion",
|
||||
prefix == 11 && suffix == 22 && ordered_suffix == 22 &&
|
||||
ordered_finished.load(),
|
||||
"submit done did not drain prior PM4 work");
|
||||
"idle fence did not drain prior PM4 work");
|
||||
|
||||
auto &resources = context;
|
||||
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
|
||||
@@ -2971,7 +3197,7 @@ public:
|
||||
unmap_complete.acquire();
|
||||
}
|
||||
unmap_thread.join();
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
Require("GpuCommandLane", "unmap queue progress",
|
||||
unmap_returned &&
|
||||
!resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
|
||||
@@ -3098,7 +3324,7 @@ public:
|
||||
dma_cursor == dma_commands.size(),
|
||||
"DMA_DATA packet stream has the wrong size");
|
||||
gpu.Submit(dma_commands, {});
|
||||
gpu.Done();
|
||||
gpu.WaitForIdle();
|
||||
constexpr uint32_t clean_fill_value = 0xdecafbad;
|
||||
gpu.SendCommandSync([&] {
|
||||
auto &buffer_cache = resources.GetBufferCache();
|
||||
@@ -3336,15 +3562,12 @@ public:
|
||||
std::binary_semaphore command_entered{0};
|
||||
std::binary_semaphore release_command{0};
|
||||
std::atomic<bool> shutdown_complete{false};
|
||||
std::atomic<bool> submission_lane_entered{false};
|
||||
bool shutdown_owner_visible = false;
|
||||
resources.MapMemory(empty_unmap_base, empty_unmap_size);
|
||||
gpu.SendCommand([&] {
|
||||
command_entered.release();
|
||||
release_command.acquire();
|
||||
shutdown_owner_visible = &context.GetGpu() == &gpu;
|
||||
gpu.Done();
|
||||
submission_lane_entered = true;
|
||||
});
|
||||
command_entered.acquire();
|
||||
std::jthread shutdown_thread([&] {
|
||||
@@ -3360,8 +3583,7 @@ public:
|
||||
shutdown_thread.join();
|
||||
Require(
|
||||
"GpuCommandLane", "owned shutdown completion",
|
||||
shutdown_complete.load() && submission_lane_entered.load() &&
|
||||
shutdown_owner_visible,
|
||||
shutdown_complete.load() && shutdown_owner_visible,
|
||||
"GPU owner did not remain available while draining queued work");
|
||||
resources.UnmapMemory(empty_unmap_base, empty_unmap_size);
|
||||
Require("GpuCommandLane", "unmap after shutdown",
|
||||
@@ -23539,6 +23761,158 @@ TestCase VectorVopcCmpxLtU16CapturedSdwaExecMask() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorVopcCmpxLtI16CapturedSdwaExecMask() {
|
||||
using O = ShaderOpcode;
|
||||
enum Encoding { Captured, Compact, Vop3, HighLhs, HighRhs, SdwaOperands };
|
||||
struct CompareCase {
|
||||
u32 lhs;
|
||||
u32 rhs;
|
||||
u32 expected_exec;
|
||||
Encoding encoding = Captured;
|
||||
u32 incoming_exec = 1;
|
||||
u32 src0_sel = 6;
|
||||
u32 src1_sel = 6;
|
||||
u32 src0_sext = 0;
|
||||
u32 src1_sext = 0;
|
||||
};
|
||||
const std::array<CompareCase, 30> cases{{
|
||||
{0xffff000eu, 15u, 1}, // Ignore the high half: 14 < 15.
|
||||
{0x8000000fu, 15u, 0}, // Equality is false despite a negative high half.
|
||||
{0x00000010u, 15u, 0},
|
||||
{0x7fff0000u, 15u, 1},
|
||||
{0x0000ffffu, 15u, 1}, // Signed -1, not unsigned 65535.
|
||||
{0x00008000u, 15u, 1}, // Minimum signed halfword.
|
||||
{0x00007fffu, 15u, 0}, // Maximum signed halfword.
|
||||
{0x0000fff0u, 15u, 1},
|
||||
{0x0000ffffu, 15u, 0, Captured, 0}, // Cannot reactivate an inactive lane.
|
||||
{0xffff000fu, 15u, 0, Captured, 0},
|
||||
{0x1234ffffu, 0xabcd0000u, 1, Compact}, // -1 < 0.
|
||||
{0xffff0000u, 0x0000ffffu, 0, Compact}, // 0 < -1 is false.
|
||||
{0xabcd8000u, 0x12347fffu, 1, Vop3},
|
||||
{0x12347fffu, 0xabcd8000u, 0, Vop3},
|
||||
{0xffff0010u, 15u, 1, HighLhs}, // Select -1 in WORD_1.
|
||||
{0x0010ffffu, 15u, 0, HighLhs}, // Select 16, not the negative low half.
|
||||
{0x1234fffeu, 0xffff8000u, 1, HighRhs}, // -2 < selected -1.
|
||||
{0x1234ffffu, 0xabcd0000u, 1, SdwaOperands, 1, 4, 4, 1, 1},
|
||||
{0x12345680u, 0u, 1, SdwaOperands, 1, 0, 6, 1, 0}, // BYTE_0 SEXT: -128.
|
||||
{0x1234567fu, 0u, 0, SdwaOperands, 1, 0, 6, 1, 0}, // +127.
|
||||
{0x12345680u, 0u, 0, SdwaOperands, 1, 0, 6, 0, 0}, // Without SEXT: +128.
|
||||
{0x12348000u, 0u, 1, SdwaOperands, 1, 1, 6, 1, 0},
|
||||
{0x12803456u, 0u, 1, SdwaOperands, 1, 2, 6, 1, 0},
|
||||
{0x80345678u, 0u, 1, SdwaOperands, 1, 3, 6, 1, 0},
|
||||
{0x0000ffffu, 0x12345680u, 0, SdwaOperands, 1, 6, 0, 0, 1}, // -1 < -128 is false.
|
||||
{0x0000ff7fu, 0x12345680u, 1, SdwaOperands, 1, 6, 0, 0, 1}, // -129 < -128.
|
||||
{0x0000ffffu, 0x12347f00u, 1, SdwaOperands, 1, 6, 1, 0, 1},
|
||||
{0u, 0x12803456u, 0, SdwaOperands, 1, 6, 2, 0, 1},
|
||||
{0u, 0x80345678u, 0, SdwaOperands, 1, 6, 3, 0, 1},
|
||||
{0xff000001u, 0x000100ffu, 1, SdwaOperands, 1, 3, 2, 1, 1},
|
||||
}};
|
||||
constexpr u32 vcc_lo = 0x76543210u;
|
||||
constexpr u32 vcc_hi = 0x89abcdefu;
|
||||
TestCase test;
|
||||
test.name = "VectorVopcCmpxLtI16CapturedSdwaExecMask";
|
||||
for (const auto &entry : cases) {
|
||||
test.initial.push_back(entry.lhs);
|
||||
}
|
||||
test.expected = test.initial;
|
||||
auto &code = test.code;
|
||||
for (u32 i = 0; i < cases.size(); ++i) {
|
||||
const auto &entry = cases[i];
|
||||
AppendVMovU32(&code, 30, i * 4u);
|
||||
AppendBufferLoadDword(&code, 1, 30); // Runtime input prevents constant folding.
|
||||
AppendVMovU32(&code, 2, entry.rhs);
|
||||
AppendSMovLiteral(&code, 106, vcc_lo);
|
||||
AppendSMovLiteral(&code, 107, vcc_hi);
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(entry.incoming_exec)));
|
||||
switch (entry.encoding) {
|
||||
case Compact: code.push_back(0x7d320501u); break;
|
||||
case Vop3: code.insert(code.end(), {0xd499007eu, 0x00020501u}); break;
|
||||
case HighLhs: code.insert(code.end(), {0x7d331ef9u, 0x86050001u}); break;
|
||||
case HighRhs:
|
||||
code.push_back(EncodeVopc(0x99u, 249u, 2u));
|
||||
code.push_back(EncodeVopcSdwa(1u, 0u, 0u, 6u, 5u));
|
||||
break;
|
||||
case SdwaOperands:
|
||||
code.push_back(EncodeVopc(0x99u, 249u, 2u));
|
||||
code.push_back(EncodeVopcSdwa(1u, 0u, 0u, entry.src0_sel, entry.src1_sel,
|
||||
entry.src0_sext, entry.src1_sext));
|
||||
break;
|
||||
default: code.insert(code.end(), {0x7d331ef9u, 0x86060001u}); break;
|
||||
}
|
||||
code.push_back(EncodeSMovB32(20, 126)); // Snapshot EXEC before restoring it.
|
||||
code.push_back(EncodeSMovB32(21, 127));
|
||||
code.push_back(EncodeSMovB32(126, InlineU32(1)));
|
||||
const u32 out = static_cast<u32>(cases.size()) + i * 4u;
|
||||
AppendStoreSgprPair(&code, 20, out);
|
||||
AppendStoreSgprPair(&code, 106, out + 2u); // CMPX preserves both VCC halves.
|
||||
test.expected.insert(test.expected.end(),
|
||||
{entry.expected_exec, 0u, vcc_lo, vcc_hi});
|
||||
}
|
||||
AppendEnd(&code);
|
||||
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
|
||||
O::V_CMPX_LT_I16, O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.decoded_counts = {{"V_CMPX_LT_I16", cases.size()}};
|
||||
test.required_spirv = {"OpSLessThan", "OpBitFieldSExtract"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorVopcCmpxLtI16WaveMasks(u32 wave_size) {
|
||||
using O = ShaderOpcode;
|
||||
constexpr u32 marker = 0xfeedabcdu;
|
||||
constexpr u32 vcc_lo = 0x01234567u;
|
||||
constexpr u32 vcc_hi = 0x89abcdefu;
|
||||
constexpr u32 expected_exec_lo = 0xaaaaaaaau;
|
||||
const u32 expected_exec_hi = wave_size == 64u ? 0x33333333u : 0u;
|
||||
TestCase test;
|
||||
test.name = wave_size == 32u ? "VectorVopcCmpxLtI16Wave32Masks"
|
||||
: "VectorVopcCmpxLtI16Wave64Masks";
|
||||
test.initial.assign(6u * wave_size, 0xdeadbeefu);
|
||||
constexpr u32 inputs[] = {0xffff000eu, 0x0000ffffu, 0x8000000fu, 0x12348000u};
|
||||
for (u32 lane = 0; lane < wave_size; ++lane) {
|
||||
test.initial[lane] = inputs[lane % 4u];
|
||||
}
|
||||
test.expected = test.initial;
|
||||
for (u32 lane = 0; lane < wave_size; ++lane) {
|
||||
test.expected[wave_size + lane] = expected_exec_lo;
|
||||
test.expected[2u * wave_size + lane] = expected_exec_hi;
|
||||
test.expected[3u * wave_size + lane] = vcc_lo;
|
||||
test.expected[4u * wave_size + lane] = vcc_hi;
|
||||
const u32 mask = lane < 32u ? expected_exec_lo : expected_exec_hi;
|
||||
if ((mask & (1u << (lane % 32u))) != 0u) {
|
||||
test.expected[5u * wave_size + lane] = marker;
|
||||
}
|
||||
}
|
||||
auto &code = test.code;
|
||||
code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0));
|
||||
AppendBufferLoadDword(&code, 1, 30);
|
||||
AppendVMovLiteral(&code, 4, marker);
|
||||
code.push_back(EncodeSop1(0x04, 10, 126)); // Save the full incoming EXEC.
|
||||
AppendSMovLiteral(&code, 106, vcc_lo);
|
||||
AppendSMovLiteral(&code, 107, vcc_hi);
|
||||
AppendSMovLiteral(&code, 126, 0xeeeeeeeeu);
|
||||
AppendSMovLiteral(&code, 127, 0x77777777u);
|
||||
code.insert(code.end(), {0x7d331ef9u, 0x86060001u});
|
||||
code.push_back(EncodeSMovB32(20, 126));
|
||||
code.push_back(EncodeSMovB32(21, 127));
|
||||
AppendStoreVgprAtLaneDwordOffset(&code, 4, 0, 5u * wave_size);
|
||||
code.push_back(EncodeSop1(0x04, 126, 10)); // Restore lanes to inspect the raw masks.
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 20, 0, wave_size);
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 21, 0, 2u * wave_size);
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 106, 0, 3u * wave_size);
|
||||
AppendStoreSgprAtLaneDwordOffset(&code, 107, 0, 4u * wave_size);
|
||||
AppendEnd(&code);
|
||||
test.compute_info.threads_num[0] = wave_size;
|
||||
test.compute_info.threads_num[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::V_MOV_B32, O::S_MOV_B32, O::S_MOV_B64, O::V_LSHLREV_B32,
|
||||
O::V_ADD_NC_U32, O::BUFFER_LOAD_DWORD, O::V_CMPX_LT_I16,
|
||||
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
|
||||
test.required_spirv = {"OpSLessThan", "OpBitFieldSExtract"};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() {
|
||||
using O = ShaderOpcode;
|
||||
struct CompareCase {
|
||||
@@ -23546,9 +23920,9 @@ TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() {
|
||||
u32 rhs;
|
||||
u32 incoming_exec;
|
||||
u32 expected_exec;
|
||||
u32 encoding = 0; // 0: SDWA, 1: compact, 2: VOP3, 3: SDWA sign-extend.
|
||||
u32 encoding = 0; // SDWA, compact, VOP3, sign-extended words/bytes.
|
||||
};
|
||||
const std::array<CompareCase, 12> cases{{
|
||||
const std::array<CompareCase, 16> cases{{
|
||||
{0x12340000u, 0x56780000u, 1, 1}, // Equal low half; ignore high half.
|
||||
{0xffff0001u, 0xabcd0001u, 1, 1},
|
||||
{0x00000000u, 0x12340000u, 1, 1},
|
||||
@@ -23561,6 +23935,10 @@ TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() {
|
||||
{0x00008000u, 0x0000ffffu, 1, 0, 2},
|
||||
{0x0001ffffu, 0x12340001u, 1, 0, 3}, // Sign-extended low halves differ.
|
||||
{0x00000001u, 0x00010001u, 0, 0}, // CMPX cannot reactivate an inactive lane.
|
||||
{0x12345680u, 0x0000ff80u, 1, 1, 4}, // SEXT BYTE_0 is unsigned U16 0xff80.
|
||||
{0x123456ffu, 0x0000ffffu, 1, 1, 4},
|
||||
{0x0000ff80u, 0x12348000u, 1, 1, 5}, // SEXT BYTE_1 on src1.
|
||||
{0x1234ffffu, 0x5678ffffu, 1, 1, 6}, // SEXT WORD_0 versus DWORD low half.
|
||||
}};
|
||||
constexpr u32 vcc_hi = 0x89abcdefu;
|
||||
TestCase test;
|
||||
@@ -23585,6 +23963,18 @@ TestCase VectorVopcCmpxEqU16SdwaCompactVop3ExecMask() {
|
||||
code.push_back(EncodeVopc(0xba, 249u, 1u));
|
||||
code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 6u, 6u, 1u, 1u));
|
||||
break;
|
||||
case 4:
|
||||
code.push_back(EncodeVopc(0xba, 249u, 1u));
|
||||
code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 0u, 6u, 1u, 0u));
|
||||
break;
|
||||
case 5:
|
||||
code.push_back(EncodeVopc(0xba, 249u, 1u));
|
||||
code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 6u, 1u, 0u, 1u));
|
||||
break;
|
||||
case 6:
|
||||
code.push_back(EncodeVopc(0xba, 249u, 1u));
|
||||
code.push_back(EncodeVopcSdwa(0u, 0u, 0u, 4u, 6u, 1u, 0u));
|
||||
break;
|
||||
default:
|
||||
code.push_back(EncodeVopc(0xba, 249u, 1u));
|
||||
code.push_back(EncodeVopcSdwa(0u));
|
||||
@@ -30372,6 +30762,10 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(VectorVopcSdwaCmpxWritesExecMask);
|
||||
AddCase(VectorVopcCmpxGtU16CapturedSdwaExecMask);
|
||||
AddCase(VectorVopcCmpxLtU16CapturedSdwaExecMask);
|
||||
AddCase(VectorVopcCmpxLtI16CapturedSdwaExecMask);
|
||||
for (u32 wave_size : {32u, 64u}) {
|
||||
cases.push_back(VectorVopcCmpxLtI16WaveMasks(wave_size));
|
||||
}
|
||||
AddCase(VectorVopcCmpxEqU16SdwaCompactVop3ExecMask);
|
||||
AddCase(VectorVopcCmpNgtF16CapturedSdwaAndEdges);
|
||||
AddCase(VectorVopcCmpNltF16CapturedSdwaAndEdges);
|
||||
@@ -33981,12 +34375,15 @@ void CheckPm4Predication(RenderContext &renderer) {
|
||||
uint32_t wait, const void *source, bool skip) {
|
||||
predicated = unconditional = 0;
|
||||
const auto packet = commands(op, condition, wait, source);
|
||||
const auto tick = renderer.GetCommandScheduler().CurrentTick();
|
||||
Pm4Execution execution;
|
||||
Require(name, stage,
|
||||
processor.Process(execution, packet) == Pm4ProcessResult::Complete &&
|
||||
processor.ShouldSkipPredicatedPackets() == skip &&
|
||||
predicated == (skip ? 0u : 11u) && unconditional == 22,
|
||||
"predicate polarity, tagged packet execution, or packet consumption is wrong");
|
||||
Require(name, stage, renderer.GetCommandScheduler().CurrentTick() == tick,
|
||||
"predication unnecessarily submitted and drained the host queue");
|
||||
};
|
||||
struct Case {
|
||||
uint64_t delta;
|
||||
@@ -35290,6 +35687,13 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, VectorVopcCmpxEqU16SdwaCompactVop3ExecMask());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-i16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorVopcCmpxLtI16CapturedSdwaExecMask());
|
||||
RunCase(&vulkan, VectorVopcCmpxLtI16WaveMasks(32));
|
||||
RunCase(&vulkan, VectorVopcCmpxLtI16WaveMasks(64));
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--cmpx-lt-u16-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorVopcCmpxLtU16CapturedSdwaExecMask());
|
||||
@@ -35506,6 +35910,11 @@ int main(int argc, char **argv) {
|
||||
CheckPm4CeCompletion(vulkan.RuntimeRenderer());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--suspend-point-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
vulkan.CheckGpuSuspendPoint();
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--alignbyte-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, VectorAlignByteUsesFiveBitByteOffset());
|
||||
@@ -35915,6 +36324,7 @@ int main(int argc, char **argv) {
|
||||
for (const auto &test : graphics_tests) {
|
||||
RunGraphicsCase(&vulkan, test);
|
||||
}
|
||||
vulkan.CheckGpuSuspendPoint();
|
||||
vulkan.CheckGpuCommandLane();
|
||||
if (skipped_device_checks) {
|
||||
std::printf(
|
||||
|
||||
Reference in new issue
Block a user