mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 08:00:25 +02:00
- Added SetRenderScale method to both D3D12 and Vulkan backends to adjust the render scale dynamically. - Updated eye size calculations to reflect the new render scale in both backends. - Implemented ResizeWritablePair method to handle resizing of swapchains based on the requested eye sizes. - Modified BeginFrame methods to ensure swapchains are resized appropriately before rendering. - Enhanced error handling to maintain current eye sizes when the requested sizes cannot be allocated. - Added tests to validate render scale functionality, ensuring correct behavior when scaling up and down, including handling of refused sizes.
330 lines
13 KiB
C++
330 lines
13 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include "vr/openxr_config.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace mkw::vr {
|
|
|
|
inline constexpr uint32_t kOpenXREyeCount = 2;
|
|
|
|
enum class OpenXRLogLevel {
|
|
Info,
|
|
Warning,
|
|
Error,
|
|
};
|
|
|
|
using OpenXRLogCallback =
|
|
std::function<void(OpenXRLogLevel level, std::string_view message)>;
|
|
|
|
struct OpenXRError {
|
|
XrResult result = XR_SUCCESS;
|
|
std::string operation;
|
|
std::string message;
|
|
|
|
explicit operator bool() const {
|
|
return XR_FAILED(result) || !message.empty();
|
|
}
|
|
};
|
|
|
|
struct OpenXRViewConfiguration {
|
|
XrViewConfigurationView properties{XR_TYPE_VIEW_CONFIGURATION_VIEW};
|
|
uint32_t render_width = 0;
|
|
uint32_t render_height = 0;
|
|
};
|
|
|
|
struct OpenXREyeSize {
|
|
uint32_t width = 0;
|
|
uint32_t height = 0;
|
|
|
|
bool operator==(const OpenXREyeSize& other) const noexcept {
|
|
return width == other.width && height == other.height;
|
|
}
|
|
bool operator!=(const OpenXREyeSize& other) const noexcept { return !(*this == other); }
|
|
};
|
|
|
|
// A view's image at `scale` of the size the runtime recommends, never below one
|
|
// pixel or above the runtime's maximum. The size the session starts at, and the
|
|
// one a backend rebuilds its eyes at when the render scale changes live.
|
|
inline OpenXREyeSize OpenXRScaledEyeSize(const XrViewConfigurationView& view, float scale) noexcept {
|
|
const auto scaled = [scale](uint32_t recommended, uint32_t maximum) {
|
|
const double value = std::round(static_cast<double>(recommended) * static_cast<double>(scale));
|
|
return static_cast<uint32_t>(std::clamp(value, 1.0, static_cast<double>(std::max(maximum, 1u))));
|
|
};
|
|
return {scaled(view.recommendedImageRectWidth, view.maxImageRectWidth),
|
|
scaled(view.recommendedImageRectHeight, view.maxImageRectHeight)};
|
|
}
|
|
|
|
struct OpenXRRuntimeInfo {
|
|
std::string runtime_name;
|
|
XrVersion runtime_version = 0;
|
|
std::string system_name;
|
|
uint32_t vendor_id = 0;
|
|
uint32_t max_layer_count = 0;
|
|
bool supports_orientation_tracking = false;
|
|
bool supports_position_tracking = false;
|
|
};
|
|
|
|
struct OpenXRReferenceSpaceChange {
|
|
uint64_t serial = 0;
|
|
XrReferenceSpaceType type = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
XrTime change_time = 0;
|
|
bool pose_in_previous_space_valid = false;
|
|
XrPosef pose_in_previous_space{{0.0f, 0.0f, 0.0f, 1.0f},
|
|
{0.0f, 0.0f, 0.0f}};
|
|
};
|
|
|
|
// A frame token is produced by WaitFrame and must be passed back to the other
|
|
// frame functions. This prevents an old pose/timestamp from being paired with a
|
|
// newer compositor frame.
|
|
struct OpenXRFrame {
|
|
uint64_t serial = 0;
|
|
XrTime predicted_display_time = 0;
|
|
XrDuration predicted_display_period = 0;
|
|
bool should_render = false;
|
|
bool views_valid = false;
|
|
XrViewStateFlags view_state_flags = 0;
|
|
std::array<XrView, kOpenXREyeCount> views{};
|
|
};
|
|
|
|
enum class OpenXREventStatus {
|
|
Continue,
|
|
ExitRequested,
|
|
Error,
|
|
};
|
|
|
|
enum class OpenXRFrameStatus {
|
|
Ready,
|
|
SessionNotRunning,
|
|
ExitRequested,
|
|
Error,
|
|
};
|
|
|
|
// Owns the backend-neutral OpenXR object graph and session state machine.
|
|
//
|
|
// A graphics backend performs its requirements/device work after Initialize(),
|
|
// then passes its XrGraphicsBinding* structure to CreateSession(). Swapchains are
|
|
// intentionally not owned here: D3D12 and Vulkan need different image wrapping
|
|
// and synchronization strategies.
|
|
//
|
|
// OpenXRRuntime is not internally synchronized. PollEvents and all frame calls
|
|
// must be serialized by one XR-owner thread. Read-only handles may be handed to
|
|
// the graphics integration only while the corresponding object is alive.
|
|
class OpenXRRuntime final {
|
|
public:
|
|
explicit OpenXRRuntime(OpenXRLogCallback logger = {});
|
|
~OpenXRRuntime();
|
|
|
|
OpenXRRuntime(const OpenXRRuntime&) = delete;
|
|
OpenXRRuntime& operator=(const OpenXRRuntime&) = delete;
|
|
OpenXRRuntime(OpenXRRuntime&&) = delete;
|
|
OpenXRRuntime& operator=(OpenXRRuntime&&) = delete;
|
|
|
|
// Same-device Vulkan runtimes may touch Dawn's queue in frame/swapchain
|
|
// calls. Share Dawn's device guard; never hold it across xrWaitFrame.
|
|
struct GraphicsQueueGuard {
|
|
GraphicsQueueGuard(void* value, void (*release)(void*)) : token(value), unlock(release) {}
|
|
GraphicsQueueGuard(const GraphicsQueueGuard&) = delete;
|
|
GraphicsQueueGuard& operator=(const GraphicsQueueGuard&) = delete;
|
|
void* token;
|
|
void (*unlock)(void*);
|
|
~GraphicsQueueGuard() { if (token && unlock) unlock(token); }
|
|
};
|
|
void SetGraphicsQueueGuard(void* (*lock)(), void (*unlock)(void*)) {
|
|
m_queue_lock = lock; m_queue_unlock = unlock;
|
|
}
|
|
GraphicsQueueGuard LockGraphicsQueue() const {
|
|
return {m_queue_lock ? m_queue_lock() : nullptr, m_queue_unlock};
|
|
}
|
|
|
|
// Creates the instance, resolves the HMD system, and enumerates the stereo
|
|
// view configuration. Returns false without terminating the application;
|
|
// the caller should continue in non-VR mode.
|
|
bool Initialize(const OpenXRConfig& config);
|
|
|
|
// graphics_binding is the address of an XrGraphicsBinding* structure and is
|
|
// forwarded through XrSessionCreateInfo::next. It must remain valid only for
|
|
// the duration of this call.
|
|
bool CreateSession(const void* graphics_binding);
|
|
|
|
// Loads an extension entry point from this instance. Backends use this to
|
|
// query graphics requirements before Aurora creates/selects its device.
|
|
bool GetInstanceProcAddress(const char* name, PFN_xrVoidFunction* function);
|
|
|
|
template <typename Function>
|
|
bool LoadFunction(const char* name, Function* function) {
|
|
static_assert(std::is_pointer_v<Function>,
|
|
"OpenXR PFN typedefs must be pointer types");
|
|
return GetInstanceProcAddress(
|
|
name, reinterpret_cast<PFN_xrVoidFunction*>(function));
|
|
}
|
|
|
|
// Requests an orderly exit when running, processes STOPPING for a bounded
|
|
// period, then releases spaces and the session. The instance/system remain
|
|
// available so a backend session can be recreated.
|
|
void DestroySession();
|
|
|
|
// Releases every owned object. Safe to call repeatedly.
|
|
void Shutdown();
|
|
|
|
// Polls every currently queued event and owns xrBeginSession/xrEndSession
|
|
// transitions for READY/STOPPING. EXITING and LOSS_PENDING are surfaced to
|
|
// the application rather than terminating it here.
|
|
OpenXREventStatus PollEvents();
|
|
bool RequestExitSession();
|
|
|
|
// Graphics backends call OpenXR entry points directly for swapchain work.
|
|
// Feed their results back here so positive loss-pending and negative
|
|
// session/instance-loss results update the central lifecycle state.
|
|
void ObserveResult(XrResult result) noexcept;
|
|
|
|
// Explicit frame protocol. A successful WaitFrame must be followed by
|
|
// BeginFrame and EndFrame using the same token. LocateViews is optional when
|
|
// should_render is false; it is otherwise normally called after BeginFrame.
|
|
OpenXRFrameStatus WaitFrame(OpenXRFrame& frame);
|
|
bool BeginFrame(const OpenXRFrame& frame);
|
|
bool LocateViews(OpenXRFrame& frame);
|
|
// Locates the views for `display_time` outside the frame protocol, for a
|
|
// packet whose eyes are rendered before the compositor frame that will show
|
|
// them is begun (the standalone backend's render-first pacing). Fills the
|
|
// frame's display time, views, flags and validity; requires a running session.
|
|
bool LocateViewsAt(XrTime display_time, OpenXRFrame& frame);
|
|
bool EndFrame(
|
|
const OpenXRFrame& frame,
|
|
const XrCompositionLayerBaseHeader* const* layers,
|
|
uint32_t layer_count);
|
|
bool EndFrame(
|
|
const OpenXRFrame& frame,
|
|
const std::vector<const XrCompositionLayerBaseHeader*>& layers);
|
|
bool EndFrameWithoutLayers(const OpenXRFrame& frame);
|
|
|
|
// Recreates the application reference space with a caller-provided offset.
|
|
// This is the application-side recenter primitive; call only between frames.
|
|
bool ResetAppSpace(const XrPosef& pose_in_reference_space);
|
|
|
|
bool IsInitialized() const { return m_instance != XR_NULL_HANDLE; }
|
|
bool HasSession() const { return m_session != XR_NULL_HANDLE; }
|
|
bool IsSessionRunning() const { return m_session_running; }
|
|
bool IsSessionFocused() const {
|
|
return m_session_state == XR_SESSION_STATE_FOCUSED;
|
|
}
|
|
bool IsSessionVisible() const {
|
|
return m_session_state == XR_SESSION_STATE_VISIBLE ||
|
|
m_session_state == XR_SESSION_STATE_FOCUSED;
|
|
}
|
|
bool ShouldExit() const {
|
|
return m_exit_requested || m_session_loss_pending || m_instance_loss_pending;
|
|
}
|
|
|
|
XrInstance Instance() const { return m_instance; }
|
|
XrSystemId SystemId() const { return m_system_id; }
|
|
XrSession Session() const { return m_session; }
|
|
XrSpace AppSpace() const { return m_app_space; }
|
|
XrSpace ViewSpace() const { return m_view_space; }
|
|
XrSessionState SessionState() const { return m_session_state; }
|
|
uint64_t SessionRunSerial() const { return m_session_run_serial; }
|
|
// Advances each time the runtime reports that a hand's interaction profile
|
|
// changed (a controller picked up or put down).
|
|
uint64_t InteractionProfileSerial() const { return m_interaction_profile_serial; }
|
|
XrReferenceSpaceType AppSpaceType() const { return m_app_space_type; }
|
|
XrEnvironmentBlendMode EnvironmentBlendMode() const { return m_blend_mode; }
|
|
|
|
const OpenXRConfig& Config() const { return m_config; }
|
|
const OpenXRRuntimeInfo& RuntimeInfo() const { return m_runtime_info; }
|
|
const std::array<OpenXRViewConfiguration, kOpenXREyeCount>& ViewConfiguration() const {
|
|
return m_view_configuration;
|
|
}
|
|
const std::vector<std::string>& EnabledExtensions() const {
|
|
return m_enabled_extensions;
|
|
}
|
|
const std::vector<int64_t>& SwapchainFormats() const { return m_swapchain_formats; }
|
|
const OpenXRReferenceSpaceChange& LastReferenceSpaceChange() const {
|
|
return m_reference_space_change;
|
|
}
|
|
// Consumes every queued application-space change whose effective time is
|
|
// no later than display_time. Multiple future recenter events are retained
|
|
// independently rather than overwriting one another.
|
|
bool ConsumeAppSpaceChangesThrough(XrTime display_time);
|
|
const OpenXRError& LastError() const { return m_last_error; }
|
|
|
|
private:
|
|
void* (*m_queue_lock)() = nullptr;
|
|
void (*m_queue_unlock)(void*) = nullptr;
|
|
enum class FramePhase {
|
|
Idle,
|
|
Waited,
|
|
Begun,
|
|
};
|
|
|
|
bool EnumerateInstanceCapabilities();
|
|
bool CreateInstance();
|
|
bool InitializeSystem();
|
|
bool EnumerateViewConfiguration();
|
|
bool SelectEnvironmentBlendMode();
|
|
bool CreateReferenceSpaces();
|
|
bool EnumerateSwapchainFormats();
|
|
bool HandleSessionStateChanged(const XrEventDataSessionStateChanged& event);
|
|
bool IsFrameTokenCurrent(const OpenXRFrame& frame, FramePhase expected) const;
|
|
bool LocateViewsForFrame(OpenXRFrame& frame);
|
|
void ResetFrameState();
|
|
void DestroyReferenceSpaces();
|
|
void ResetSessionState();
|
|
void ResetInstanceState();
|
|
|
|
bool Check(XrResult result, std::string_view operation);
|
|
bool Fail(XrResult result, std::string_view operation, std::string_view detail);
|
|
void ClearError();
|
|
void Log(OpenXRLogLevel level, std::string_view message) const noexcept;
|
|
std::string ResultString(XrResult result) const;
|
|
|
|
OpenXRLogCallback m_logger;
|
|
OpenXRConfig m_config;
|
|
OpenXRError m_last_error;
|
|
|
|
XrInstance m_instance = XR_NULL_HANDLE;
|
|
XrSystemId m_system_id = XR_NULL_SYSTEM_ID;
|
|
XrSession m_session = XR_NULL_HANDLE;
|
|
XrSpace m_app_space = XR_NULL_HANDLE;
|
|
XrSpace m_view_space = XR_NULL_HANDLE;
|
|
|
|
XrSessionState m_session_state = XR_SESSION_STATE_UNKNOWN;
|
|
XrReferenceSpaceType m_app_space_type = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
XrEnvironmentBlendMode m_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
|
|
bool m_session_running = false;
|
|
bool m_exit_requested = false;
|
|
bool m_session_loss_pending = false;
|
|
bool m_instance_loss_pending = false;
|
|
bool m_shutting_down_session = false;
|
|
|
|
FramePhase m_frame_phase = FramePhase::Idle;
|
|
uint64_t m_session_run_serial = 0;
|
|
uint64_t m_interaction_profile_serial = 0;
|
|
uint64_t m_next_frame_serial = 1;
|
|
uint64_t m_active_frame_serial = 0;
|
|
XrTime m_active_frame_display_time = 0;
|
|
|
|
OpenXRRuntimeInfo m_runtime_info;
|
|
std::array<OpenXRViewConfiguration, kOpenXREyeCount> m_view_configuration{};
|
|
std::vector<std::string> m_available_extensions;
|
|
std::vector<std::string> m_available_api_layers;
|
|
std::vector<std::string> m_enabled_extensions;
|
|
std::vector<std::string> m_enabled_api_layers;
|
|
std::vector<XrReferenceSpaceType> m_supported_reference_spaces;
|
|
std::vector<XrEnvironmentBlendMode> m_supported_blend_modes;
|
|
std::vector<int64_t> m_swapchain_formats;
|
|
OpenXRReferenceSpaceChange m_reference_space_change;
|
|
std::vector<OpenXRReferenceSpaceChange> m_pending_app_space_changes;
|
|
};
|
|
|
|
} // namespace mkw::vr
|