Files
thejerf--baijiSteamVR/Source/Core/VideoCommon/GeometryShaderManager.cpp
T
2026-07-22 02:52:37 +02:00

656 lines
30 KiB
C++

// Copyright 2014 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/GeometryShaderManager.h"
#include <array>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <string>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Core/System.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/FreeLookCamera.h"
#include "VideoCommon/RenderState.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"
#ifdef ENABLE_VR
#include "VideoCommon/VR/OpenXRManager.h"
#endif
static constexpr int LINE_PT_TEX_OFFSETS[8] = {0, 16, 8, 4, 2, 1, 1, 1};
namespace
{
static constexpr float METROID_PERSPECTIVE_HUD_FORWARD_OFFSET = 1.0f;
static constexpr int METROID_HUD_REFERENCE_CONTEXT_COMBAT = 1;
static constexpr float METROID_COMBAT_HUD_REFERENCE_FAR_Z = -28.5f;
static constexpr float METROID_COMBAT_HUD_REFERENCE_NEAR_Z = -18.0f;
struct PerspectiveHudTransform
{
std::array<float, 3> scale{};
std::array<float, 3> position{};
float zobj = 0.0f;
float distance = 0.0f;
float origin_z = 0.0f;
float ref_minus_one_z = 0.0f;
float ref_plus_one_z = 0.0f;
float hydra_like_position_z = 0.0f;
float hydra_like_origin_z = 0.0f;
float znear = 0.0f;
float zfar = 0.0f;
float hydra_default_zobj = 0.0f;
bool valid = false;
};
void ApplyRowTransform(std::array<std::array<float, 4>, 4>* rows, const Common::Matrix44& matrix)
{
for (auto& row : *rows)
{
const std::array<float, 4> src = row;
row[0] = src[0] * matrix.data[0] + src[1] * matrix.data[4] + src[2] * matrix.data[8] +
src[3] * matrix.data[12];
row[1] = src[0] * matrix.data[1] + src[1] * matrix.data[5] + src[2] * matrix.data[9] +
src[3] * matrix.data[13];
row[2] = src[0] * matrix.data[2] + src[1] * matrix.data[6] + src[2] * matrix.data[10] +
src[3] * matrix.data[14];
row[3] = src[0] * matrix.data[3] + src[1] * matrix.data[7] + src[2] * matrix.data[11] +
src[3] * matrix.data[15];
}
}
void ApplyRowTransform(std::array<std::array<float, 4>, 2>* rows, const Common::Matrix44& matrix)
{
for (auto& row : *rows)
{
const std::array<float, 4> src = row;
row[0] = src[0] * matrix.data[0] + src[1] * matrix.data[4] + src[2] * matrix.data[8] +
src[3] * matrix.data[12];
row[1] = src[0] * matrix.data[1] + src[1] * matrix.data[5] + src[2] * matrix.data[9] +
src[3] * matrix.data[13];
row[2] = src[0] * matrix.data[2] + src[1] * matrix.data[6] + src[2] * matrix.data[10] +
src[3] * matrix.data[14];
row[3] = src[0] * matrix.data[3] + src[1] * matrix.data[7] + src[2] * matrix.data[11] +
src[3] * matrix.data[15];
}
}
bool IsFinite(float value)
{
return std::isfinite(value);
}
bool IsMetroidHudStableReferenceAllowed(int context, float reference_view_z)
{
if (context != METROID_HUD_REFERENCE_CONTEXT_COMBAT)
return true;
// Prime 1 combat HUD body anchors sit around -20 in normal combat and can move toward -27 for
// visor-specific HUD projections. Grappling-hook arm/attachment draws are classified as HUD
// anchors too, but sit around -32 and must not replace the shared combat HUD body depth.
return reference_view_z >= METROID_COMBAT_HUD_REFERENCE_FAR_Z &&
reference_view_z <= METROID_COMBAT_HUD_REFERENCE_NEAR_Z;
}
PerspectiveHudTransform CalculatePerspectiveHudTransform(const Projection::Raw& projection,
float units_per_meter,
float reference_view_z)
{
PerspectiveHudTransform result;
if (projection[0] == 0.0f || projection[2] == 0.0f || reference_view_z >= 0.0f)
return result; // need a valid frustum and a HUD model in front of the camera
// Reference depth = the HUD model's ACTUAL view-space distance (|reference_view_z|), not the
// frustum mid-point. Metroid's HUD perspective frustum is enormous (znear~1, zfar~4000+), so a
// frustum-interpolated reference put zobj in the thousands while the models actually sit only
// ~20 units away. That made scale = size_reference/zobj ~100x too small, shrinking the HUD to a
// dot. Using the real model depth makes the apparent size depend only on size_reference/distance
// (i.e. the Size/Distance sliders), independent of the model's view-space depth.
const float zobj = -reference_view_z;
if (!IsFinite(zobj) || zobj < 1.0e-4f)
return result;
result.zobj = zobj;
if (projection[4] != 0.0f && projection[4] != 1.0f)
{
result.zfar = projection[5] / projection[4];
result.znear = result.zfar * projection[4] / (projection[4] - 1.0f);
result.hydra_default_zobj = result.znear + (result.zfar - result.znear) * 0.5f;
}
const float left = (-(projection[1] + 1.0f) / projection[0]) * zobj;
const float right = left + (2.0f / projection[0]) * zobj;
const float bottom = (-(projection[3] + 1.0f) / projection[2]) * zobj;
const float top = bottom + (2.0f / projection[2]) * zobj;
const float width = right - left;
const float height = top - bottom;
if (width == 0.0f || height == 0.0f || !IsFinite(width) || !IsFinite(height))
return result;
result.distance = units_per_meter * g_ActiveConfig.vr_screen_distance;
const float size_reference = units_per_meter * g_ActiveConfig.vr_screen_size;
const float hud_width = std::abs((2.0f / projection[0]) * size_reference);
const float hud_height = std::abs((2.0f / projection[2]) * size_reference);
result.scale[0] = hud_width / width;
result.scale[1] = hud_height / height;
result.scale[2] = result.scale[0];
result.position[0] = result.scale[0] * (-(right + left) * 0.5f);
result.position[1] = result.scale[1] * (-(top + bottom) * 0.5f);
// Centre the HUD model near the screen-distance plane, independent of Size, while biasing the
// perspective HUD models slightly toward the viewer. This preserves the original depth direction
// but keeps the Prime beam/visor boxes in front of the flat HUD reference, matching Hydra.
// reference_view_z is the model origin's view-space z (the position-matrix translation), so the
// un-biased model sits exactly `distance` in front regardless of scale; the per-vertex
// scale[2] * (viewPos.z - reference_view_z) deviation still supplies the relative model depth.
// This decouples the sliders (Distance = forward placement, Size = scale). The earlier
// `scale[2] * zobj - distance` coupled them and assumed the model sat at view-z == -zobj;
// a plain -distance assumed view-z == 0. Neither held, so the HUD floated too far away.
result.position[2] = result.scale[2] * (zobj + METROID_PERSPECTIVE_HUD_FORWARD_OFFSET) -
result.distance;
result.origin_z = result.scale[2] * reference_view_z + result.position[2];
result.ref_minus_one_z = result.scale[2] * (reference_view_z - 1.0f) + result.position[2];
result.ref_plus_one_z = result.scale[2] * (reference_view_z + 1.0f) + result.position[2];
result.hydra_like_position_z = result.scale[2] * zobj - result.distance;
result.hydra_like_origin_z = result.scale[2] * reference_view_z + result.hydra_like_position_z;
result.valid = IsFinite(result.scale[0]) && IsFinite(result.scale[1]) &&
IsFinite(result.scale[2]) && IsFinite(result.position[0]) &&
IsFinite(result.position[1]) && IsFinite(result.position[2]) &&
IsFinite(result.distance) && IsFinite(result.origin_z) &&
IsFinite(result.ref_minus_one_z) && IsFinite(result.ref_plus_one_z);
return result;
}
} // namespace
void GeometryShaderManager::Init()
{
constants = {};
m_vr_hud_shared_reference_valid = false;
m_vr_hud_shared_reference_context = 0;
m_vr_hud_stable_reference_valid = false;
m_vr_hud_stable_reference_context = 0;
m_vr_hud_frame_anchor_candidate_valid = false;
m_vr_hud_frame_anchor_candidate_context = 0;
// Init any initial constants which aren't zero when bpmem is zero.
SetViewportChanged();
SetProjectionChanged();
dirty = true;
}
void GeometryShaderManager::Dirty()
{
// This function is called after a savestate is loaded.
// Any constants that can changed based on settings should be re-calculated
m_projection_changed = true;
// Uses EFB scale config
SetLinePtWidthChanged();
dirty = true;
}
void GeometryShaderManager::SetVSExpand(VSExpand expand)
{
if (constants.vs_expand != expand)
{
constants.vs_expand = expand;
dirty = true;
}
}
void GeometryShaderManager::SetConstants(PrimitiveType prim)
{
if (g_ActiveConfig.stereo_mode != StereoMode::Off)
{
#ifdef ENABLE_VR
const bool openxr_mode = (g_ActiveConfig.stereo_mode == StereoMode::OpenXR);
#else
const bool openxr_mode = false;
#endif
if (m_projection_changed || openxr_mode)
{
m_projection_changed = false;
#ifdef ENABLE_VR
if (openxr_mode)
{
auto& system = Core::System::GetInstance();
auto& vertex_shader_manager = system.GetVertexShaderManager();
const bool perspective = xfmem.projection.type == ProjectionType::Perspective;
constants.stereoparams = {0.0f, 0.0f, 0.0f, 0.0f};
constants.eye_projection = {};
constants.eye_z_row = {};
constants.depth_params = {};
constants.vr_screen = {};
constants.head_projection = {};
constants.head_locked_params = {};
constants.pixel_center_correction = {};
const float upm_override = vr_units_per_meter_override;
vr_units_per_meter_override = -1.0f; // consume
const float headlocked_projection_scale_x = vr_headlocked_projection_scale_x;
const float headlocked_projection_scale_y = vr_headlocked_projection_scale_y;
const float headlocked_projection_offset_x = vr_headlocked_projection_offset_x;
const float headlocked_projection_offset_y = vr_headlocked_projection_offset_y;
const bool metroid_hud_self_center = vr_metroid_hud_self_center;
vr_metroid_hud_self_center = false; // consume
const bool metroid_hud_anchor_candidate = vr_metroid_hud_anchor_candidate;
vr_metroid_hud_anchor_candidate = false; // consume
const int metroid_hud_reference_context = vr_metroid_hud_reference_context;
vr_metroid_hud_reference_context = 0; // consume
vr_headlocked_projection_scale_x = 1.0f;
vr_headlocked_projection_scale_y = 1.0f;
vr_headlocked_projection_offset_x = 0.0f;
vr_headlocked_projection_offset_y = 0.0f;
// Hoisted out of the session block so it is also available when finalizing the
// ortho/head-locked depth_params (HUD thickness) below.
// Base scale: an engaged Camera Anchor may impose its own world scale (a first-person
// anchor often wants a different one than the game's global value); per-draw
// UnitsPerMeter overrides still win over both.
const float base_upm = VR::g_openxr ? VR::g_openxr->GetEffectiveUnitsPerMeter() :
g_ActiveConfig.vr_units_per_meter;
const float upm = std::max(upm_override > 0.0f ? upm_override : base_upm, 0.0001f);
if (VR::g_openxr && VR::g_openxr->IsSessionRunning())
{
// Under the head-pose lock, only re-fetch the head pose from OpenXR when we've
// been explicitly invalidated (at the XFB-copy frame boundary). This prevents
// mid-frame LocateViews() updates from desynchronising different draw calls
// within the same game frame. The lock applies whenever ImmediateXFB is off
// (VRLockHeadPosePerFrame): presents then interleave with the next frame's draw
// stream, so per-draw refresh WILL land mid-frame. With ImmediateXFB on,
// presentation is frame-aligned, so refetch every call for fresher tracking.
const bool upm_changed = std::abs(upm - m_cached_units_per_meter) > 0.0001f;
const bool need_refresh =
upm_changed || !g_ActiveConfig.VRLockHeadPosePerFrame() || m_vr_pose_needs_refresh;
if (need_refresh)
{
std::array<std::array<float, 4>, 4> eye_projection_rows{};
std::array<std::array<float, 4>, 2> eye_z_rows{};
VR::g_openxr->GetEyeProjectionRows(upm, eye_projection_rows, eye_z_rows);
// OpenXR stereo path bypasses the classic cproj path, so apply freelook here too.
if (perspective && g_freelook_camera.IsActive())
{
const Common::Matrix44 freelook_view = g_freelook_camera.GetView();
ApplyRowTransform(&eye_projection_rows, freelook_view);
ApplyRowTransform(&eye_z_rows, freelook_view);
}
m_cached_eye_projection = eye_projection_rows;
m_cached_eye_z_row = eye_z_rows;
m_cached_units_per_meter = upm;
// Unrotated per-eye projection rows for head-locked content.
std::array<std::array<float, 4>, 4> head_proj_rows{};
VR::g_openxr->GetRawEyeProjectionRows(upm, head_proj_rows);
m_cached_head_projection = head_proj_rows;
// Snapshot the pose the cache was built from. SubmitFrame will use
// this snapshot so render_pose == submit_pose regardless of any
// later LocateViews that may clobber m_eye_views before xrEndFrame.
VR::g_openxr->RecordRenderedEyeViews();
m_vr_pose_needs_refresh = false;
}
constants.eye_projection[0] = m_cached_eye_projection[0];
constants.eye_projection[1] = m_cached_eye_projection[1];
constants.eye_projection[2] = m_cached_eye_projection[2];
constants.eye_projection[3] = m_cached_eye_projection[3];
constants.eye_z_row[0] = m_cached_eye_z_row[0];
constants.eye_z_row[1] = m_cached_eye_z_row[1];
// Unrotated per-eye projection rows for head-locked content (cached above).
constants.head_projection[0] = m_cached_head_projection[0];
constants.head_projection[1] = m_cached_head_projection[1];
constants.head_projection[2] = m_cached_head_projection[2];
constants.head_projection[3] = m_cached_head_projection[3];
for (u32 eye = 0; eye < 2; ++eye)
{
auto& row0 = constants.head_projection[eye * 2 + 0];
auto& row1 = constants.head_projection[eye * 2 + 1];
row0[0] *= headlocked_projection_scale_x;
row0[3] *= headlocked_projection_scale_x;
row0[2] -= headlocked_projection_offset_x;
row1[1] *= headlocked_projection_scale_y;
row1[3] *= headlocked_projection_scale_y;
row1[2] -= headlocked_projection_offset_y;
}
constants.head_locked_params = {g_ActiveConfig.vr_head_locked_curvature, 0.0f, 0.0f,
0.0f};
constants.pixel_center_correction = vertex_shader_manager.constants.pixelcentercorrection;
// Virtual screen params (for ortho draws: menus, FMV, HUD).
const float dist = upm * g_ActiveConfig.vr_screen_distance;
const float half_h = upm * g_ActiveConfig.vr_screen_size * 0.5f;
const float half_w = half_h * (16.0f / 9.0f);
// .w is unused since the per-draw depth layering was removed (Exact Screen Depth
// reproduces the game's own depth instead of synthesizing layer offsets).
constants.vr_screen = {half_w, half_h, dist, 0.0f};
if (perspective)
{
// Detect if the game's projection flips the X axis (e.g. mirror mode in
// Mario Kart). The VR GS path replaces the game's projection entirely with
// the VR eye projection, which always has a positive X scale. If the game's
// projection has a negative X scale, triangle winding reverses and the game
// adjusts its cull mode accordingly — but our VR projection doesn't reproduce
// the flip, so the cull mode becomes wrong and back-faces are shown instead of
// front-faces. Pass the sign to the GS so it can negate the output X when
// needed, restoring correct winding AND the intended mirrored view.
const float proj_x_sign =
(xfmem.projection.rawProjection[0] < 0.0f) ? -1.0f : 1.0f;
constants.stereoparams[0] = proj_x_sign;
float depth_scale = 1.0f;
float depth_offset = 0.0f;
if (VertexShaderManager::UseVertexDepthRange())
{
if (g_backend_info.bSupportsReversedDepthRange)
{
depth_scale = std::fabs(xfmem.viewport.zRange) / 16777215.0f;
if (xfmem.viewport.zRange < 0.0f)
depth_offset = xfmem.viewport.farZ / 16777215.0f;
else
depth_offset = 1.0f - xfmem.viewport.farZ / 16777215.0f;
}
else
{
depth_scale = xfmem.viewport.zRange / 16777215.0f;
depth_offset = 1.0f - xfmem.viewport.farZ / 16777215.0f;
}
}
constants.depth_params = {xfmem.projection.rawProjection[4],
xfmem.projection.rawProjection[5],
depth_scale, depth_offset};
// Perspective flag consumed in the OpenXR GS path.
constants.stereoparams[3] = 1.0f;
// Skybox heuristic (Hydra "Detect Skybox"): an object drawn at the camera origin
// (translation 0,0,0) with a non-identity matrix is treated as a skybox. Render it
// with the eye position locked at 0,0,0 (rotation only, no IPD/positional offset) so
// it sits at infinity instead of appearing too close. cstereo.z is the per-draw
// world-position weight consumed in the GS perspective path: 1.0 = normal (apply eye
// position), 0.0 = skybox (rotation only). This must be set for EVERY perspective VR
// draw, otherwise normal geometry would lose its per-eye offset and stereo would break.
bool is_skybox = false;
if (g_ActiveConfig.vr_detect_skybox)
{
const auto& pnm = vertex_shader_manager.constants.posnormalmatrix;
if (pnm[0][3] == 0.0f && pnm[1][3] == 0.0f && pnm[2][3] == 0.0f &&
pnm[0][0] != 1.0f)
{
is_skybox = true;
}
}
constants.stereoparams[2] = is_skybox ? 0.0f : 1.0f;
}
else if (g_ActiveConfig.vr_virtual_screen)
{
// Orthographic VR flag — signals GS to use virtual screen path.
constants.stereoparams[3] = -1.0f;
}
}
// Per-draw VR stereo override (screen/fullscreen handling from shader overrides)
if (!std::isnan(vr_stereo_override))
{
constants.stereoparams[3] = vr_stereo_override;
vr_stereo_override = std::numeric_limits<float>::quiet_NaN();
}
const bool perspective_hud =
perspective && VR::g_openxr && VR::g_openxr->IsSessionRunning() &&
constants.stereoparams[3] < -2.5f;
if (perspective_hud)
{
// Model origin's view-space z (position-matrix translation).
const float reference_view_z = vertex_shader_manager.constants.posnormalmatrix[2][3];
// The radar/minimap (METROID_VISOR_RADAR_HINT / METROID_RADAR_DOT) is a flat overlay whose
// blip geometry spans a large depth. Under the SHARED scene transform that spread clips
// behind the camera and the whole minimap vanishes at low HUD distances. Self-centre it on
// its own origin depth (its own, smaller scale — the pre-shared-reference behaviour) so it
// stays at `distance` and visible. Coherent 3D content (Samus + hook, beam boxes) keeps the
// shared reference so its relative depth survives.
// (The radar/minimap draws are NOT classified as a RADAR layer in practice — combat-visor
// logs show them under METROID_SCAN_TEXT — so the depth-outlier check below is what
// actually catches them. The flag is kept as a cheap explicit opt-out.)
const bool self_center_layer = metroid_hud_self_center;
const bool valid_reference =
std::isfinite(reference_view_z) && reference_view_z < 0.0f;
if (metroid_hud_anchor_candidate && !self_center_layer && valid_reference)
{
// Pick the farthest stable HUD-body origin seen this frame as the anchor for the next
// frame. Free-aim adds/moves nearer reticle pieces, but those are not anchor candidates
// and cannot pull the whole 3D HUD plane toward the camera.
const bool stable_reference_allowed = IsMetroidHudStableReferenceAllowed(
metroid_hud_reference_context, reference_view_z);
const bool replace_candidate =
stable_reference_allowed &&
(!m_vr_hud_frame_anchor_candidate_valid ||
metroid_hud_reference_context != m_vr_hud_frame_anchor_candidate_context ||
reference_view_z < m_vr_hud_frame_anchor_candidate_z);
if (replace_candidate)
{
m_vr_hud_frame_anchor_candidate_z = reference_view_z;
m_vr_hud_frame_anchor_candidate_valid = true;
m_vr_hud_frame_anchor_candidate_context = metroid_hud_reference_context;
}
}
// Reuse ONE shared reference for all coherent -3 draws in this frame, so every Prime
// perspective-HUD model shares a single headlocked transform and keeps its relative scene
// depth (the grappling hook behind Samus's arm, stacked beam boxes). The reference comes
// from the previous frame's stable HUD body anchor when available. First-frame fallback uses
// the first coherent draw only until a stable anchor is promoted at the frame boundary.
const bool stable_reference_matches =
m_vr_hud_stable_reference_valid &&
m_vr_hud_stable_reference_context == metroid_hud_reference_context;
if ((!m_vr_hud_shared_reference_valid ||
m_vr_hud_shared_reference_context != metroid_hud_reference_context) &&
!self_center_layer)
{
if (stable_reference_matches)
{
m_vr_hud_shared_reference_z = m_vr_hud_stable_reference_z;
m_vr_hud_shared_reference_valid = true;
m_vr_hud_shared_reference_context = metroid_hud_reference_context;
}
else if (valid_reference)
{
m_vr_hud_shared_reference_z = reference_view_z;
m_vr_hud_shared_reference_valid = true;
m_vr_hud_shared_reference_context = metroid_hud_reference_context;
}
}
const float shared_reference_z =
m_vr_hud_shared_reference_valid ? m_vr_hud_shared_reference_z : reference_view_z;
// Depth-outlier draws self-centre on their own origin (their own, smaller/larger scale —
// the pre-shared-reference behaviour). Under the SHARED transform, a draw sitting much
// closer to the camera than the reference (the minimap surface/blips at view-z ~-1..-5 vs
// the ~-20 HUD reference) slides behind the near plane whenever Distance < Size
// (pos.z = sizeRef - dist) and progressively vanishes. Coherent draws (Samus + hook,
// beam boxes — all within ~0.7-1.2x of the reference) keep the shared scene so their
// relative depth survives.
bool depth_outlier = false;
if (std::isfinite(reference_view_z) && reference_view_z < 0.0f &&
shared_reference_z < 0.0f)
{
const float depth_ratio = reference_view_z / shared_reference_z;
depth_outlier = depth_ratio < 0.5f || depth_ratio > 2.0f;
}
const float ref_for_transform =
(self_center_layer || depth_outlier) ? reference_view_z : shared_reference_z;
const PerspectiveHudTransform transform = CalculatePerspectiveHudTransform(
xfmem.projection.rawProjection, upm, ref_for_transform);
if (transform.valid)
{
constants.vr_screen[0] = transform.position[0];
constants.vr_screen[1] = transform.position[1];
constants.vr_screen[2] = transform.position[2];
constants.head_locked_params[1] = transform.scale[0];
constants.head_locked_params[2] = transform.scale[1];
constants.head_locked_params[3] = transform.scale[2];
constants.depth_params[3] = transform.distance;
}
else
{
constants.stereoparams[3] = -2.0f;
}
}
// For ortho/screen draws, pass depth params via depth_params
// (depth_params is otherwise unused for non-perspective draws).
// .x = unused, .y = element depth (within draw call),
// .z = HUD thickness in game units (world-space depth spread across the layer's ortho-Z)
if (constants.stereoparams[3] < -0.5f)
{
// Per-override element depth if set, otherwise the baked-in default. Element Depth is
// no longer user-configurable (Exact Screen Depth superseded it); this fixed value is
// the old slider default, kept only so the legacy fallback path still spreads elements.
constexpr float kDefaultElementDepth = 0.001f;
constants.depth_params[1] = (vr_element_depth_override >= 0.0f)
? vr_element_depth_override
: kDefaultElementDepth;
vr_element_depth_override = -1.0f; // consume
// Gives a 2D layer (HUD/menu) real 3D depth: ortho-Z elements spread across this
// many game units of world-space thickness (Hydra "HudThickness"). 0 = flat.
constants.depth_params[2] = upm * g_ActiveConfig.vr_hud_thickness;
if (perspective_hud)
constants.depth_params[3] = constants.depth_params[3] > 0.0f ?
constants.depth_params[3] :
upm * g_ActiveConfig.vr_screen_distance;
}
dirty = true;
}
else
#endif
if (xfmem.projection.type == ProjectionType::Perspective)
{
const float offset = g_ActiveConfig.stereo_depth;
constants.stereoparams[0] = g_ActiveConfig.bStereoSwapEyes ? offset : -offset;
constants.stereoparams[1] = g_ActiveConfig.bStereoSwapEyes ? -offset : offset;
constants.stereoparams[2] = g_ActiveConfig.stereo_convergence;
constants.stereoparams[3] = 0.0f;
dirty = true;
}
else
{
constants.stereoparams[0] = 0.0f;
constants.stereoparams[1] = 0.0f;
constants.stereoparams[2] = 0.0f;
constants.stereoparams[3] = 0.0f;
dirty = true;
}
}
}
if (g_ActiveConfig.UseVSForLinePointExpand())
{
if (prim == PrimitiveType::Points)
SetVSExpand(VSExpand::Point);
else if (prim == PrimitiveType::Lines)
SetVSExpand(VSExpand::Line);
else
SetVSExpand(VSExpand::None);
}
if (m_viewport_changed)
{
m_viewport_changed = false;
constants.lineptparams[0] = 2.0f * xfmem.viewport.wd;
constants.lineptparams[1] = -2.0f * xfmem.viewport.ht;
dirty = true;
}
}
void GeometryShaderManager::SetViewportChanged()
{
m_viewport_changed = true;
}
void GeometryShaderManager::SetProjectionChanged()
{
m_projection_changed = true;
}
void GeometryShaderManager::InvalidateVRHeadPose()
{
m_vr_pose_needs_refresh = true;
if (m_vr_hud_frame_anchor_candidate_valid)
{
const bool stable_reference_allowed = IsMetroidHudStableReferenceAllowed(
m_vr_hud_frame_anchor_candidate_context, m_vr_hud_frame_anchor_candidate_z);
const bool accept_candidate =
stable_reference_allowed &&
(!m_vr_hud_stable_reference_valid ||
m_vr_hud_frame_anchor_candidate_context != m_vr_hud_stable_reference_context ||
m_vr_hud_frame_anchor_candidate_z < m_vr_hud_stable_reference_z);
// Within one HUD context, keep the farthest allowed stable body anchor so free-aim cannot pull
// the whole 3D HUD toward the camera. Across contexts, such as combat HUD -> pause/map UI,
// replace the stable reference so menu models do not inherit the combat HUD depth.
if (accept_candidate)
{
m_vr_hud_stable_reference_z = m_vr_hud_frame_anchor_candidate_z;
m_vr_hud_stable_reference_valid = true;
m_vr_hud_stable_reference_context = m_vr_hud_frame_anchor_candidate_context;
}
m_vr_hud_frame_anchor_candidate_valid = false;
}
// Reuse the promoted stable perspective-HUD reference on the next frame.
m_vr_hud_shared_reference_valid = false;
}
void GeometryShaderManager::SetLinePtWidthChanged()
{
constants.lineptparams[2] = bpmem.lineptwidth.linesize / 6.f;
constants.lineptparams[3] = bpmem.lineptwidth.pointsize / 6.f;
constants.texoffset[2] = LINE_PT_TEX_OFFSETS[bpmem.lineptwidth.lineoff];
constants.texoffset[3] = LINE_PT_TEX_OFFSETS[bpmem.lineptwidth.pointoff];
dirty = true;
}
void GeometryShaderManager::SetTexCoordChanged(u8 texmapid)
{
TCoordInfo& tc = bpmem.texcoords[texmapid];
int bitmask = 1 << texmapid;
constants.texoffset[0] &= ~bitmask;
constants.texoffset[0] |= tc.s.line_offset << texmapid;
constants.texoffset[1] &= ~bitmask;
constants.texoffset[1] |= tc.s.point_offset << texmapid;
dirty = true;
}
void GeometryShaderManager::DoState(PointerWrap& p)
{
p.Do(m_projection_changed);
p.Do(m_viewport_changed);
p.Do(constants);
if (p.IsReadMode())
{
// Fixup the current state from global GPU state
// NOTE: This requires that all GPU memory has been loaded already.
Dirty();
}
}