Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion aurora-main/lib/gx/gx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,8 @@ void populate_pipeline_config(PipelineConfig& config, GXPrimitive primitive, GXV
config.pixelFmt = g_gxState.pixelFmt;
config.dstAlpha = effective_dst_alpha(g_gxState.pixelFmt, g_gxState.alphaUpdate, g_gxState.dstAlpha);
config.depthCompare = g_gxState.depthCompare;
config.depthUpdate = g_gxState.depthUpdate;
// As on the hardware (and in Dolphin): with the Z compare off, the Z buffer isn't updated either.
config.depthUpdate = g_gxState.depthCompare && g_gxState.depthUpdate;
config.alphaUpdate = effective_alpha_update(g_gxState.pixelFmt, g_gxState.alphaUpdate);
config.colorUpdate = g_gxState.colorUpdate;
}
Expand Down
6 changes: 6 additions & 0 deletions runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ target_include_directories(mkw_vr_eye_gaze_tests PRIVATE "${CMAKE_CURRENT_LIST_D
target_compile_features(mkw_vr_eye_gaze_tests PRIVATE cxx_std_17)
add_test(NAME mkw_vr_eye_gaze_tests COMMAND mkw_vr_eye_gaze_tests)

# The AX mix kernels' AVX2 and NEON forms against their scalar reference loops.
add_executable(mkw_ax_mix_kernels_tests "${CMAKE_CURRENT_LIST_DIR}/tests/ax_mix_kernels_tests.cpp")
target_include_directories(mkw_ax_mix_kernels_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
target_compile_features(mkw_ax_mix_kernels_tests PRIVATE cxx_std_17)
add_test(NAME mkw_ax_mix_kernels_tests COMMAND mkw_ax_mix_kernels_tests)

# The in-headset settings panel's controller chord, release latch, selection,
# scrolling and canvas mapping, plus the thread bridge they publish through.
add_executable(mkw_vr_settings_panel_tests tests/vr_settings_panel_tests.cpp src/vr/openxr_settings_panel.cpp)
Expand Down
9 changes: 7 additions & 2 deletions runtime/include/guest_flat_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,15 @@ bool IsActive();
// accesses must use the checked Memory::* path.
// Windows user mode and x86-64 always use a 4 KiB base page, so those builds
// fold this to a compile-time false: it appears in every flat access and must
// not become a hot-path load. Only AArch64, where the page size is a kernel
// configuration (4/16/64 KiB), has to probe it at runtime.
// not become a hot-path load. On AArch64 the page size is a kernel
// configuration (4/16/64 KiB), so it is probed at runtime, except in the native
// Steam Frame build: that build only runs on the headset, whose SteamOS kernel
// uses 4 KiB pages, and Initialize() refuses to start on any other page size.
#if defined(_WIN32) || defined(__x86_64__)
#define MKW_GUEST_FLAT_FIXED_PAGE_SIZE 1
#elif defined(MKW_HEADSET_STEAM_FRAME) && !defined(__ANDROID__)
#define MKW_GUEST_FLAT_FIXED_PAGE_SIZE 1
#define MKW_GUEST_FLAT_VERIFY_PAGE_SIZE 1
#endif

#if defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
Expand Down
24 changes: 21 additions & 3 deletions runtime/include/nand_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,37 @@ inline bool CopyBootstrapFile(const std::filesystem::path& sourceRoot,
std::error_code& ec) {
const auto source = sourceRoot / relativePath;
const auto destination = destinationRoot / relativePath;
auto copyOptions = std::filesystem::copy_options::none;
if (std::filesystem::exists(destination, ec)) {
return !ec;
if (ec) {
return false;
}
// Keep whatever the player has, except an empty file where the payload has content. WC24
// rejects a zero-length download or friend list outright and MKW reports that as a save
// error; since seeding only ran for missing files, such a file stayed broken for good.
const auto existingSize = std::filesystem::file_size(destination, ec);
if (ec) {
return false;
}
std::error_code sourceError;
const auto sourceSize = std::filesystem::file_size(source, sourceError);
if (existingSize != 0 || sourceError || sourceSize == 0) {
return true;
}
copyOptions = std::filesystem::copy_options::overwrite_existing;
} else if (ec) {
return false;
}

std::filesystem::create_directories(destination.parent_path(), ec);
if (ec) {
return false;
}
std::filesystem::copy_file(source, destination, std::filesystem::copy_options::none, ec);
std::filesystem::copy_file(source, destination, copyOptions, ec);
return !ec;
}

// Create these WC24 files only for a new profile; never overwrite user data.
// Create these WC24 files for a new profile, or refill one left empty; never overwrite user data.
constexpr std::string_view kBootstrapFiles[] = {
"shared2/wc24/misc.bin",
"shared2/wc24/nwc24dl.bin",
Expand Down
51 changes: 38 additions & 13 deletions runtime/include/runtime_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -1064,14 +1064,24 @@ inline const std::optional<std::string>& ControllerButton(size_t index) {
// user-specific paths. This is used by the in-game F10 settings bar.
inline bool WriteSetting(std::string_view section, std::string_view key, std::string_view value) {
const auto path = ResolveConfigPath();
std::ifstream input(path);
std::vector<std::string> lines;
std::string line;
while (std::getline(input, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
std::error_code existsError;
if (std::filesystem::exists(path, existsError)) {
// A file that's there but can't be read is not an empty one: rewriting it from nothing
// would throw away every other setting.
std::ifstream input(path);
std::string line;
while (input && std::getline(input, line)) {
if (!line.empty() && line.back() == '\r') {
line.pop_back();
}
lines.push_back(std::move(line));
}
if (!input.eof()) {
std::cerr << "[runtime-config] Unable to read " << PathToUtf8(path) << "; not saving "
<< key << std::endl;
return false;
}
lines.push_back(std::move(line));
}

const std::string normalizedSection = Trim(section);
Expand Down Expand Up @@ -1124,19 +1134,34 @@ inline bool WriteSetting(std::string_view section, std::string_view key, std::st
}
}

// Written beside the file and renamed over it, so a crash or a full disk mid-write leaves the
// old file whole rather than a truncated one.
std::error_code ec;
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path(), ec);
}
std::ofstream output(path, std::ios::trunc);
if (!output) {
std::cerr << "[runtime-config] Unable to write " << PathToUtf8(path) << std::endl;
return false;
std::filesystem::path temporary = path;
temporary += ".tmp";
{
std::ofstream output(temporary, std::ios::trunc);
for (const auto& outputLine : lines) {
output << outputLine << '\n';
}
output.close();
if (!output) {
std::cerr << "[runtime-config] Unable to write " << PathToUtf8(temporary) << std::endl;
std::filesystem::remove(temporary, ec);
return false;
}
}
for (const auto& outputLine : lines) {
output << outputLine << '\n';
std::filesystem::rename(temporary, path, ec);
if (ec) {
std::cerr << "[runtime-config] Unable to replace " << PathToUtf8(path) << ": " << ec.message()
<< std::endl;
std::filesystem::remove(temporary, ec);
return false;
}
return static_cast<bool>(output);
return true;
}

inline std::string FormatString(std::string_view value) {
Expand Down
13 changes: 11 additions & 2 deletions runtime/src/guest_flat_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>

#include "memory.h"
Expand Down Expand Up @@ -61,8 +62,9 @@ constexpr size_t kAllocationGranularity = 0x10000; // 64 KiB
constexpr size_t kHostPageSize = 0x1000;

// Only hosts that can expose a page larger than 4 KiB need to discover their
// size at runtime; see RequiresCheckedAccess() in guest_flat_memory.h.
#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
// size at runtime, or check it when the build assumes 4 KiB; see
// RequiresCheckedAccess() in guest_flat_memory.h.
#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE) || defined(MKW_GUEST_FLAT_VERIFY_PAGE_SIZE)
size_t HostPageSize()
{
const long size = sysconf(_SC_PAGESIZE);
Expand Down Expand Up @@ -521,6 +523,13 @@ void Initialize(const std::vector<RegionRequest>& regions) {

#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE)
g_requiresCheckedAccess = HostPageSize() > kGuestPageSize;
#elif defined(MKW_GUEST_FLAT_VERIFY_PAGE_SIZE)
if (HostPageSize() != kGuestPageSize) {
throw std::runtime_error(
"This is a Steam Frame build, which assumes the headset's 4 KiB memory pages, but this "
"kernel uses " + std::to_string(HostPageSize()) + "-byte pages. Build without "
"--headset steam_frame for this device.");
}
#endif

if (g_initialized) {
Expand Down
150 changes: 150 additions & 0 deletions runtime/src/hle/audio/ax_mix_kernels.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@
#if defined(__AVX2__)
#include <immintrin.h>
#define MKW_AX_MIX_AVX2 1
#define MKW_AX_MIX_NEON 0
#elif defined(__aarch64__)
// Advanced SIMD is architectural on AArch64: the NEON forms below are the arm64 counterparts of
// the AVX2 kernels, processing eight 16-bit samples per step as two int32x4 halves.
#include <arm_neon.h>
#define MKW_AX_MIX_AVX2 0
#define MKW_AX_MIX_NEON 1
#else
#define MKW_AX_MIX_AVX2 0
#define MKW_AX_MIX_NEON 0
#endif

namespace AxMixKernels {
Expand Down Expand Up @@ -99,10 +107,63 @@ inline uint16_t MixAddRampAvx2(int32_t* out, const int16_t* input, uint32_t coun
}
#endif

#if MKW_AX_MIX_NEON
inline uint16_t MixAddRampNeon(int32_t* out, const int16_t* input, uint32_t count,
uint16_t volume, uint16_t delta, int16_t& dpop) {
uint32_t i = 0;
int16_t last = dpop;
if (count >= 8) {
// Same ramp model as the AVX2 form: volume + k*delta stays inside int32 for every count
// the AX mix uses, and the 16-bit wrap is applied only where the scalar loop applies it.
const int32x4_t lanesLo = {0, 1, 2, 3};
const int32x4_t lanesHi = {4, 5, 6, 7};
const int32x4_t deltaVec = vdupq_n_s32(static_cast<int32_t>(delta));
const int32x4_t wrapMask = vdupq_n_s32(0xFFFF);
const int32x4_t blockStep = vdupq_n_s32(static_cast<int32_t>(delta) * 8);
const int32x4_t base = vdupq_n_s32(static_cast<int32_t>(volume));
int32x4_t rampLo = vmlaq_s32(base, lanesLo, deltaVec);
int32x4_t rampHi = vmlaq_s32(base, lanesHi, deltaVec);
int32x4_t lastBlock = vdupq_n_s32(0);
for (; i + 8 <= count; i += 8) {
const int16x8_t samples = vld1q_s16(input + i);
const int32x4_t sLo = vmovl_s16(vget_low_s16(samples));
const int32x4_t sHi = vmovl_s16(vget_high_s16(samples));
// int16 x uint16 fits in int32: the low 32-bit product is exact. Signed 32 -> 16
// saturation after the >> 15 is exactly clamp(-0x8000, 0x7fff).
int32x4_t scaledLo = vshrq_n_s32(vmulq_s32(sLo, vandq_s32(rampLo, wrapMask)), 15);
int32x4_t scaledHi = vshrq_n_s32(vmulq_s32(sHi, vandq_s32(rampHi, wrapMask)), 15);
scaledLo = vmovl_s16(vqmovn_s32(scaledLo));
scaledHi = vmovl_s16(vqmovn_s32(scaledHi));
vst1q_s32(out + i, vaddq_s32(vld1q_s32(out + i), scaledLo));
vst1q_s32(out + i + 4, vaddq_s32(vld1q_s32(out + i + 4), scaledHi));
lastBlock = scaledHi;
rampLo = vaddq_s32(rampLo, blockStep);
rampHi = vaddq_s32(rampHi, blockStep);
}
if (i != 0) {
last = static_cast<int16_t>(vgetq_lane_s32(lastBlock, 3));
volume = static_cast<uint16_t>(static_cast<uint32_t>(vgetq_lane_s32(rampLo, 0)));
}
}
for (; i < count; ++i) {
const int32_t scaled =
(static_cast<int32_t>(input[i]) * static_cast<int32_t>(volume)) >> 15;
const int16_t sample = ClampToS16(scaled);
out[i] += sample;
volume = static_cast<uint16_t>(volume + delta);
last = sample;
}
dpop = last;
return volume;
}
#endif

inline uint16_t MixAddRamp(int32_t* out, const int16_t* input, uint32_t count,
uint16_t volume, uint16_t delta, int16_t& dpop) {
#if MKW_AX_MIX_AVX2
return MixAddRampAvx2(out, input, count, volume, delta, dpop);
#elif MKW_AX_MIX_NEON
return MixAddRampNeon(out, input, count, volume, delta, dpop);
#else
return MixAddRampScalar(out, input, count, volume, delta, dpop);
#endif
Expand Down Expand Up @@ -162,10 +223,50 @@ inline uint16_t ScaleRampAvx2(int16_t* samples, uint32_t count, uint16_t volume,
}
#endif

#if MKW_AX_MIX_NEON
inline uint16_t ScaleRampNeon(int16_t* samples, uint32_t count, uint16_t volume,
uint16_t delta) {
uint32_t i = 0;
if (count >= 8) {
const int32x4_t lanesLo = {0, 1, 2, 3};
const int32x4_t lanesHi = {4, 5, 6, 7};
const int32x4_t deltaVec = vdupq_n_s32(static_cast<int32_t>(delta));
const int32x4_t wrapMask = vdupq_n_s32(0xFFFF);
const int32x4_t blockStep = vdupq_n_s32(static_cast<int32_t>(delta) * 8);
const int32x4_t base = vdupq_n_s32(static_cast<int32_t>(volume));
int32x4_t rampLo = vmlaq_s32(base, lanesLo, deltaVec);
int32x4_t rampHi = vmlaq_s32(base, lanesHi, deltaVec);
for (; i + 8 <= count; i += 8) {
const int16x8_t block = vld1q_s16(samples + i);
const int32x4_t scaledLo = vshrq_n_s32(
vmulq_s32(vmovl_s16(vget_low_s16(block)), vandq_s32(rampLo, wrapMask)), 15);
const int32x4_t scaledHi = vshrq_n_s32(
vmulq_s32(vmovl_s16(vget_high_s16(block)), vandq_s32(rampHi, wrapMask)), 15);
// Signed 32 -> 16 saturation is exactly clamp(-0x8000, 0x7fff).
vst1q_s16(samples + i, vcombine_s16(vqmovn_s32(scaledLo), vqmovn_s32(scaledHi)));
rampLo = vaddq_s32(rampLo, blockStep);
rampHi = vaddq_s32(rampHi, blockStep);
}
if (i != 0) {
volume = static_cast<uint16_t>(static_cast<uint32_t>(vgetq_lane_s32(rampLo, 0)));
}
}
for (; i < count; ++i) {
const int32_t scaled =
(static_cast<int32_t>(samples[i]) * static_cast<int32_t>(volume)) >> 15;
samples[i] = ClampToS16(scaled);
volume = static_cast<uint16_t>(volume + delta);
}
return volume;
}
#endif

inline uint16_t ScaleRamp(int16_t* samples, uint32_t count, uint16_t volume,
uint16_t delta) {
#if MKW_AX_MIX_AVX2
return ScaleRampAvx2(samples, count, volume, delta);
#elif MKW_AX_MIX_NEON
return ScaleRampNeon(samples, count, volume, delta);
#else
return ScaleRampScalar(samples, count, volume, delta);
#endif
Expand Down Expand Up @@ -214,10 +315,34 @@ inline void MixAccumRamp32Avx2(int32_t* dst, const int32_t* src, const uint16_t*
}
#endif

#if MKW_AX_MIX_NEON
inline void MixAccumRamp32Neon(int32_t* dst, const int32_t* src, const uint16_t* ramp,
uint32_t count) {
uint32_t i = 0;
for (; i + 4 <= count; i += 4) {
const int32x4_t source = vld1q_s32(src + i);
// The ramp is unsigned 16-bit, so it is a non-negative int32 and the widening signed
// multiply produces the exact 64-bit product (|product| < 2^47); the arithmetic >> 15
// then narrows to the same bits the scalar (int32)(p >> 15) keeps.
const int32x4_t gain = vreinterpretq_s32_u32(vmovl_u16(vld1_u16(ramp + i)));
const int64x2_t lo = vshrq_n_s64(vmull_s32(vget_low_s32(source), vget_low_s32(gain)), 15);
const int64x2_t hi = vshrq_n_s64(vmull_high_s32(source, gain), 15);
const int32x4_t result = vcombine_s32(vmovn_s64(lo), vmovn_s64(hi));
vst1q_s32(dst + i, vaddq_s32(vld1q_s32(dst + i), result));
}
for (; i < count; ++i) {
dst[i] += static_cast<int32_t>(
(static_cast<int64_t>(src[i]) * static_cast<int64_t>(ramp[i])) >> 15);
}
}
#endif

inline void MixAccumRamp32(int32_t* dst, const int32_t* src, const uint16_t* ramp,
uint32_t count) {
#if MKW_AX_MIX_AVX2
MixAccumRamp32Avx2(dst, src, ramp, count);
#elif MKW_AX_MIX_NEON
MixAccumRamp32Neon(dst, src, ramp, count);
#else
MixAccumRamp32Scalar(dst, src, ramp, count);
#endif
Expand Down Expand Up @@ -270,9 +395,32 @@ inline void LoadBigEndian32Avx2(int32_t* dst, const uint8_t* src, size_t count)
}
#endif

#if MKW_AX_MIX_NEON
// rev32 on byte lanes is the whole byte swap; four words per step.
inline void StoreBigEndian32Neon(uint8_t* dst, const int32_t* src, size_t count) {
size_t i = 0;
for (; i + 4 <= count; i += 4) {
const uint8x16_t value = vreinterpretq_u8_s32(vld1q_s32(src + i));
vst1q_u8(dst + i * sizeof(uint32_t), vrev32q_u8(value));
}
StoreBigEndian32Scalar(dst + i * sizeof(uint32_t), src + i, count - i);
}

inline void LoadBigEndian32Neon(int32_t* dst, const uint8_t* src, size_t count) {
size_t i = 0;
for (; i + 4 <= count; i += 4) {
const uint8x16_t value = vld1q_u8(src + i * sizeof(uint32_t));
vst1q_s32(dst + i, vreinterpretq_s32_u8(vrev32q_u8(value)));
}
LoadBigEndian32Scalar(dst + i, src + i * sizeof(uint32_t), count - i);
}
#endif

inline void StoreBigEndian32(uint8_t* dst, const int32_t* src, size_t count) {
#if MKW_AX_MIX_AVX2
StoreBigEndian32Avx2(dst, src, count);
#elif MKW_AX_MIX_NEON
StoreBigEndian32Neon(dst, src, count);
#else
StoreBigEndian32Scalar(dst, src, count);
#endif
Expand All @@ -281,6 +429,8 @@ inline void StoreBigEndian32(uint8_t* dst, const int32_t* src, size_t count) {
inline void LoadBigEndian32(int32_t* dst, const uint8_t* src, size_t count) {
#if MKW_AX_MIX_AVX2
LoadBigEndian32Avx2(dst, src, count);
#elif MKW_AX_MIX_NEON
LoadBigEndian32Neon(dst, src, count);
#else
LoadBigEndian32Scalar(dst, src, count);
#endif
Expand Down
Loading
Loading