mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 17:00:32 +02:00
174 lines
7.8 KiB
C++
174 lines
7.8 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#if defined(MKW_ENABLE_OPENXR)
|
|
|
|
#include "vr/openxr_runtime.h"
|
|
#include "vr/openxr_settings_panel.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
|
|
namespace mkw::vr {
|
|
|
|
// Backend-neutral frame vocabulary shared by every graphics binding.
|
|
//
|
|
// The OpenXR pacing thread in openxr_integration.cpp is written against these
|
|
// types and the method surface documented on OpenXRD3D12Backend; each concrete
|
|
// backend (D3D12 on Windows, Vulkan on Android) implements that same surface so
|
|
// the pacing, retained-layer and policy logic is compiled once for both.
|
|
|
|
enum class OpenXRFrameMode {
|
|
ImmersiveProjection,
|
|
VirtualScreen,
|
|
};
|
|
|
|
enum class OpenXRBeginStatus {
|
|
Ready,
|
|
SessionNotRunning,
|
|
ExitRequested,
|
|
Error,
|
|
};
|
|
|
|
enum class OpenXRSubmissionStatus {
|
|
Success,
|
|
// GPU work may have touched the compositor image or the shared buffers with no completion
|
|
// marker to wait on; the session cannot continue.
|
|
Failed,
|
|
// The eye copy was not submitted and nothing touched the compositor image or the shared
|
|
// buffers, so the frame may end without a layer and the next one is tried normally.
|
|
Skipped,
|
|
Timeout,
|
|
ShuttingDown,
|
|
};
|
|
|
|
// The headset settings panel as a compositor quad layer of its own, over the
|
|
// eyes or the menu screen, so the eye resolution never limits its text. Its
|
|
// image is rendered with the frame's eyes (Aurora's panel stereo target) into a
|
|
// swapchain of the panel canvas's own size. Nothing is allocated or copied
|
|
// until the panel first opens, and nothing is submitted while it is closed.
|
|
struct OpenXRPanelLayer {
|
|
// The frame renders the panel's image: set by the pacing thread when the
|
|
// panel is open, cleared by a backend that could not provide the layer.
|
|
bool requested = false;
|
|
// Where it hangs in the application space, once the head pose is known.
|
|
bool placed = false;
|
|
XrPosef pose{{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}};
|
|
float width_meters = 0.0f;
|
|
float height_meters = 0.0f;
|
|
};
|
|
|
|
// The panel image's size, which is the settings panel canvas's.
|
|
inline constexpr uint32_t kOpenXRPanelLayerWidth = static_cast<uint32_t>(kSettingsPanelWidthPixels);
|
|
inline constexpr uint32_t kOpenXRPanelLayerHeight = static_cast<uint32_t>(kSettingsPanelHeightPixels);
|
|
|
|
// How many more pacing cycles an eye swapchain pair replaced by a new render
|
|
// resolution lives on before it is destroyed. The pair was last shown by the
|
|
// xrEndFrame of the cycle before its replacement, and the compositor keeps
|
|
// reading that layer's images until a later one lands and its own GPU work on
|
|
// them is done; the spec allows a runtime to use the images after
|
|
// xrDestroySwapchain, but destroying a pair the compositor was still consuming
|
|
// lost the Vulkan device on a PC runtime. Each cycle ends a compositor frame,
|
|
// so this many later frames have replaced the pair's picture by then.
|
|
inline constexpr uint32_t kOpenXRRetiredSwapchainCycles = 8;
|
|
|
|
// The panel's layer, submitted after (so over) the scene's.
|
|
inline XrCompositionLayerQuad OpenXRPanelQuadLayer(const OpenXRPanelLayer& panel, XrSpace space,
|
|
XrSwapchain swapchain) noexcept {
|
|
XrCompositionLayerQuad quad{XR_TYPE_COMPOSITION_LAYER_QUAD};
|
|
// ImGui leaves premultiplied colour in the cleared panel image.
|
|
quad.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
|
quad.space = space;
|
|
quad.eyeVisibility = XR_EYE_VISIBILITY_BOTH;
|
|
quad.subImage.swapchain = swapchain;
|
|
quad.subImage.imageRect = {{0, 0},
|
|
{static_cast<int32_t>(kOpenXRPanelLayerWidth),
|
|
static_cast<int32_t>(kOpenXRPanelLayerHeight)}};
|
|
quad.subImage.imageArrayIndex = 0;
|
|
quad.pose = panel.pose;
|
|
quad.size = {panel.width_meters, panel.height_meters};
|
|
return quad;
|
|
}
|
|
|
|
// The part of the virtual screen's image that holds anything. Aurora
|
|
// letterboxes the desktop snapshot into that eye-sized image exactly like this
|
|
// (webgpu::calculate_present_viewport_for_aspect) and, when the settings panel
|
|
// is drawn into the eyes, centres it at kSettingsPanelWidthFraction of the
|
|
// width; the rest is black. An unknown content_aspect (0) keeps the whole image.
|
|
inline XrRect2Di OpenXRVirtualScreenContentRect(uint32_t width, uint32_t height, float content_aspect) noexcept {
|
|
XrRect2Di rect{{0, 0}, {static_cast<int32_t>(width), static_cast<int32_t>(height)}};
|
|
if (width == 0 || height == 0 || !(content_aspect > 0.0f)) {
|
|
return rect;
|
|
}
|
|
uint32_t content_width = width;
|
|
uint32_t content_height = std::min<uint32_t>(
|
|
height, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(
|
|
static_cast<double>(width) * static_cast<double>(1.0f / content_aspect)))));
|
|
if (content_height == height) {
|
|
content_width = std::min<uint32_t>(
|
|
width, std::max<uint32_t>(1u, static_cast<uint32_t>(std::lround(static_cast<double>(height) *
|
|
static_cast<double>(content_aspect)))));
|
|
}
|
|
const uint32_t panel_width = std::min<uint32_t>(
|
|
width, static_cast<uint32_t>(std::ceil(static_cast<double>(width) * kSettingsPanelWidthFraction)));
|
|
const uint32_t panel_height = std::min<uint32_t>(
|
|
height, static_cast<uint32_t>(std::ceil(static_cast<double>(panel_width) * kSettingsPanelHeightPixels /
|
|
kSettingsPanelWidthPixels)));
|
|
const uint32_t shown_width = std::max(content_width, panel_width);
|
|
const uint32_t shown_height = std::max(content_height, panel_height);
|
|
rect.offset = {static_cast<int32_t>((width - shown_width) / 2), static_cast<int32_t>((height - shown_height) / 2)};
|
|
rect.extent = {static_cast<int32_t>(shown_width), static_cast<int32_t>(shown_height)};
|
|
return rect;
|
|
}
|
|
|
|
struct OpenXRPresentation {
|
|
OpenXRFrameMode mode = OpenXRFrameMode::ImmersiveProjection;
|
|
|
|
// Used only by VirtualScreen.
|
|
float quad_distance_meters = 2.0f;
|
|
float quad_width_meters = 2.4f;
|
|
// The desktop snapshot's width over height, which Aurora letterboxes into
|
|
// the screen's image (see OpenXRVirtualScreenContentRect); 0 while unknown.
|
|
float quad_content_aspect = 0.0f;
|
|
|
|
// When quad_anchored is set, the quad is placed at quad_pose in the
|
|
// application reference space and stays put as the player looks around.
|
|
// Otherwise it falls back to being head-locked in XR_VIEW_SPACE, centered
|
|
// straight ahead at -Z, which is what happens until tracking has produced a
|
|
// head pose good enough to anchor against.
|
|
bool quad_anchored = false;
|
|
XrPosef quad_pose{{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}};
|
|
|
|
// Used only by ImmersiveProjection: Aurora left each eye transparent outside
|
|
// the race's 2D-layer screen (AuroraStereoFrame::window), so the projection
|
|
// layer is blended by its alpha over whatever is under it.
|
|
bool immersive_window = false;
|
|
// The immersive window's eyes were aimed through the window itself, so each
|
|
// is only the window: its image is render_width x render_height, the
|
|
// top-left part of the eye's swapchain image the layer shows.
|
|
bool window_eyes = false;
|
|
|
|
// Show the room through the headset's cameras around the virtual screen or
|
|
// the immersive window (OpenXRPassthrough). Taken when the presentation is
|
|
// handed to the backend, which starts or pauses the view then; a backend
|
|
// without one ignores it.
|
|
bool passthrough = false;
|
|
|
|
OpenXRPanelLayer panel;
|
|
};
|
|
|
|
struct OpenXRBackendFrame {
|
|
OpenXRFrame xr_frame;
|
|
OpenXRPresentation presentation;
|
|
std::array<uint32_t, kOpenXREyeCount> render_width{};
|
|
std::array<uint32_t, kOpenXREyeCount> render_height{};
|
|
bool expects_gpu_submission = false;
|
|
};
|
|
|
|
} // namespace mkw::vr
|
|
|
|
#endif // defined(MKW_ENABLE_OPENXR)
|