mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 16:23:05 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a22a9d9348 | ||
|
|
81f312638f | ||
|
|
f1f930e06c |
No files matched your search
@@ -477,6 +477,12 @@ add_executable(ngs2_sampler_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(ngs2_sampler_tests common fmt::fmt LibAtrac9)
|
||||
target_include_directories(ngs2_sampler_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(pad_haptics_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/PadHapticsTests.cpp"
|
||||
)
|
||||
target_link_libraries(pad_haptics_tests common fmt::fmt SDL3::SDL3-static)
|
||||
target_include_directories(pad_haptics_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/SaveDataMemoryTests.cpp"
|
||||
)
|
||||
@@ -569,6 +575,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
|
||||
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
|
||||
add_test(NAME 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 shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
@@ -636,6 +643,7 @@ if(BUILD_TESTING)
|
||||
virtual_memory_allocation_tests
|
||||
audio_out2_port_tests
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
save_data_memory_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -706,26 +706,66 @@ uint32_t FormattedOutOfBoundsValue(ValueEmitContext& ctx, const IR::MemoryInfo&
|
||||
return ConstructU32Composite(ctx.state, components, values);
|
||||
}
|
||||
|
||||
void StoreFormattedInBounds(ValueEmitContext& ctx, const IR::MemoryInfo& mem,
|
||||
const PreparedFormattedMemory& plan, uint32_t component,
|
||||
uint32_t data) {
|
||||
if (component >= plan.info.component_count) return;
|
||||
const auto bits = plan.info.component_bits[component];
|
||||
if (bits == 16u && (plan.info.type == Format::ComponentType::Snorm ||
|
||||
plan.info.type == Format::ComponentType::Float)) {
|
||||
uint32_t EncodeFormattedStoreComponent(ValueEmitContext& ctx,
|
||||
const Format::BufferFormatInfo& info,
|
||||
uint32_t component, uint32_t data) {
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.type == Format::ComponentType::Unorm) {
|
||||
const auto value = EmitBitCastF32U32(ctx.state, data);
|
||||
const auto clamped = EmitFPMin32(
|
||||
ctx.state, EmitFPMax32(ctx.state, value, ConstantF32Value(ctx.state, 0.0f)),
|
||||
ConstantF32Value(ctx.state, 1.0f));
|
||||
const auto scaled = EmitFPMul32(
|
||||
ctx.state, clamped, ConstantF32Value(ctx.state, float((1u << bits) - 1u)));
|
||||
return Unary(ctx.state, spv::OpConvertFToU, TypeU32(ctx.state),
|
||||
EmitFPRoundEven32(ctx.state, scaled));
|
||||
}
|
||||
if (bits == 16u && (info.type == Format::ComponentType::Snorm ||
|
||||
info.type == Format::ComponentType::Float)) {
|
||||
const auto value = EmitBitCastF32U32(ctx.state, data);
|
||||
const auto pair = EmitCompositeConstructF32x2(ctx.state, value,
|
||||
ConstantF32Value(ctx.state, 0.0f));
|
||||
data = plan.info.type == Format::ComponentType::Float
|
||||
? EmitPackHalf2x16(ctx.state, pair)
|
||||
: EmitPackSnorm2x16(ctx.state, pair);
|
||||
}
|
||||
if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordInBounds(ctx, mem, plan.resource, plan.addresses[component],
|
||||
plan.indices[component], bits, data);
|
||||
} else {
|
||||
StoreWordInBounds(ctx, plan.resource, plan.indices[component], data);
|
||||
return info.type == Format::ComponentType::Float ? EmitPackHalf2x16(ctx.state, pair)
|
||||
: EmitPackSnorm2x16(ctx.state, pair);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void StoreFormattedPrepared(ValueEmitContext& ctx, const IR::Inst& inst,
|
||||
const IR::MemoryInfo& mem, const MemoryResourceAccess& resource,
|
||||
const Format::BufferFormatInfo& info, uint32_t data,
|
||||
uint32_t components) {
|
||||
// RDNA2 formatted stores transfer the entire format, zero-filling missing VGPRs.
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, info.component_count,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
auto packed = ConstantU32(ctx.state, 0);
|
||||
for (uint32_t component = 0; component < info.component_count; component++) {
|
||||
auto value = ConstantU32(ctx.state, 0);
|
||||
if (component < components) {
|
||||
value = data;
|
||||
if (components != 1u) {
|
||||
value = ctx.state.builder.AllocateId();
|
||||
ctx.state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(ctx.state),
|
||||
value, data, component);
|
||||
}
|
||||
}
|
||||
value = EncodeFormattedStoreComponent(ctx, info, component, value);
|
||||
const auto bits = info.component_bits[component];
|
||||
if (info.packed_bitfield) {
|
||||
packed = EmitBitFieldInsert(
|
||||
ctx.state, packed, value,
|
||||
ConstantU32(ctx.state, info.component_bit_offset[component]),
|
||||
ConstantU32(ctx.state, bits));
|
||||
} else if (bits == 8u || bits == 16u) {
|
||||
StoreSubwordInBounds(ctx, mem, resource, plan.addresses[component],
|
||||
plan.indices[component], bits, value);
|
||||
} else {
|
||||
StoreWordInBounds(ctx, resource, plan.indices[component], value);
|
||||
}
|
||||
}
|
||||
if (info.packed_bitfield) StoreWordInBounds(ctx, resource, plan.indices[0], packed);
|
||||
});
|
||||
}
|
||||
|
||||
void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::MemoryInfo& mem) {
|
||||
@@ -737,11 +777,7 @@ void FormattedStore(ValueEmitContext& ctx, const IR::Inst& inst, const IR::Memor
|
||||
StoreWordPrepared(ctx, inst, RebaseRawComponent(mem, 0u), resource, data);
|
||||
return;
|
||||
}
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, 1u,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(ctx.state, plan.in_bounds, [&]() {
|
||||
StoreFormattedInBounds(ctx, mem, plan, 0u, data);
|
||||
});
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, data, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -859,16 +895,7 @@ void StoreWideBuffer(ValueEmitContext& ctx, const IR::Inst& inst, uint32_t compo
|
||||
const auto info = Format::GetFormatInfo(
|
||||
mem.formatted ? BufferFormat(ctx, mem) : Prospero::BufferFormat::kInvalid);
|
||||
if (info.type != Format::ComponentType::Unknown) {
|
||||
const auto plan = PrepareFormattedMemory(ctx, inst, mem, resource, info, components,
|
||||
FormattedAccess::Store);
|
||||
EmitIfCondition(state, plan.in_bounds, [&]() {
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
const auto data = state.builder.AllocateId();
|
||||
state.builder.AddFunction(spv::OpCompositeExtract, TypeU32(state), data,
|
||||
composite, component);
|
||||
StoreFormattedInBounds(ctx, mem, plan, component, data);
|
||||
}
|
||||
});
|
||||
StoreFormattedPrepared(ctx, inst, mem, resource, info, composite, components);
|
||||
return;
|
||||
}
|
||||
for (uint32_t component = 0; component < components; component++) {
|
||||
|
||||
@@ -440,6 +440,14 @@ void ConfigurationEditDialog::add_game_directory() {
|
||||
start_dir = m_game_dirs_list->item(m_game_dirs_list->count() - 1)->text();
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
// Use the native macOS picker to browse mounted volumes.
|
||||
const auto dir = QFileDialog::getExistingDirectory(this, tr("Select game folder"), start_dir);
|
||||
if (dir.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
AddGameDirectoryItem(dir);
|
||||
#else
|
||||
QFileDialog dialog(this, tr("Select game folders"), start_dir);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly, true);
|
||||
@@ -462,6 +470,7 @@ void ConfigurationEditDialog::add_game_directory() {
|
||||
for (const auto& dir: dialog.selectedFiles()) {
|
||||
AddGameDirectoryItem(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
update_game_directory_buttons();
|
||||
}
|
||||
|
||||
+18
-3
@@ -7,6 +7,8 @@
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/audio_internal.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
|
||||
@@ -105,7 +107,8 @@ private:
|
||||
int channels_num = 0;
|
||||
int volume[12] = {};
|
||||
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
SDL_AudioStream* stream = nullptr;
|
||||
Controller::DualSenseHaptics::Stream* haptics = nullptr;
|
||||
};
|
||||
|
||||
struct PortIn {
|
||||
@@ -268,6 +271,8 @@ bool Audio::OpenSdlDevice(PortOut* port) {
|
||||
|
||||
void Audio::CloseSdlDevice(PortOut* port) {
|
||||
EXIT_IF(port == nullptr);
|
||||
Controller::DualSenseHaptics::Close(port->haptics);
|
||||
port->haptics = nullptr;
|
||||
|
||||
if (port->stream != nullptr) {
|
||||
SDL_DestroyAudioStream(port->stream);
|
||||
@@ -435,7 +440,9 @@ Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, For
|
||||
port.volume[i] = 32768;
|
||||
}
|
||||
|
||||
if (type != AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
if (type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
port.haptics = Controller::DualSenseHaptics::Open(freq);
|
||||
} else {
|
||||
OpenSdlDevice(&port);
|
||||
}
|
||||
|
||||
@@ -547,7 +554,15 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
auto& port = m_out_ports[params[i].handle.GetId()];
|
||||
|
||||
QueueSdlAudio(&port, params[i].data, blocking);
|
||||
if (port.type == AUDIO_OUT_PORT_TYPE_VIBRATION) {
|
||||
// Haptics never pace output; keep the stream alive against close and volume changes.
|
||||
Common::LockGuard lock(m_mutex);
|
||||
Controller::DualSenseHaptics::Queue(
|
||||
port.haptics, Controller::GetActiveControllerId(), params[i].data, port.samples_num,
|
||||
static_cast<uint32_t>(port.channels_num), FormatIsFloat(port.format), port.volume);
|
||||
} else {
|
||||
QueueSdlAudio(&port, params[i].data, blocking);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < num; i++) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/padData.h"
|
||||
@@ -113,6 +114,7 @@ public:
|
||||
void ReleaseHostPads();
|
||||
void GetConnectionInfo(bool* flag, int* count);
|
||||
void SetVibration(uint8_t large_motor, uint8_t small_motor);
|
||||
int GetActiveControllerId();
|
||||
void SetLightBar(uint8_t r, uint8_t g, uint8_t b);
|
||||
bool SetTriggerEffect(const PadTriggerEffectParam& param);
|
||||
void ReadState(ControllerState* state, bool* flag, int* count);
|
||||
@@ -518,6 +520,7 @@ void GameController::ResetInputState() {
|
||||
|
||||
void GameController::ReleaseHostPads() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
DualSenseHaptics::Shutdown();
|
||||
|
||||
std::vector<SDL_Gamepad*> pads;
|
||||
for (const auto id: m_connected_ids) {
|
||||
@@ -551,6 +554,9 @@ void GameController::ReleaseHostPads() {
|
||||
|
||||
void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (DualSenseHaptics::SetVibration(m_active_id, large_motor, small_motor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_active_id == HOST_INPUT_CONTROLLER_ID) {
|
||||
return;
|
||||
@@ -568,6 +574,11 @@ void GameController::SetVibration(uint8_t large_motor, uint8_t small_motor) {
|
||||
}
|
||||
}
|
||||
|
||||
int GameController::GetActiveControllerId() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return m_active_id;
|
||||
}
|
||||
|
||||
void GameController::SetLightBar(uint8_t r, uint8_t g, uint8_t b) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(m_active_id));
|
||||
@@ -692,6 +703,10 @@ void ResetInputState() {
|
||||
g_controller->ResetInputState();
|
||||
}
|
||||
|
||||
int GetActiveControllerId() {
|
||||
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI PadInit() {
|
||||
PRINT_NAME();
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ void SetRightStick(int id, int x, int y);
|
||||
void SetTouchPad(int id, int finger, bool down, float x, float y);
|
||||
void SetSensor(int id, Sensor sensor, const float* data, uint64_t time_us);
|
||||
void ResetInputState();
|
||||
int GetActiveControllerId();
|
||||
|
||||
int KYTY_SYSV_ABI PadInit();
|
||||
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
#include "libs/dualSenseHaptics.h"
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream {
|
||||
uint32_t freq = 0;
|
||||
SDL_AudioStream* sdl = nullptr;
|
||||
SDL_AudioDeviceID device = 0;
|
||||
uint64_t next_check = 0;
|
||||
std::vector<float> buffer;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int FRAME_BYTES = 4 * sizeof(float);
|
||||
Common::Mutex g_mutex;
|
||||
int g_controller = -1;
|
||||
int g_playback_streams = 0;
|
||||
uint8_t g_large_motor = 0, g_small_motor = 0;
|
||||
uint64_t g_rumble_until = 0, g_haptics_until = 0;
|
||||
|
||||
void ApplyRumble() { // Caller holds g_mutex; no calls into Audio or Controller.
|
||||
SDL_LockJoysticks();
|
||||
if (auto* pad = SDL_GetGamepadFromID(static_cast<SDL_JoystickID>(g_controller));
|
||||
pad != nullptr) {
|
||||
const auto now = SDL_GetTicks();
|
||||
const bool muted = g_haptics_until > now || g_rumble_until <= now;
|
||||
(void)SDL_RumbleGamepad(
|
||||
pad, muted ? 0 : g_large_motor * 0x101U, muted ? 0 : g_small_motor * 0x101U,
|
||||
g_rumble_until > now ? static_cast<uint32_t>(g_rumble_until - now) : 0);
|
||||
}
|
||||
SDL_UnlockJoysticks();
|
||||
}
|
||||
|
||||
void SelectController(int controller) {
|
||||
if (g_controller != controller) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
g_controller = controller;
|
||||
g_large_motor = g_small_motor = 0;
|
||||
g_rumble_until = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void SDLCALL UpdateRumble(void*, SDL_AudioStream*, int, int) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (g_haptics_until != 0 && SDL_GetTicks() >= g_haptics_until) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
|
||||
void CloseDevice(Stream* stream) {
|
||||
if (stream->sdl != nullptr) {
|
||||
// Destroy outside g_mutex: SDL waits for any running get callback.
|
||||
SDL_DestroyAudioStream(stream->sdl);
|
||||
stream->sdl = nullptr;
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (--g_playback_streams == 0 && g_haptics_until != 0) {
|
||||
g_haptics_until = 0;
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_AudioDeviceID FindDevice() {
|
||||
int count = 0;
|
||||
auto* devices = SDL_GetAudioPlaybackDevices(&count);
|
||||
SDL_AudioDeviceID device = 0;
|
||||
for (int i = 0; i < count && device == 0; i++) {
|
||||
const char* name = SDL_GetAudioDeviceName(devices[i]);
|
||||
SDL_AudioSpec spec {};
|
||||
if (name != nullptr && SDL_strcasestr(name, "DualSense") != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(devices[i], &spec, nullptr) && spec.channels == 4) {
|
||||
device = devices[i];
|
||||
}
|
||||
}
|
||||
SDL_free(devices);
|
||||
return device;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Stream* Open(uint32_t freq) {
|
||||
if (freq == 0 || !SDL_InitSubSystem(SDL_INIT_AUDIO)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new Stream {.freq = freq};
|
||||
}
|
||||
|
||||
void Close(Stream* stream) {
|
||||
if (stream != nullptr) {
|
||||
CloseDevice(stream);
|
||||
delete stream;
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume) {
|
||||
if (stream == nullptr || data == nullptr || frames == 0 || channels == 0 || volume == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
CloseDevice(stream);
|
||||
return;
|
||||
}
|
||||
const auto now = SDL_GetTicks();
|
||||
if (now >= stream->next_check) {
|
||||
stream->next_check = now + 2000;
|
||||
const auto device = FindDevice();
|
||||
if (stream->device != device || stream->sdl == nullptr) {
|
||||
CloseDevice(stream);
|
||||
stream->device = device;
|
||||
if (device != 0) {
|
||||
const SDL_AudioSpec desired {SDL_AUDIO_F32, 4, static_cast<int>(stream->freq)};
|
||||
auto* sdl = SDL_OpenAudioDeviceStream(device, &desired, UpdateRumble, nullptr);
|
||||
SDL_AudioSpec actual {};
|
||||
if (sdl != nullptr &&
|
||||
SDL_GetAudioDeviceFormat(SDL_GetAudioStreamDevice(sdl), &actual, nullptr) &&
|
||||
actual.channels == 4 && SDL_ResumeAudioStreamDevice(sdl)) {
|
||||
stream->sdl = sdl;
|
||||
{
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_playback_streams++;
|
||||
}
|
||||
LOGF("DualSenseHaptics: playing on '%s'\n", SDL_GetAudioDeviceName(device));
|
||||
} else {
|
||||
SDL_DestroyAudioStream(sdl);
|
||||
LOGF("DualSenseHaptics: cannot open quad output: %s\n", SDL_GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stream->sdl == nullptr) {
|
||||
return;
|
||||
}
|
||||
stream->buffer.assign(static_cast<size_t>(frames) * 4, 0.0f);
|
||||
bool audible = false;
|
||||
for (uint32_t frame = 0; frame < frames; frame++) {
|
||||
for (uint32_t ch = 0; ch < 2; ch++) {
|
||||
const auto src_ch = channels == 1 ? 0 : ch;
|
||||
const auto index = static_cast<size_t>(frame) * channels + src_ch;
|
||||
float value = is_float ? static_cast<const float*>(data)[index]
|
||||
: static_cast<const int16_t*>(data)[index] / 32768.0f;
|
||||
value *= volume[src_ch] / 32768.0f;
|
||||
stream->buffer[static_cast<size_t>(frame) * 4 + ch + 2] = value;
|
||||
audible |= std::fabs(value) > 1.0f / 1024;
|
||||
}
|
||||
}
|
||||
auto queued = std::max(0, SDL_GetAudioStreamQueued(stream->sdl));
|
||||
if (queued > static_cast<int>(stream->freq * FRAME_BYTES * 80 / 1000)) {
|
||||
SDL_ClearAudioStream(stream->sdl);
|
||||
queued = 0;
|
||||
}
|
||||
const int bytes = static_cast<int>(frames * FRAME_BYTES);
|
||||
if (audible) {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) !=
|
||||
SDL_GAMEPAD_TYPE_PS5) {
|
||||
return;
|
||||
}
|
||||
SelectController(controller);
|
||||
const auto playback_now = SDL_GetTicks();
|
||||
const bool starting = g_haptics_until <= playback_now;
|
||||
const auto duration = (queued + bytes) * 1000ULL / (stream->freq * FRAME_BYTES);
|
||||
g_haptics_until =
|
||||
std::max(g_haptics_until, playback_now + std::max<uint64_t>(duration, 250));
|
||||
if (starting) {
|
||||
ApplyRumble();
|
||||
}
|
||||
}
|
||||
if (!SDL_PutAudioStreamData(stream->sdl, stream->buffer.data(), bytes)) {
|
||||
CloseDevice(stream);
|
||||
}
|
||||
}
|
||||
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor) {
|
||||
if (SDL_GetGamepadTypeForID(static_cast<SDL_JoystickID>(controller)) != SDL_GAMEPAD_TYPE_PS5) {
|
||||
return false;
|
||||
}
|
||||
Common::LockGuard lock(g_mutex);
|
||||
SelectController(controller);
|
||||
g_large_motor = large_motor;
|
||||
g_small_motor = small_motor;
|
||||
g_rumble_until = SDL_GetTicks() + 0xffff;
|
||||
ApplyRumble();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Common::LockGuard lock(g_mutex);
|
||||
g_rumble_until = 0;
|
||||
SelectController(-1);
|
||||
}
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Controller::DualSenseHaptics {
|
||||
|
||||
struct Stream;
|
||||
|
||||
Stream* Open(uint32_t freq);
|
||||
void Close(Stream* stream);
|
||||
void Queue(Stream* stream, int controller, const void* data, uint32_t frames, uint32_t channels,
|
||||
bool is_float, const int* volume);
|
||||
// Returns false for other gamepad types, which retain the normal rumble path.
|
||||
bool SetVibration(int controller, uint8_t large_motor, uint8_t small_motor);
|
||||
void Shutdown();
|
||||
|
||||
} // namespace Libs::Controller::DualSenseHaptics
|
||||
|
||||
#endif // EMULATOR_INCLUDE_EMULATOR_DUALSENSE_HAPTICS_H_
|
||||
@@ -0,0 +1,321 @@
|
||||
// Exercise the production module with real SDL streams and fake USB endpoints.
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
void Check(bool condition, const char* text) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "PadHapticsTests: %s\n", text);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
struct Device {
|
||||
SDL_AudioDeviceID id;
|
||||
const char* name;
|
||||
int channels;
|
||||
};
|
||||
struct Rumble {
|
||||
Uint16 large, small;
|
||||
Uint32 duration;
|
||||
};
|
||||
std::vector<Device> devices;
|
||||
std::vector<SDL_AudioStream*> streams;
|
||||
Rumble rumble {};
|
||||
Uint64 now = 1000;
|
||||
int audio_refs = 0, opens = 0, rumble_calls = 0;
|
||||
bool fail_open = false, fail_resume = false;
|
||||
int actual_channels = 4;
|
||||
SDL_AudioDeviceID opened_device = 0;
|
||||
SDL_AudioSpec opened_spec {};
|
||||
} // namespace
|
||||
|
||||
namespace Fake {
|
||||
Uint64 GetTicks() {
|
||||
return now;
|
||||
}
|
||||
bool InitSubSystem(SDL_InitFlags flags) {
|
||||
const bool result = SDL_InitSubSystem(flags);
|
||||
if (result && (flags & SDL_INIT_AUDIO)) {
|
||||
audio_refs++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void QuitSubSystem(SDL_InitFlags flags) {
|
||||
if (flags & SDL_INIT_AUDIO) {
|
||||
audio_refs--;
|
||||
}
|
||||
SDL_QuitSubSystem(flags);
|
||||
}
|
||||
SDL_AudioDeviceID* GetAudioPlaybackDevices(int* count) {
|
||||
*count = static_cast<int>(devices.size());
|
||||
auto* ids = static_cast<SDL_AudioDeviceID*>(
|
||||
SDL_malloc((devices.size() + 1) * sizeof(SDL_AudioDeviceID)));
|
||||
for (size_t i = 0; i < devices.size(); i++) {
|
||||
ids[i] = devices[i].id;
|
||||
}
|
||||
ids[devices.size()] = 0;
|
||||
return ids;
|
||||
}
|
||||
const char* GetAudioDeviceName(SDL_AudioDeviceID id) {
|
||||
for (const auto& device: devices) {
|
||||
if (device.id == id) {
|
||||
return device.name;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool GetAudioDeviceFormat(SDL_AudioDeviceID id, SDL_AudioSpec* spec, int*) {
|
||||
if (id == 999) {
|
||||
*spec = {SDL_AUDIO_F32, actual_channels, 48000};
|
||||
return true;
|
||||
}
|
||||
for (const auto& device: devices) {
|
||||
if (device.id == id) {
|
||||
*spec = {SDL_AUDIO_F32, device.channels, 48000};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
SDL_AudioStream* OpenAudioDeviceStream(SDL_AudioDeviceID id, const SDL_AudioSpec* spec,
|
||||
SDL_AudioStreamCallback callback, void* userdata) {
|
||||
opens++;
|
||||
opened_device = id;
|
||||
opened_spec = *spec;
|
||||
if (fail_open) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* stream = SDL_CreateAudioStream(spec, spec);
|
||||
Check(stream != nullptr && SDL_SetAudioStreamGetCallback(stream, callback, userdata),
|
||||
"real SDL stream/callback creation failed");
|
||||
streams.push_back(stream);
|
||||
return stream;
|
||||
}
|
||||
bool ResumeAudioStreamDevice(SDL_AudioStream*) {
|
||||
return !fail_resume;
|
||||
}
|
||||
void DestroyAudioStream(SDL_AudioStream* stream) {
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto it = std::find(streams.begin(), streams.end(), stream);
|
||||
Check(it != streams.end(), "stream destroyed twice");
|
||||
streams.erase(it);
|
||||
SDL_DestroyAudioStream(stream);
|
||||
}
|
||||
SDL_AudioDeviceID GetAudioStreamDevice(SDL_AudioStream*) {
|
||||
return 999;
|
||||
}
|
||||
SDL_GamepadType GetGamepadTypeForID(SDL_JoystickID id) {
|
||||
return id == 1 ? SDL_GAMEPAD_TYPE_PS5 : SDL_GAMEPAD_TYPE_UNKNOWN;
|
||||
}
|
||||
SDL_Gamepad* GetGamepadFromID(SDL_JoystickID id) {
|
||||
return id == 1 ? reinterpret_cast<SDL_Gamepad*>(static_cast<uintptr_t>(id)) : nullptr;
|
||||
}
|
||||
bool RumbleGamepad(SDL_Gamepad*, Uint16 large, Uint16 small, Uint32 duration) {
|
||||
rumble = {large, small, duration};
|
||||
rumble_calls++;
|
||||
return true;
|
||||
}
|
||||
} // namespace Fake
|
||||
|
||||
#define SDL_GetTicks Fake::GetTicks
|
||||
#define SDL_InitSubSystem Fake::InitSubSystem
|
||||
#define SDL_QuitSubSystem Fake::QuitSubSystem
|
||||
#define SDL_GetAudioPlaybackDevices Fake::GetAudioPlaybackDevices
|
||||
#define SDL_GetAudioDeviceName Fake::GetAudioDeviceName
|
||||
#define SDL_GetAudioDeviceFormat Fake::GetAudioDeviceFormat
|
||||
#define SDL_OpenAudioDeviceStream Fake::OpenAudioDeviceStream
|
||||
#define SDL_ResumeAudioStreamDevice Fake::ResumeAudioStreamDevice
|
||||
#define SDL_DestroyAudioStream Fake::DestroyAudioStream
|
||||
#define SDL_GetAudioStreamDevice Fake::GetAudioStreamDevice
|
||||
#define SDL_GetGamepadTypeForID Fake::GetGamepadTypeForID
|
||||
#define SDL_GetGamepadFromID Fake::GetGamepadFromID
|
||||
#define SDL_RumbleGamepad Fake::RumbleGamepad
|
||||
#include "libs/dualSenseHaptics.cpp"
|
||||
#undef SDL_GetTicks
|
||||
#undef SDL_InitSubSystem
|
||||
#undef SDL_QuitSubSystem
|
||||
#undef SDL_GetAudioPlaybackDevices
|
||||
#undef SDL_GetAudioDeviceName
|
||||
#undef SDL_GetAudioDeviceFormat
|
||||
#undef SDL_OpenAudioDeviceStream
|
||||
#undef SDL_ResumeAudioStreamDevice
|
||||
#undef SDL_DestroyAudioStream
|
||||
#undef SDL_GetAudioStreamDevice
|
||||
#undef SDL_GetGamepadTypeForID
|
||||
#undef SDL_GetGamepadFromID
|
||||
#undef SDL_RumbleGamepad
|
||||
|
||||
namespace {
|
||||
namespace Haptics = Libs::Controller::DualSenseHaptics;
|
||||
using Port = std::unique_ptr<Haptics::Stream, decltype(&Haptics::Close)>;
|
||||
constexpr std::array<int, 2> unity {32768, 32768};
|
||||
constexpr std::array<float, 4> pcm {0.5f, 0.5f, 0.5f, 0.5f};
|
||||
|
||||
struct Fixture {
|
||||
Fixture() {
|
||||
devices = {{10, "Speakers (DualSense Wireless Controller)", 4}};
|
||||
rumble = {};
|
||||
now = 1000;
|
||||
opens = rumble_calls = 0;
|
||||
fail_open = fail_resume = false;
|
||||
actual_channels = 4;
|
||||
}
|
||||
~Fixture() {
|
||||
Haptics::Shutdown();
|
||||
Check(streams.empty() && audio_refs == 0, "stream or audio subsystem reference leaked");
|
||||
}
|
||||
};
|
||||
|
||||
Port Open() {
|
||||
auto* stream = Haptics::Open(48000);
|
||||
Check(stream != nullptr, "port open failed");
|
||||
return Port(stream, Haptics::Close);
|
||||
}
|
||||
void Queue(const Port& port, const void* data, uint32_t frames = 2, uint32_t channels = 2,
|
||||
bool is_float = true, const int* volume = unity.data()) {
|
||||
Haptics::Queue(port.get(), 1, data, frames, channels, is_float, volume);
|
||||
}
|
||||
void Pull() {
|
||||
std::array<float, 64> buffer {};
|
||||
Check(!streams.empty() &&
|
||||
SDL_GetAudioStreamData(streams.back(), buffer.data(), sizeof(buffer)) >= 0,
|
||||
"audio device pull failed");
|
||||
}
|
||||
void ExpectPcm(std::array<float, 8> expected) {
|
||||
std::array<float, 8> actual {};
|
||||
Check(!streams.empty() && SDL_GetAudioStreamData(streams.back(), actual.data(),
|
||||
sizeof(actual)) == sizeof(actual),
|
||||
"wrong output byte count");
|
||||
for (size_t i = 0; i < actual.size(); i++) {
|
||||
Check(std::abs(actual[i] - expected[i]) < 1e-6f, "haptics mapping or volume is wrong");
|
||||
}
|
||||
}
|
||||
|
||||
void TestFormatsAndVolume() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
const std::array<int, 2> volume {32768, 16384};
|
||||
const std::array<float, 4> stereo {0.5f, -0.25f, 1.0f, 0.0f};
|
||||
Queue(port, stereo.data(), 2, 2, true, volume.data());
|
||||
Check(opened_device == 10 && opened_spec.channels == 4 && opened_spec.format == SDL_AUDIO_F32,
|
||||
"did not select quad DualSense PCM");
|
||||
ExpectPcm({0, 0, 0.5f, -0.125f, 0, 0, 1.0f, 0});
|
||||
const std::array<int16_t, 2> mono {16384, -32768};
|
||||
Queue(port, mono.data(), 2, 1, false, volume.data() + 1);
|
||||
ExpectPcm({0, 0, 0.25f, 0.25f, 0, 0, -0.5f, -0.5f});
|
||||
std::array<float, 24> multichannel {};
|
||||
multichannel.fill(0.9f);
|
||||
multichannel[0] = 0.1f;
|
||||
multichannel[1] = 0.2f;
|
||||
multichannel[12] = 0.3f;
|
||||
multichannel[13] = 0.4f;
|
||||
Queue(port, multichannel.data(), 2, 12);
|
||||
ExpectPcm({0, 0, 0.1f, 0.2f, 0, 0, 0.3f, 0.4f});
|
||||
}
|
||||
|
||||
void TestDiscoveryAndHotplug() {
|
||||
Fixture f;
|
||||
devices = {{10, "DualSense Wireless Controller", 2}, {20, "Other speakers", 4}};
|
||||
auto port = Open();
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.empty() && opens == 0, "stereo controller or unrelated speakers selected");
|
||||
devices[0].channels = 4;
|
||||
now += 2001;
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.size() == 1 && opened_device == 10, "late USB connection was not discovered");
|
||||
devices.clear();
|
||||
now += 2001;
|
||||
Queue(port, pcm.data());
|
||||
Check(streams.empty(), "disconnected endpoint retained its stream");
|
||||
}
|
||||
|
||||
void TestFailuresAndBoundedQueue() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
std::array<float, 128> block {};
|
||||
block.fill(0.5f);
|
||||
fail_open = true;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty() && audio_refs == 1, "failed open leaked a stream/reference");
|
||||
fail_open = false;
|
||||
fail_resume = true;
|
||||
now += 2001;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty() && audio_refs == 1, "failed resume leaked a stream/reference");
|
||||
fail_resume = false;
|
||||
actual_channels = 2;
|
||||
now += 2001;
|
||||
Queue(port, block.data(), 64);
|
||||
Check(streams.empty(), "post-open stereo downgrade accepted");
|
||||
actual_channels = 4;
|
||||
now += 2001;
|
||||
for (int i = 0; i < 70; i++) {
|
||||
Queue(port, block.data(), 64);
|
||||
}
|
||||
constexpr int block_bytes = 64 * 4 * sizeof(float);
|
||||
constexpr int max_queue = 48000 * 4 * sizeof(float) * 80 / 1000;
|
||||
Check(streams.size() == 1 &&
|
||||
SDL_GetAudioStreamQueued(streams.back()) <= max_queue + block_bytes,
|
||||
"queue grows indefinitely when its clock stalls");
|
||||
}
|
||||
|
||||
void TestRumbleLeaseAndDuration() {
|
||||
Fixture f;
|
||||
auto port = Open();
|
||||
Check(Haptics::SetVibration(1, 100, 50), "set rumble failed");
|
||||
Check(rumble.large == 100 * 0x101 && rumble.duration == 65535, "ordinary rumble changed");
|
||||
Queue(port, pcm.data());
|
||||
Check(rumble.large == 0 && rumble.small == 0, "rumble was not stopped before haptics");
|
||||
now = 1100;
|
||||
const std::array<float, 4> silence {};
|
||||
Queue(port, silence.data());
|
||||
now = 1249;
|
||||
Pull();
|
||||
Check(rumble.large == 0, "rumble resumed before lease expiration");
|
||||
now = 1250;
|
||||
Pull();
|
||||
Check(rumble.large == 100 * 0x101 && rumble.small == 50 * 0x101 && rumble.duration == 65285,
|
||||
"idle haptics did not restore remaining rumble duration");
|
||||
now = 66500;
|
||||
Queue(port, pcm.data());
|
||||
now = 66751;
|
||||
Pull();
|
||||
Check(rumble.large == 0 && rumble.duration == 0, "expired rumble restarted after haptics");
|
||||
Haptics::Shutdown();
|
||||
const int calls = rumble_calls;
|
||||
Pull();
|
||||
Check(rumble_calls == calls, "audio pull touched rumble after shutdown");
|
||||
}
|
||||
|
||||
void TestCloseRestoresRumble() {
|
||||
Fixture f;
|
||||
auto first = Open(), second = Open();
|
||||
Haptics::SetVibration(1, 100, 50);
|
||||
Queue(first, pcm.data());
|
||||
Queue(second, pcm.data());
|
||||
Check(rumble.large == 0, "haptics did not suppress rumble");
|
||||
first.reset();
|
||||
Check(rumble.large == 0, "closing one of two streams restored rumble early");
|
||||
second.reset();
|
||||
Check(rumble.large == 100 * 0x101 && rumble.small == 50 * 0x101,
|
||||
"closing the final stream left rumble suppressed");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
TestFormatsAndVolume();
|
||||
TestDiscoveryAndHotplug();
|
||||
TestFailuresAndBoundedQueue();
|
||||
TestRumbleLeaseAndDuration();
|
||||
TestCloseRestoresRumble();
|
||||
std::printf("PadHapticsTests: all cases passed\n");
|
||||
}
|
||||
@@ -23770,7 +23770,7 @@ TestCase BufferLoadFormatXChecksOnlyTransferredComponent() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferStoreFormatXChecksOnlyTransferredComponent() {
|
||||
TestCase BufferStoreFormatXRejectsPartialRecord() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
@@ -23781,10 +23781,10 @@ TestCase BufferStoreFormatXChecksOnlyTransferredComponent() {
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "BufferStoreFormatXChecksOnlyTransferredComponent";
|
||||
test.name = "BufferStoreFormatXRejectsPartialRecord";
|
||||
test.code = std::move(code);
|
||||
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
||||
test.expected = {0x11111111u, 0x22222222u, 0x33333333u, 0xaaaaaaaau};
|
||||
test.expected = test.initial;
|
||||
test.storage_buffer_range_dwords = 4;
|
||||
test.user_data = MakeStructuredStorageBufferData(
|
||||
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
||||
@@ -23817,7 +23817,7 @@ TestCase BufferLoadFormatXyChecksOnlyTransferredComponents() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferStoreFormatXyChecksOnlyTransferredComponents() {
|
||||
TestCase BufferStoreFormatXyRejectsPartialRecord() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
@@ -23829,10 +23829,10 @@ TestCase BufferStoreFormatXyChecksOnlyTransferredComponents() {
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "BufferStoreFormatXyChecksOnlyTransferredComponents";
|
||||
test.name = "BufferStoreFormatXyRejectsPartialRecord";
|
||||
test.code = std::move(code);
|
||||
test.initial = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
||||
test.expected = {0x11111111u, 0x22222222u, 0xaaaaaaaau, 0xbbbbbbbbu};
|
||||
test.expected = test.initial;
|
||||
test.storage_buffer_range_dwords = 4;
|
||||
test.user_data = MakeStructuredStorageBufferData(
|
||||
0, 4, false, BufferFormat(Prospero::BufferFormat::k32_32_32_32Float));
|
||||
@@ -24147,6 +24147,60 @@ TestCase BufferStoreFormatXResource16UintPreservesAdjacentLanes() {
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferStoreFormatXyzwPackedUnormSkinningVectors() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
// The captured skinning dispatch stores a compressed quaternion with W = 1.
|
||||
constexpr std::array<float, 12> values = {
|
||||
0.25f, 0.5f, 0.75f, 1.0f,
|
||||
-1.0f, 0.0f, 2.0f, 1.0f,
|
||||
0.5f, 0.5f, 0.5f, 0.5f};
|
||||
std::vector<u32> code;
|
||||
for (u32 record = 0; record < 3; record++) {
|
||||
for (u32 component = 0; component < 4; component++) {
|
||||
AppendVMovLiteral(&code, component,
|
||||
std::bit_cast<u32>(values[record * 4 + component]));
|
||||
}
|
||||
AppendVMovU32(&code, 20, record);
|
||||
code.push_back(EncodeMubuf0(0x07u, 0, true, false));
|
||||
code.push_back(EncodeMubuf1(0, 0, 20));
|
||||
}
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "BufferStoreFormatXyzwPackedUnormSkinningVectors";
|
||||
test.code = std::move(code);
|
||||
test.initial = std::vector<u32>(4, 0xdeadbeefu);
|
||||
test.expected = {0xeff80100u, 0xfff00000u, 0xa0080200u, 0xdeadbeefu};
|
||||
test.user_data = MakeStructuredStorageBufferData(
|
||||
4, 3, false, BufferFormat(Prospero::BufferFormat::k10_10_10_2UNorm));
|
||||
test.has_user_data = true;
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_XYZW, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferStoreFormatXZeroFillsPackedRecord() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
std::vector<u32> code;
|
||||
AppendVMovLiteral(&code, 0, std::bit_cast<u32>(0.5f));
|
||||
AppendVMovU32(&code, 20, 4);
|
||||
code.push_back(EncodeMubuf0(0x04u));
|
||||
code.push_back(EncodeMubuf1(0, 0, 20));
|
||||
AppendEnd(&code);
|
||||
|
||||
TestCase test;
|
||||
test.name = "BufferStoreFormatXZeroFillsPackedRecord";
|
||||
test.code = std::move(code);
|
||||
test.initial = {0x11111111u, 0xffffffffu, 0x33333333u};
|
||||
test.expected = {0x11111111u, 0x00000200u, 0x33333333u};
|
||||
test.user_data = MakeStructuredStorageBufferData(
|
||||
0, 12, false, BufferFormat(Prospero::BufferFormat::k10_10_10_2UNorm));
|
||||
test.has_user_data = true;
|
||||
test.opcodes = {O::V_MOV_B32, O::BUFFER_STORE_FORMAT_X, O::S_ENDPGM};
|
||||
return test;
|
||||
}
|
||||
|
||||
TestCase BufferStoreFormatXyzwFloat16ConvertsComponents() {
|
||||
using O = ShaderOpcode;
|
||||
|
||||
@@ -29090,9 +29144,9 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(BufferLoadFormatXyzwRejectsPartialRecord);
|
||||
AddCase(BufferStoreFormatXyzwDropsPartialRecord);
|
||||
AddCase(BufferLoadFormatXChecksOnlyTransferredComponent);
|
||||
AddCase(BufferStoreFormatXChecksOnlyTransferredComponent);
|
||||
AddCase(BufferStoreFormatXRejectsPartialRecord);
|
||||
AddCase(BufferLoadFormatXyChecksOnlyTransferredComponents);
|
||||
AddCase(BufferStoreFormatXyChecksOnlyTransferredComponents);
|
||||
AddCase(BufferStoreFormatXyRejectsPartialRecord);
|
||||
AddCase(BufferStoreVariants);
|
||||
AddCase(BufferFormatVariants);
|
||||
AddCase(BufferLoadFormatXyzwSnapshotsOverlappingAddress);
|
||||
@@ -29103,6 +29157,8 @@ std::vector<TestCase> MakeCases() {
|
||||
AddCase(BufferFormatStoreVariants);
|
||||
AddCase(BufferStoreFormatXResource16UintWritesHalfword);
|
||||
AddCase(BufferStoreFormatXResource16UintPreservesAdjacentLanes);
|
||||
AddCase(BufferStoreFormatXyzwPackedUnormSkinningVectors);
|
||||
AddCase(BufferStoreFormatXZeroFillsPackedRecord);
|
||||
AddCase(BufferStoreFormatXyzwFloat16ConvertsComponents);
|
||||
AddCase(BufferStoreFormatXyzwSnorm16CapturedSkinningVectors);
|
||||
AddCase(BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords);
|
||||
@@ -33836,15 +33892,19 @@ int main(int argc, char **argv) {
|
||||
RunCase(&vulkan, VectorVopcCmpxOrderedCapturedExecMask());
|
||||
return 0;
|
||||
}
|
||||
if (argc == 2 && std::strcmp(argv[1], "--buffer-snorm-store-only") == 0) {
|
||||
if (argc == 2 && std::strcmp(argv[1], "--buffer-format-store-only") == 0) {
|
||||
VulkanHarness vulkan;
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwPackedUnormSkinningVectors());
|
||||
RunCase(&vulkan, BufferStoreFormatXZeroFillsPackedRecord());
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwFloat16ConvertsComponents());
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwSnorm16CapturedSkinningVectors());
|
||||
RunCase(&vulkan, BufferStoreFormatXSnorm16ClampsRoundsAndPreservesHalfwords());
|
||||
RunCase(&vulkan, BufferStoreFormatXResource16UintWritesHalfword());
|
||||
RunCase(&vulkan, BufferStoreFormatXResource16UintPreservesAdjacentLanes());
|
||||
RunCase(&vulkan, BufferStoreFormatXyResource88UintWritesBytes());
|
||||
RunCase(&vulkan, BufferStoreFormatXyzwDropsPartialRecord());
|
||||
RunCase(&vulkan, BufferStoreFormatXChecksOnlyTransferredComponent());
|
||||
RunCase(&vulkan, BufferStoreFormatXRejectsPartialRecord());
|
||||
RunCase(&vulkan, BufferStoreFormatXyRejectsPartialRecord());
|
||||
RunCase(&vulkan, BufferFormatStoreVariants());
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user