diff --git a/src/libs/hmd2.h b/src/libs/hmd2.h new file mode 100644 index 00000000..ed24869a --- /dev/null +++ b/src/libs/hmd2.h @@ -0,0 +1,24 @@ +#ifndef EMULATOR_SRC_LIBS_HMD2_H_ +#define EMULATOR_SRC_LIBS_HMD2_H_ + +#include +#include + +namespace Libs::Hmd2 { + +inline constexpr uint32_t PANEL_WIDTH = 4000; +inline constexpr uint32_t PANEL_HEIGHT = 2040; +inline constexpr uint64_t OUTPUT_MODE_89_91HZ = 0x2000c; +inline constexpr uint64_t OUTPUT_MODE_119_88HZ = 0x2000f; +inline constexpr int32_t ERROR_REPROJECTION_NOT_INITIALIZED = -1972240373; // 0x8a72000b + +struct ReprojectionState { + std::atomic output_mode {0}; + std::atomic timing_us {3000}; +}; + +[[nodiscard]] ReprojectionState* GetReprojectionState(); + +} // namespace Libs::Hmd2 + +#endif // EMULATOR_SRC_LIBS_HMD2_H_ diff --git a/src/libs/libHmd2.cpp b/src/libs/libHmd2.cpp index dc643c66..f1dd5469 100644 --- a/src/libs/libHmd2.cpp +++ b/src/libs/libHmd2.cpp @@ -1,11 +1,16 @@ #include "common/emulatorConfig.h" +#include "graphics/guest_gpu/tile.h" +#include "libs/agc.h" #include "libs/errno.h" +#include "libs/hmd2.h" #include "libs/libs.h" #include "loader/symbolDatabase.h" #include #include #include +#include +#include namespace Libs { @@ -16,6 +21,8 @@ namespace Hmd2 { constexpr int32_t ERROR_ALREADY_INITIALIZED = -1972240383; // 0x8a720001 constexpr int32_t ERROR_NOT_INITIALIZED = -1972240382; // 0x8a720002 constexpr int32_t ERROR_PARAMETER_NULL = -1972240376; // 0x8a720008 +constexpr int32_t ERROR_REPROJECTION_ALREADY_INITIALIZED = -1972240372; // 0x8a72000c +constexpr int32_t ERROR_REPROJECTION_IN_VR_MODE = -1972240356; // 0x8a72001c struct SceHmd2InitializeParam { void* reserved0; @@ -41,14 +48,59 @@ struct SceHmd2DeviceInformation { float virtualImageDistance; }; +struct SceHmd2FieldOfView { + float tanOut; + float tanIn; + float tanTop; + float tanBottom; +}; + +struct SceHmd2ReprojectionInitializeParam { + void* pReprojectionBuff; + void* pDisplayBuff; + int32_t threadPriority; + int32_t cpuAffinityMask; + uint32_t pipeId; + uint32_t queueId; + uint32_t type; + void* pSeeThroughBuff; + int32_t reprojectionTiming; + uint32_t reserved[5]; +}; + static_assert(sizeof(SceHmd2InitializeParam) == 16); static_assert(offsetof(SceHmd2InitializeParam, reserved) == 8); static_assert(sizeof(SceHmd2DeviceInformation) == 28); static_assert(offsetof(SceHmd2DeviceInformation, deviceInfo) == 8); static_assert(offsetof(SceHmd2DeviceInformation, hmuMount) == 20); static_assert(offsetof(SceHmd2DeviceInformation, virtualImageDistance) == 24); +static_assert(sizeof(SceHmd2FieldOfView) == 16); +static_assert(alignof(SceHmd2FieldOfView) == 4); +static_assert(sizeof(SceHmd2ReprojectionInitializeParam) == 72); +static_assert(alignof(SceHmd2ReprojectionInitializeParam) == 8); +static_assert(offsetof(SceHmd2ReprojectionInitializeParam, threadPriority) == 16); +static_assert(offsetof(SceHmd2ReprojectionInitializeParam, type) == 32); +static_assert(offsetof(SceHmd2ReprojectionInitializeParam, pSeeThroughBuff) == 40); +static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reprojectionTiming) == 48); +static_assert(offsetof(SceHmd2ReprojectionInitializeParam, reserved) == 52); static std::atomic g_initialized = false; +static std::mutex g_reprojection_mutex; +static std::atomic g_reprojection_state {nullptr}; + +ReprojectionState* GetReprojectionState() { + return g_reprojection_state.load(std::memory_order_acquire); +} + +static Graphics::SizeAlign ReprojectionDisplaySize() { + // The host display target is one uncompressed tiled 10-bit panel surface. + Graphics::TileSizeAlign size {}; + Graphics::TileGetTextureTotalSize(Graphics::Prospero::BufferFormat::k10_10_10_2UNorm, + PANEL_WIDTH, PANEL_HEIGHT, 1, 1, + Graphics::Prospero::TileMode::kRenderTarget, false, size); + EXIT_IF(size.size == 0 || size.align == 0); + return {size.size, size.align}; +} static int32_t KYTY_SYSV_ABI Hmd2Initialize(const SceHmd2InitializeParam* param) { PRINT_NAME(); @@ -76,7 +128,7 @@ static int32_t KYTY_SYSV_ABI Hmd2GetDeviceInformation(SceHmd2DeviceInformation* info->hmuMount = 1; info->lensSeparationDistance = 63; // Headset characteristics: 4000x2040 panel and 2 m virtual image distance. - info->deviceInfo.panelResolution = {4000, 2040}; + info->deviceInfo.panelResolution = {PANEL_WIDTH, PANEL_HEIGHT}; info->virtualImageDistance = 2.0f; // Host presentation has no emulated headset scan-out delay; latency remains zero. } else { @@ -88,11 +140,100 @@ static int32_t KYTY_SYSV_ABI Hmd2GetDeviceInformation(SceHmd2DeviceInformation* return OK; } +static int32_t KYTY_SYSV_ABI Hmd2GetFieldOfViewWithoutHandle(SceHmd2FieldOfView* fov) { + PRINT_NAME(); + if (!g_initialized.load()) { + return ERROR_NOT_INITIALIZED; + } + if (fov == nullptr) { + return ERROR_PARAMETER_NULL; + } + // Virtual ER15 field of view: 55/42.5/51.2/51.2 degrees. + // These tangents describe that geometry, not bit-exact firmware output. + *fov = {1.4281480312f, 0.9163311720f, 1.2437491417f, 1.2437491417f}; + LOGF("Hmd2: FOV tangents out=%f in=%f top=%f bottom=%f\n", fov->tanOut, fov->tanIn, + fov->tanTop, fov->tanBottom); + return OK; +} + +static Graphics::SizeAlign KYTY_SYSV_ABI Hmd2ReprojectionQueryBufferSizeAlign() { + const Graphics::SizeAlign size {sizeof(ReprojectionState), alignof(ReprojectionState)}; + LOGF("Hmd2: reprojection work size=%" PRIu64 " alignment=%zu\n", size.m_size, size.m_align); + return size; +} + +static Graphics::SizeAlign KYTY_SYSV_ABI Hmd2ReprojectionQueryDisplayBufferSizeAlign() { + const auto size = ReprojectionDisplaySize(); + LOGF("Hmd2: reprojection display size=%" PRIu64 " alignment=%zu\n", size.m_size, + size.m_align); + return size; +} + +static int32_t KYTY_SYSV_ABI +Hmd2ReprojectionInitialize(const SceHmd2ReprojectionInitializeParam* param, void*) { + std::scoped_lock lock {g_reprojection_mutex}; + if (GetReprojectionState() != nullptr) { + return ERROR_REPROJECTION_ALREADY_INITIALIZED; + } + if (param == nullptr || param->pReprojectionBuff == nullptr || param->pDisplayBuff == nullptr) { + return ERROR_PARAMETER_NULL; + } + EXIT_NOT_IMPLEMENTED(param->pSeeThroughBuff != nullptr); + const auto display_size = ReprojectionDisplaySize(); + auto* state = new (param->pReprojectionBuff) ReprojectionState; + if (param->reprojectionTiming != 0) { + state->timing_us.store(static_cast(param->reprojectionTiming), + std::memory_order_relaxed); + } + g_reprojection_state.store(state, std::memory_order_release); + LOGF("Hmd2: reprojection initialized work=%p display=%p size=%" PRIu64 " timing=%u\n", + static_cast(state), param->pDisplayBuff, display_size.m_size, + state->timing_us.load(std::memory_order_relaxed)); + return OK; +} + +static int32_t KYTY_SYSV_ABI Hmd2ReprojectionEnableVrMode(uint64_t output_mode) { + auto* state = GetReprojectionState(); + if (state == nullptr) { + return ERROR_REPROJECTION_NOT_INITIALIZED; + } + if (output_mode != OUTPUT_MODE_89_91HZ && output_mode != OUTPUT_MODE_119_88HZ) { + return VideoOut::VIDEO_OUT_ERROR_INVALID_VALUE; + } + if (!Config::VrEnabled()) { + return VideoOut::VIDEO_OUT_ERROR_NO_DEVICE; + } + uint64_t expected = 0; + if (!state->output_mode.compare_exchange_strong(expected, output_mode)) { + return ERROR_REPROJECTION_IN_VR_MODE; + } + LOGF("Hmd2: VR output mode=0x%" PRIx64 "\n", output_mode); + return OK; +} + +static int32_t KYTY_SYSV_ABI Hmd2ReprojectionSetTiming(int32_t timing) { + auto* state = GetReprojectionState(); + if (state == nullptr) { + return ERROR_REPROJECTION_NOT_INITIALIZED; + } + EXIT_NOT_IMPLEMENTED(timing < 2000 || timing > 7000); + const auto rounded = static_cast((timing + 99) / 100 * 100); + state->timing_us.store(rounded, std::memory_order_release); + LOGF("Hmd2: reprojection timing=%u us\n", rounded); + return OK; +} + } // namespace Hmd2 LIB_DEFINE(InitHmd2_1) { LIB_FUNC("c812oYs7Vsc", Hmd2::Hmd2Initialize); LIB_FUNC("bIi4YUfSRys", Hmd2::Hmd2GetDeviceInformation); + LIB_FUNC("gF8+lvc7GuQ", Hmd2::Hmd2GetFieldOfViewWithoutHandle); + LIB_FUNC("U-CnbmeyYaA", Hmd2::Hmd2ReprojectionQueryBufferSizeAlign); + LIB_FUNC("-C2nkoEYOnU", Hmd2::Hmd2ReprojectionQueryDisplayBufferSizeAlign); + LIB_FUNC("C0rPwER-yxg", Hmd2::Hmd2ReprojectionInitialize); + LIB_FUNC("VVvFh51o20s", Hmd2::Hmd2ReprojectionEnableVrMode); + LIB_FUNC("FkQX7rjFomk", Hmd2::Hmd2ReprojectionSetTiming); } } // namespace Libs