EG/core/vr_manager.py
2025-09-29 10:52:39 +08:00

4199 lines
175 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
VR管理器模块
负责VR功能的初始化、渲染和交互
- OpenVR/SteamVR集成
- VR头显跟踪和渲染
- VR控制器交互
- VR模式切换
"""
import sys
import gc
import numpy as np
from panda3d.core import (
WindowProperties, GraphicsPipe, FrameBufferProperties,
GraphicsOutput, Texture, Camera, PerspectiveLens, MatrixLens,
Mat4, Vec3, TransformState, RenderState, CardMaker,
BitMask32, PandaNode, NodePath, LMatrix4, LVector3, LVector4,
CS_yup_right, CS_default, PythonCallbackObject
)
from direct.task import Task
from direct.showbase.DirectObject import DirectObject
try:
import openvr
OPENVR_AVAILABLE = True
except ImportError:
OPENVR_AVAILABLE = False
print("警告: OpenVR未安装VR功能将不可用")
# 导入手柄控制器、动作系统、交互系统、摇杆系统和传送系统
from .vr_controller import LeftController, RightController
from .vr_actions import VRActionManager
from .vr_interaction import VRInteractionManager
from .vr_joystick import VRJoystickManager
from .vr_teleport import VRTeleportSystem
class VRManager(DirectObject):
"""VR管理器类 - 处理所有VR相关功能"""
def __init__(self, world):
"""初始化VR管理器
Args:
world: 主世界对象引用
"""
super().__init__()
self.world = world
self.vr_system = None
self.vr_enabled = False
self.vr_initialized = False
# VR渲染相关
self.vr_left_eye_buffer = None
self.vr_right_eye_buffer = None
self.vr_left_camera = None
self.vr_right_camera = None
self.vr_compositor = None
# VR纹理和ID缓存 - 修复重复准备问题
self.vr_left_texture = None
self.vr_right_texture = None
self.left_texture_id = None # 缓存左眼纹理的OpenGL ID
self.right_texture_id = None # 缓存右眼纹理的OpenGL ID
self.textures_prepared = False # 标记纹理是否已准备
# VR跟踪数据
self.hmd_pose = Mat4.identMat()
self.controller_poses = {}
self.tracked_device_poses = []
self.poses = None # OpenVR渲染姿态数组
self.game_poses = None # OpenVR游戏逻辑姿态数组
# 🚀 对象池和缓存系统 - 修复16-19帧周期性GPU峰值
self._matrix_pool = [] # Mat4对象池
self._matrix_pool_size = 8 # 池大小,足够处理多个控制器
self._cached_matrices = {} # 设备ID到矩阵的缓存
self._controller_poses_cache = {} # 控制器姿态缓存避免每帧clear()
# 🚀 OpenVR Texture对象缓存 - 避免每帧创建openvr.Texture_t()
self._left_ovr_texture = None # 左眼纹理对象缓存
self._right_ovr_texture = None # 右眼纹理对象缓存
# Python垃圾回收控制
self._gc_control_enabled = True # 是否启用GC控制
self._gc_disabled = False # GC是否被禁用
self._manual_gc_interval = 900 # 每900帧手动触发一次GC (15秒@60fps) - 减少GC频率
self._last_manual_gc_frame = 0
# 🚀 立即初始化对象池和GC控制在其他组件之前
self._initialize_object_pools()
# VR渲染参数
self.eye_width = 1080
self.eye_height = 1200
self.near_clip = 0.1
self.far_clip = 1000.0
# VR分辨率缩放优化
self.resolution_scale = 0.75 # 默认0.75倍分辨率,性能和质量平衡
self.base_eye_width = 1080 # 原始推荐分辨率
self.base_eye_height = 1200
self.scaled_eye_width = 1080 # 实际使用的缩放后分辨率
self.scaled_eye_height = 1200
# VR质量预设
self.quality_presets = {
'performance': 0.6, # 性能模式 - 约60%分辨率
'balanced': 0.75, # 平衡模式 - 约75%分辨率
'quality': 1.0 # 质量模式 - 100%分辨率
}
self.current_quality_preset = 'balanced' # 默认平衡模式
# VR任务
self.vr_task = None
# VR锚点层级系统
self.tracking_space = None
self.hmd_anchor = None
self.left_eye_anchor = None
self.right_eye_anchor = None
# 坐标系转换矩阵 - 使用Panda3D内置方法
self.coord_mat = LMatrix4.convert_mat(CS_yup_right, CS_default)
self.coord_mat_inv = LMatrix4.convert_mat(CS_default, CS_yup_right)
# 性能监控
self.frame_count = 0
self.last_fps_check = 0
self.last_fps_time = 0
self.vr_fps = 0
self.submit_failures = 0
self.pose_failures = 0
# OpenVR 帧ID跟踪防止重复提交
self.openvr_frame_id = 0
self.left_eye_last_render_frame = -1
self.right_eye_last_render_frame = -1
# 高级性能监控(默认关闭,手动开启)
self.performance_monitoring = False # 是否启用性能监控
self.debug_output_enabled = False # 是否启用调试输出
self.debug_mode = 'detailed' # 'brief' 或 'detailed'
self.cpu_usage = 0.0
self.memory_usage = 0.0
self.gpu_usage = 0.0
self.gpu_memory_usage = 0.0
self.frame_times = [] # 存储最近帧时间
self.max_frame_time_history = 60 # 保存60帧的历史
self.last_performance_check = 0
self.performance_check_interval = 0.5 # 每0.5秒更新一次性能数据
# 渲染管线详细监控
self.enable_pipeline_monitoring = True # 是否启用管线监控
self.performance_mode_enabled = False # 性能优化模式(禁用监控以减少对象创建)
self.performance_mode_trigger_frame = 600 # 第600帧后启用性能模式
self.wait_poses_time = 0.0 # waitGetPoses耗时
self.left_render_time = 0.0 # 左眼渲染耗时
self.right_render_time = 0.0 # 右眼渲染耗时
self.submit_time = 0.0 # 纹理提交耗时
self.left_render_count = 0 # 左眼渲染次数计数
self.right_render_count = 0 # 右眼渲染次数计数
self.total_frame_time = 0.0 # 总帧时间
self.vr_sync_wait_time = 0.0 # VR同步等待时间
# 时间监控历史保存最近30帧
self.wait_poses_times = []
self.render_times = []
self.submit_times = []
self.sync_wait_times = []
self.pipeline_history_size = 30
# GPU渲染时间监控OpenVR Frame Timing
self.enable_gpu_timing = False # 是否启用GPU时间监控默认关闭
self.gpu_scene_render_ms = 0.0 # GPU场景渲染时间
self.gpu_pre_submit_ms = 0.0 # 提交前GPU时间
self.gpu_post_submit_ms = 0.0 # 提交后GPU时间
self.gpu_total_render_ms = 0.0 # GPU总渲染时间
self.gpu_compositor_render_ms = 0.0 # GPU合成器渲染时间
self.gpu_client_frame_interval_ms = 0.0 # 客户端帧间隔
self.gpu_timing_history = [] # GPU时间历史记录
self.gpu_timing_history_size = 30 # GPU时间历史记录大小
self.gpu_timing_failure_count = 0 # GPU时间获取失败次数
# VR系统信息
self.current_eye_resolution = (self.eye_width, self.eye_height)
self.recommended_eye_resolution = (0, 0)
self.vr_display_frequency = 0.0
self.vr_vsync_enabled = True
self.vsync_to_photons_ms = 0.0 # VSync到光子的延迟
self.target_frame_time_ms = 0.0 # 目标帧时间
self.vsync_window_ms = 0.0 # VSync时间窗口
self.async_reprojection_enabled = False # 异步重投影状态
self.motion_smoothing_enabled = False # 运动平滑状态
# 🧪 VR测试模式
self.vr_test_mode = False # 是否启用VR测试模式
self.test_display_mode = 'stereo' # 'left', 'right', 'stereo'
self.test_display_quad = None # 测试显示的四边形
self.test_right_quad = None # 右眼显示的四边形(立体模式)
self.stereo_display_created = False # 立体显示是否已创建
self.test_performance_hud = None # 性能HUD
self.test_performance_text = None # 性能文本节点
self.test_mode_initialized = False # 测试模式是否已初始化
self.hud_update_counter = 0 # HUD更新计数器
self.hud_update_interval = 30 # HUD更新间隔帧数30帧约0.5秒@60fps
# 🔧 VR测试模式调试选项 - 逐步启用普通VR功能
self.test_mode_submit_texture = False # 是否在测试模式提交纹理到OpenVR
self.test_mode_wait_poses = False # 是否在测试模式调用waitGetPoses
# waitGetPoses调用策略配置 - 使用高性能的update_task策略
self.use_prediction_time = 0.011 # 11ms的预测时间 - OpenVR标准值平衡准确性和延迟
self.poses_updated_in_task = True # 始终在更新任务中获取姿态Running Start模式
# 尝试导入性能监控库
self._init_performance_monitoring()
# VR提交策略 - 基于参考实现
self.submit_together = True # 是否在right_cb中同时提交左右眼
# Running Start标记 - Valve最佳实践
self._waitgetposes_called_this_frame = False
# 姿态缓存 - 修复时序不匹配
self._cached_render_poses = None # 用于渲染的缓存姿态
self._first_frame = True # 首帧标记
# ATW控制选项 - 备选方案
self.disable_async_reprojection = False # 是否禁用异步重投影
# VR手柄控制器
self.left_controller = None
self.right_controller = None
self.controllers = {} # 设备索引到控制器的映射
self.tracked_device_anchors = {} # 跟踪设备锚点
# VR动作系统 - 可选择禁用用于API兼容性问题
self.disable_action_system = True # 禁用动作系统,使用直接控制器输入
if not self.disable_action_system:
try:
self.action_manager = VRActionManager(self)
print("✓ VR动作管理器初始化完成")
except Exception as e:
print(f"⚠️ VR动作管理器初始化失败: {e}")
self.action_manager = None
else:
self.action_manager = None
print("🚫 VR动作系统已禁用使用直接控制器输入")
# VR交互系统 - 添加异常保护
try:
self.interaction_manager = VRInteractionManager(self)
print("✓ VR交互管理器初始化完成")
except Exception as e:
print(f"⚠️ VR交互管理器初始化失败: {e}")
self.interaction_manager = None
# VR传送系统 - 添加异常保护
try:
self.teleport_system = VRTeleportSystem(self)
print("✓ VR传送系统初始化完成")
except Exception as e:
print(f"⚠️ VR传送系统初始化失败: {e}")
self.teleport_system = None
# VR摇杆系统 - 添加异常保护
try:
self.joystick_manager = VRJoystickManager(self)
print("✓ VR摇杆管理器初始化完成")
except Exception as e:
print(f"⚠️ VR摇杆管理器初始化失败: {e}")
self.joystick_manager = None
print("✓ VR管理器初始化完成")
def _initialize_object_pools(self):
"""初始化对象池 - 修复16-19帧周期性GPU峰值"""
try:
# 预填充Mat4对象池
for _ in range(self._matrix_pool_size):
self._matrix_pool.append(Mat4())
print(f"✅ Mat4对象池初始化完成 - 池大小: {self._matrix_pool_size}")
# 🚀 预创建OpenVR Texture对象 - 消除每帧创建openvr.Texture_t()的开销
try:
import openvr
self._left_ovr_texture = openvr.Texture_t()
self._right_ovr_texture = openvr.Texture_t()
# 设置固定属性(这些不变)
self._left_ovr_texture.eType = openvr.TextureType_OpenGL
self._left_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
self._right_ovr_texture.eType = openvr.TextureType_OpenGL
self._right_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
print("✅ OpenVR Texture对象缓存已创建 - 避免每帧创建新对象")
except Exception as texture_error:
print(f"⚠️ OpenVR Texture对象创建失败: {texture_error}")
# 不影响整体初始化,但性能可能不是最优
# 启用GC控制
if self._gc_control_enabled:
# 禁用自动垃圾回收,改为手动控制
gc.disable()
self._gc_disabled = True
print("✅ Python垃圾回收已禁用改为手动控制")
except Exception as e:
print(f"⚠️ 对象池初始化失败: {e}")
def _get_pooled_matrix(self):
"""从对象池获取Mat4对象"""
if self._matrix_pool:
return self._matrix_pool.pop()
else:
# 池为空时创建新对象(不应该发生,但作为备用)
return Mat4()
def _return_pooled_matrix(self, matrix):
"""将Mat4对象返回对象池"""
if len(self._matrix_pool) < self._matrix_pool_size:
# 重置矩阵到单位矩阵
matrix.identMat()
self._matrix_pool.append(matrix)
def _manual_gc_control(self):
"""手动垃圾回收控制 - 避免VR渲染期间的GC峰值"""
if not self._gc_control_enabled or not self._gc_disabled:
return
# 🚀 智能GC间隔性能模式下减少GC频率
current_interval = self._manual_gc_interval
if self.performance_mode_enabled:
current_interval = self._manual_gc_interval * 2 # 性能模式下间隔翻倍
# 每N帧手动触发一次垃圾回收
if self.frame_count - self._last_manual_gc_frame >= current_interval:
# 在非渲染关键时刻触发GC
collected = gc.collect()
self._last_manual_gc_frame = self.frame_count
# 仅在收集到对象时输出信息
if collected > 0:
print(f"🗑️ 手动GC: 清理了 {collected} 个对象 (帧#{self.frame_count})")
def _update_matrix_from_openvr(self, panda_mat, ovr_matrix):
"""直接更新现有Mat4对象的数值避免创建新对象"""
# 复用_convert_openvr_matrix_to_panda中的转换逻辑
# X轴行Panda3D的X轴对应OpenVR的X轴
panda_mat.setCell(0, 0, ovr_matrix[0][0]) # X_x → X_x
panda_mat.setCell(0, 1, ovr_matrix[0][1]) # X_y → X_y
panda_mat.setCell(0, 2, ovr_matrix[0][2]) # X_z → X_z
panda_mat.setCell(0, 3, ovr_matrix[0][3]) # 位移X分量
# Y轴行Panda3D的Y轴对应OpenVR的-Z轴
panda_mat.setCell(1, 0, -ovr_matrix[2][0]) # -Z_x → Y_x
panda_mat.setCell(1, 1, -ovr_matrix[2][1]) # -Z_y → Y_y
panda_mat.setCell(1, 2, -ovr_matrix[2][2]) # -Z_z → Y_z
panda_mat.setCell(1, 3, -ovr_matrix[2][3]) # 位移Y分量-Z位移
# Z轴行Panda3D的Z轴对应OpenVR的Y轴
panda_mat.setCell(2, 0, ovr_matrix[1][0]) # Y_x → Z_x
panda_mat.setCell(2, 1, ovr_matrix[1][1]) # Y_y → Z_y
panda_mat.setCell(2, 2, ovr_matrix[1][2]) # Y_z → Z_z
panda_mat.setCell(2, 3, ovr_matrix[1][3]) # 位移Z分量Y位移
# 齐次坐标
panda_mat.setCell(3, 0, 0)
panda_mat.setCell(3, 1, 0)
panda_mat.setCell(3, 2, 0)
panda_mat.setCell(3, 3, 1)
def convert_mat(self, mat):
"""
将OpenVR矩阵转换为Panda3D矩阵 - 基于参考实现
"""
if len(mat.m) == 4:
result = LMatrix4(
mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0],
mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1],
mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2],
mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3])
elif len(mat.m) == 3:
result = LMatrix4(
mat.m[0][0], mat.m[1][0], mat.m[2][0], 0.0,
mat.m[0][1], mat.m[1][1], mat.m[2][1], 0.0,
mat.m[0][2], mat.m[1][2], mat.m[2][2], 0.0,
mat.m[0][3], mat.m[1][3], mat.m[2][3], 1.0)
return result
def is_vr_available(self):
"""检查VR系统是否可用"""
if not OPENVR_AVAILABLE:
return False
try:
# 检查SteamVR是否运行
return openvr.isRuntimeInstalled() and openvr.isHmdPresent()
except Exception as e:
print(f"VR检查失败: {e}")
return False
def initialize_vr(self):
"""初始化VR系统"""
if not OPENVR_AVAILABLE:
print("❌ OpenVR不可用无法初始化VR")
return False
if self.vr_initialized:
print("VR系统已经初始化")
return True
try:
print("🔄 正在初始化VR系统...")
# 🚀 确保对象池已正确初始化(备用检查)
if not hasattr(self, '_matrix_pool') or len(self._matrix_pool) == 0:
print("⚠️ 对象池未初始化,正在重新初始化...")
self._initialize_object_pools()
# 初始化OpenVR - 使用Scene应用类型确保正确的焦点管理
self.vr_system = openvr.init(openvr.VRApplication_Scene)
if not self.vr_system:
print("❌ 无法初始化OpenVR系统")
return False
# 获取compositor
self.vr_compositor = openvr.VRCompositor()
if not self.vr_compositor:
print("❌ 无法获取VR Compositor")
return False
# 获取推荐的渲染目标尺寸
base_width, base_height = self.vr_system.getRecommendedRenderTargetSize()
self.base_eye_width = base_width
self.base_eye_height = base_height
# 应用分辨率缩放
self.scaled_eye_width = int(base_width * self.resolution_scale)
self.scaled_eye_height = int(base_height * self.resolution_scale)
# 使用缩放后的分辨率作为实际渲染分辨率
self.eye_width = self.scaled_eye_width
self.eye_height = self.scaled_eye_height
self.current_eye_resolution = (self.eye_width, self.eye_height)
self.recommended_eye_resolution = (base_width, base_height)
print(f"✓ VR基础分辨率: {base_width}x{base_height}")
print(f"✓ VR缩放系数: {self.resolution_scale}")
print(f"✓ VR实际分辨率: {self.eye_width}x{self.eye_height}")
print(f"📊 分辨率优化: {(1 - self.resolution_scale**2) * 100:.1f}% 像素减少")
# 获取VR系统信息
try:
# 获取显示频率
self.vr_display_frequency = self.vr_system.getFloatTrackedDeviceProperty(
openvr.k_unTrackedDeviceIndex_Hmd,
openvr.Prop_DisplayFrequency_Float
)
print(f"✓ VR显示频率: {self.vr_display_frequency} Hz")
# 获取IPD瞳距
ipd = self.vr_system.getFloatTrackedDeviceProperty(
openvr.k_unTrackedDeviceIndex_Hmd,
openvr.Prop_UserIpdMeters_Float
)
print(f"✓ IPD瞳距: {ipd * 1000:.1f}mm")
# 获取设备制造商和型号
manufacturer = self.vr_system.getStringTrackedDeviceProperty(
openvr.k_unTrackedDeviceIndex_Hmd,
openvr.Prop_ManufacturerName_String
)
model = self.vr_system.getStringTrackedDeviceProperty(
openvr.k_unTrackedDeviceIndex_Hmd,
openvr.Prop_ModelNumber_String
)
print(f"✓ VR设备: {manufacturer} {model}")
# 获取更多系统配置信息
try:
# 获取刷新率相关信息
seconds_since_last_vsync = self.vr_system.getFloatTrackedDeviceProperty(
openvr.k_unTrackedDeviceIndex_Hmd,
openvr.Prop_SecondsFromVsyncToPhotons_Float
)
self.vsync_to_photons_ms = seconds_since_last_vsync * 1000
print(f"✓ VSync到光子延迟: {self.vsync_to_photons_ms:.2f}ms")
# 获取显示延迟信息
display_refresh_rate = self.vr_display_frequency
if display_refresh_rate > 0:
self.target_frame_time_ms = 1000.0 / display_refresh_rate
print(f"✓ 目标帧时间: {self.target_frame_time_ms:.2f}ms")
# 计算VSync时间窗口
self.vsync_window_ms = self.target_frame_time_ms * 0.1 # 10%的窗口
print(f"✓ VSync时间窗口: ±{self.vsync_window_ms:.2f}ms")
except Exception as vsync_error:
print(f"⚠️ 获取VSync信息失败: {vsync_error}")
# 检查是否启用了异步重投影
try:
if hasattr(openvr, 'VRSettings'):
settings = openvr.VRSettings()
if settings:
self.async_reprojection_enabled = settings.getBool("steamvr", "enableAsyncReprojection")
print(f"✓ 异步重投影: {'启用' if self.async_reprojection_enabled else '禁用'}")
self.motion_smoothing_enabled = settings.getBool("steamvr", "motionSmoothing")
print(f"✓ 运动平滑: {'启用' if self.motion_smoothing_enabled else '禁用'}")
except Exception as settings_error:
print(f"⚠️ 获取VR设置失败: {settings_error}")
except Exception as e:
print(f"⚠️ 获取VR系统信息失败: {e}")
# 创建OpenVR姿态数组 - 分离渲染和游戏姿态
poses_t = openvr.TrackedDevicePose_t * openvr.k_unMaxTrackedDeviceCount
self.poses = poses_t() # 渲染姿态(预测的)
self.game_poses = poses_t() # 游戏逻辑姿态(当前的)
print("✓ VR渲染和游戏姿态数组已创建")
# 创建VR渲染缓冲区
if not self._create_vr_buffers():
print("❌ 创建VR渲染缓冲区失败")
return False
# 设置VR相机
if not self._setup_vr_cameras():
print("❌ 设置VR相机失败")
return False
# 优化VR渲染管线
self._optimize_vr_rendering()
# 初始化手柄控制器
self._initialize_controllers()
# 初始化动作系统 - 仅在启用时初始化
if not self.disable_action_system and self.action_manager:
try:
if not self.action_manager.initialize():
print("⚠️ VR动作系统初始化失败但VR系统将继续运行")
except Exception as e:
print(f"⚠️ VR动作系统初始化异常: {e}")
else:
if self.disable_action_system:
print("🚫 VR动作系统已禁用跳过初始化")
else:
print("⚠️ VR动作管理器未创建跳过动作系统初始化")
# 初始化交互系统 - 检查是否存在
if self.interaction_manager:
try:
if not self.interaction_manager.initialize():
print("⚠️ VR交互系统初始化失败但VR系统将继续运行")
except Exception as e:
print(f"⚠️ VR交互系统初始化异常: {e}")
else:
print("⚠️ VR交互管理器未创建跳过交互系统初始化")
# 初始化VR传送系统
if self.teleport_system:
try:
if not self.teleport_system.initialize():
print("⚠️ VR传送系统初始化失败但VR系统将继续运行")
except Exception as e:
print(f"⚠️ VR传送系统初始化异常: {e}")
else:
print("⚠️ VR传送系统未创建跳过传送系统初始化")
# 初始化VR摇杆系统
if self.joystick_manager:
try:
# 传入传送系统引用给摇杆管理器
self.joystick_manager.initialize(self.teleport_system)
print("✓ VR摇杆系统初始化成功")
except Exception as e:
print(f"⚠️ VR摇杆系统初始化异常: {e}")
else:
print("⚠️ VR摇杆管理器未创建跳过摇杆系统初始化")
# 可选:禁用异步重投影(备选方案)
if self.disable_async_reprojection:
self._disable_async_reprojection()
# 启动VR更新任务
self._start_vr_task()
self.vr_initialized = True
print("✅ VR系统初始化成功")
return True
except Exception as e:
print(f"❌ VR初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_vr_buffers(self):
"""创建VR渲染缓冲区 - 使用分辨率缩放优化"""
try:
print(f"🔧 创建VR缓冲区:")
print(f" 推荐分辨率: {self.base_eye_width}x{self.base_eye_height}")
print(f" 缩放系数: {self.resolution_scale}")
print(f" 实际分辨率: {self.eye_width}x{self.eye_height}")
print(f" 像素减少: {(1 - self.resolution_scale**2) * 100:.1f}%")
# 创建左眼纹理和缓冲区
self.vr_left_texture = self._create_vr_texture("VR Left Eye Texture")
self.vr_left_eye_buffer = self._create_vr_buffer(
"VR Left Eye",
self.vr_left_texture,
self.eye_width,
self.eye_height
)
if not self.vr_left_eye_buffer:
print("❌ 创建左眼缓冲区失败")
return False
# 设置左眼缓冲区属性
self.vr_left_eye_buffer.setSort(-100)
self.vr_left_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) # 深蓝色背景便于调试
self.vr_left_eye_buffer.setActive(True)
# 创建右眼纹理和缓冲区
self.vr_right_texture = self._create_vr_texture("VR Right Eye Texture")
self.vr_right_eye_buffer = self._create_vr_buffer(
"VR Right Eye",
self.vr_right_texture,
self.eye_width,
self.eye_height
)
if not self.vr_right_eye_buffer:
print("❌ 创建右眼缓冲区失败")
return False
# 设置右眼缓冲区属性
self.vr_right_eye_buffer.setSort(-99)
self.vr_right_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) # 深蓝色背景便于调试
self.vr_right_eye_buffer.setActive(True)
# 🚀 关键优化立即准备纹理并缓存OpenGL ID避免后续重复准备
print("🔧 准备纹理并缓存OpenGL ID...")
if self._prepare_and_cache_textures():
print("✅ VR渲染缓冲区和纹理缓存创建成功")
return True
else:
print("❌ 纹理准备失败")
return False
except Exception as e:
print(f"❌ 创建VR缓冲区失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_vr_texture(self, name):
"""创建VR纹理对象 - 基于参考实现"""
texture = Texture(name)
texture.setWrapU(Texture.WMClamp)
texture.setWrapV(Texture.WMClamp)
texture.setMinfilter(Texture.FTLinear)
texture.setMagfilter(Texture.FTLinear)
return texture
def _prepare_and_cache_textures(self):
"""准备纹理并缓存OpenGL ID - 解决重复准备问题"""
try:
# 获取graphics state guardian和prepared objects
gsg = self.world.win.getGsg()
if not gsg:
print("❌ 无法获取GraphicsStateGuardian")
return False
prepared_objects = gsg.getPreparedObjects()
if not prepared_objects:
print("❌ 无法获取PreparedObjects")
return False
# 准备左眼纹理并缓存ID
if self.vr_left_texture:
print(f" 准备左眼纹理: {self.vr_left_texture.getName()}")
texture_context = self.vr_left_texture.prepareNow(0, prepared_objects, gsg)
if texture_context and hasattr(texture_context, 'getNativeId'):
self.left_texture_id = texture_context.getNativeId()
if self.left_texture_id > 0:
print(f" ✅ 左眼纹理ID缓存: {self.left_texture_id}")
else:
print(" ❌ 左眼纹理ID无效")
return False
else:
print(" ❌ 左眼纹理准备失败")
return False
# 准备右眼纹理并缓存ID
if self.vr_right_texture:
print(f" 准备右眼纹理: {self.vr_right_texture.getName()}")
texture_context = self.vr_right_texture.prepareNow(0, prepared_objects, gsg)
if texture_context and hasattr(texture_context, 'getNativeId'):
self.right_texture_id = texture_context.getNativeId()
if self.right_texture_id > 0:
print(f" ✅ 右眼纹理ID缓存: {self.right_texture_id}")
else:
print(" ❌ 右眼纹理ID无效")
return False
else:
print(" ❌ 右眼纹理准备失败")
return False
# 标记纹理已准备
self.textures_prepared = True
print(" ✅ 纹理准备完成ID已缓存后续帧将直接使用缓存ID")
return True
except Exception as e:
print(f"❌ 纹理准备和缓存失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_vr_buffer(self, name, texture, width, height):
"""创建VR渲染缓冲区 - 基于参考实现 + 性能诊断"""
# 设置帧缓冲属性
fbprops = FrameBufferProperties()
fbprops.setRgbaBits(1, 1, 1, 1)
# 可以在这里添加多重采样抗锯齿
# fbprops.setMultisamples(4)
# 创建缓冲区
buffer = self.world.win.makeTextureBuffer(name, width, height, to_ram=False, fbp=fbprops)
if buffer:
# 🔍 性能诊断:检查缓冲区类型和属性
self._diagnose_buffer_performance(buffer, name, width, height)
# 清除默认渲染纹理
buffer.clearRenderTextures()
# 🚀 使用RTMBindOrCopy模式 - 已经是最优选择
# RTMBindOrCopy会尝试直接绑定到纹理只有硬件不支持时才回退到复制
# 这已经是Panda3D中最高效的渲染到纹理模式
buffer.addRenderTexture(texture, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
else:
print(f"⚠️ VR缓冲区创建失败: {name} ({width}x{height})")
return buffer
def _diagnose_buffer_performance(self, buffer, name, width, height):
"""诊断VR缓冲区性能特性"""
try:
# 检查缓冲区类型
buffer_type = type(buffer).__name__
# 检查是否为parasite buffer性能较差的类型
is_parasite = 'Parasite' in buffer_type
# 获取缓冲区信息
is_valid = buffer.isValid()
is_hardware = hasattr(buffer, 'getGsg') and buffer.getGsg() is not None
# 输出诊断信息(仅第一次创建时)
if not hasattr(self, '_buffer_diagnosis_shown'):
print(f"🔍 VR缓冲区诊断 [{name}]:")
print(f" 类型: {buffer_type}")
print(f" 分辨率: {width}x{height} ({width*height/1000000:.1f}M像素)")
print(f" 有效性: {'' if is_valid else ''}")
print(f" 硬件支持: {'' if is_hardware else ''}")
if is_parasite:
print(f" ⚠️ 检测到Parasite Buffer - 性能可能受限")
print(f" 建议: 检查显卡是否支持真正的离屏渲染")
else:
print(f" ✓ 使用硬件FBO - 性能良好")
# 显示帧缓冲区属性
if hasattr(buffer, 'getFbProperties'):
fbp = buffer.getFbProperties()
if fbp:
print(f" 颜色位数: R{fbp.getRedBits()}G{fbp.getGreenBits()}B{fbp.getBlueBits()}A{fbp.getAlphaBits()}")
if hasattr(fbp, 'getMultisamples') and fbp.getMultisamples() > 0:
print(f" MSAA: {fbp.getMultisamples()}x")
self._buffer_diagnosis_shown = True
except Exception as e:
print(f"缓冲区诊断失败: {e}")
def _setup_vr_cameras(self):
"""设置VR相机 - 使用锚点层级系统"""
try:
# 创建VR追踪空间锚点层级
self.tracking_space = self.world.render.attachNewNode('tracking-space')
self.hmd_anchor = self.tracking_space.attachNewNode('hmd-anchor')
self.left_eye_anchor = self.hmd_anchor.attachNewNode('left-eye')
self.right_eye_anchor = self.hmd_anchor.attachNewNode('right-eye')
# 获取投影矩阵
projection_left = self.coord_mat_inv * self.convert_mat(
self.vr_system.getProjectionMatrix(openvr.Eye_Left, self.near_clip, self.far_clip))
projection_right = self.coord_mat_inv * self.convert_mat(
self.vr_system.getProjectionMatrix(openvr.Eye_Right, self.near_clip, self.far_clip))
# 创建左眼相机节点
left_cam_node = Camera('left-cam')
left_lens = MatrixLens()
left_lens.setUserMat(projection_left)
left_cam_node.setLens(left_lens)
# 创建右眼相机节点
right_cam_node = Camera('right-cam')
right_lens = MatrixLens()
right_lens.setUserMat(projection_right)
right_cam_node.setLens(right_lens)
# 附加相机到眼睛锚点
self.vr_left_camera = self.left_eye_anchor.attachNewNode(left_cam_node)
self.vr_right_camera = self.right_eye_anchor.attachNewNode(right_cam_node)
# 设置显示区域使用标准渲染流程
left_dr = self.vr_left_eye_buffer.makeDisplayRegion()
left_dr.setCamera(self.vr_left_camera)
left_dr.setActive(True)
# 恢复DrawCallback以精确控制渲染时机
left_dr.setDrawCallback(PythonCallbackObject(self.simple_left_cb))
right_dr = self.vr_right_eye_buffer.makeDisplayRegion()
right_dr.setCamera(self.vr_right_camera)
right_dr.setActive(True)
# 恢复DrawCallback以精确控制渲染时机
right_dr.setDrawCallback(PythonCallbackObject(self.simple_right_cb))
print("✓ VR相机锚点层级系统设置完成")
return True
except Exception as e:
print(f"❌ 设置VR相机失败: {e}")
import traceback
traceback.print_exc()
return False
def _get_eye_offset(self, eye):
"""获取眼睛相对于头显的偏移"""
try:
if not self.vr_system:
# 使用标准IPD瞳距估算值
ipd = 0.064 # 64mm平均IPD
if eye == openvr.Eye_Left:
return Vec3(-ipd/2, 0, 0)
else:
return Vec3(ipd/2, 0, 0)
# 从OpenVR获取眼睛到头显的变换矩阵
eye_transform = self.vr_system.getEyeToHeadTransform(eye)
# 提取位移信息
x = eye_transform[0][3]
y = eye_transform[1][3]
z = eye_transform[2][3]
return Vec3(x, y, z)
except Exception as e:
print(f"❌ 获取眼睛偏移失败: {e}")
# 返回默认值
ipd = 0.064
if eye == openvr.Eye_Left:
return Vec3(-ipd/2, 0, 0)
else:
return Vec3(ipd/2, 0, 0)
def _optimize_vr_rendering(self):
"""优化VR模式下的渲染管线 - 减少不必要的后处理开销"""
try:
print("🔧 正在优化VR渲染管线...")
# 检查是否有RenderPipeline
if hasattr(self.world, 'render_pipeline') and self.world.render_pipeline:
print(" 检测到RenderPipeline正在优化...")
# 对VR缓冲区应用轻量级渲染设置
if hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer:
self._apply_lightweight_rendering(self.vr_left_eye_buffer, "左眼")
if hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer:
self._apply_lightweight_rendering(self.vr_right_eye_buffer, "右眼")
print("✅ VR渲染管线优化完成")
else:
print(" 未检测到RenderPipeline使用默认Panda3D渲染")
# 禁用VR缓冲区的不必要功能
self._disable_vr_buffer_extras()
except Exception as e:
print(f"⚠️ VR渲染优化失败: {e}")
import traceback
traceback.print_exc()
def _apply_lightweight_rendering(self, buffer, eye_name):
"""为VR缓冲区应用轻量级渲染设置"""
try:
# 禁用多重采样抗锯齿MSAA以提升性能
if hasattr(buffer, 'setMultisample'):
buffer.setMultisample(0)
print(f" {eye_name}: 禁用MSAA")
# 设置更简单的清除颜色操作
buffer.setClearColorActive(True)
buffer.setClearColor((0.1, 0.2, 0.4, 1.0)) # 深蓝色,便于调试
# 禁用深度写入到某些不需要的缓冲区
# (保留主要深度测试,但减少不必要的写入)
print(f" {eye_name}: 应用轻量级渲染设置")
except Exception as e:
print(f"⚠️ {eye_name}缓冲区优化失败: {e}")
def _disable_vr_buffer_extras(self):
"""禁用VR缓冲区的额外功能以提升性能"""
try:
# 禁用VR缓冲区的统计收集
if hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer:
if hasattr(self.vr_left_eye_buffer, 'setOneShot'):
self.vr_left_eye_buffer.setOneShot(False)
if hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer:
if hasattr(self.vr_right_eye_buffer, 'setOneShot'):
self.vr_right_eye_buffer.setOneShot(False)
print(" VR缓冲区额外功能已优化")
except Exception as e:
print(f"⚠️ VR缓冲区额外功能优化失败: {e}")
def _start_vr_task(self):
"""启动VR更新任务"""
if self.vr_task:
self.world.taskMgr.remove(self.vr_task)
# 设置高优先级sort=-1000确保在渲染之前执行参考 panda3d-openvr
self.vr_task = self.world.taskMgr.add(self._update_vr, "update_vr", sort=-1000)
print("✓ VR更新任务已启动高优先级sort=-1000")
def _update_vr(self, task):
"""VR更新任务 - 每帧调用(参考 panda3d-openvr 架构)"""
if not self.vr_enabled or not self.vr_system:
return task.cont
try:
# 📌 第一件事waitGetPoses 阻塞等待 VSync参考 panda3d-openvr
# 这会阻塞整个任务,确保每个 VSync 周期只执行一次
should_call_waitgetposes = (
not self.vr_test_mode or # 普通VR模式总是需要
self.test_mode_wait_poses # 测试模式:仅当启用姿态等待时
)
if should_call_waitgetposes:
self._wait_get_poses_immediate()
# 立即更新手柄,使用最新姿态(避免过时数据)
self.update_tracked_devices()
# 性能监控
self.frame_count += 1
# 🚀 自动启用性能模式 - 减少计时对象创建
if not self.performance_mode_enabled and self.frame_count >= self.performance_mode_trigger_frame:
self.performance_mode_enabled = True
if self.frame_count <= self.performance_mode_trigger_frame + 5: # 只输出一次
print(f"🎯 性能模式已启用 (帧#{self.frame_count}) - 禁用详细监控以提升性能")
# 记录帧时间
self._track_frame_time()
# 计算VR FPS
import time
current_time = time.time()
if self.last_fps_time == 0:
self.last_fps_time = current_time
elif current_time - self.last_fps_time >= 1.0: # 每秒更新一次FPS
self.vr_fps = (self.frame_count - self.last_fps_check) / (current_time - self.last_fps_time)
self.last_fps_check = self.frame_count
self.last_fps_time = current_time
# 📌 使用刚获取的姿态更新相机(而不是旧姿态)
self._update_camera_poses()
# 输出策略信息(仅第一次)
if not hasattr(self, '_new_architecture_logged'):
print("✅ 新架构已启用 - waitGetPoses 在任务开始阻塞,参考 panda3d-openvr")
print(" 这确保每个 VSync 周期只渲染一次,解决 AlreadySubmitted 错误")
self._new_architecture_logged = True
# 更新VR动作状态 - 仅在启用时更新
if not self.disable_action_system and self.action_manager:
self.action_manager.update_actions()
# 更新VR交互系统
if self.interaction_manager:
self.interaction_manager.update()
# 更新VR摇杆系统
if self.joystick_manager:
# 计算帧间隔时间
import time
if not hasattr(self, '_last_frame_time'):
self._last_frame_time = time.time()
dt = 0.016 # 默认60fps
else:
current_time = time.time()
dt = current_time - self._last_frame_time
self._last_frame_time = current_time
# 限制dt范围避免异常情况
dt = max(0.001, min(0.1, dt))
self.joystick_manager.update(dt)
# 更新系统性能指标(减少频率)
if self.frame_count % 30 == 1: # 每30帧更新一次减少开销
self._update_performance_metrics()
# 更新GPU渲染时间统计减少频率
if self.enable_gpu_timing and self.frame_count % 60 == 1:
self._get_gpu_frame_timing()
# 🚀 手动垃圾回收控制 - 避免16-19帧周期性峰值
self._manual_gc_control()
# 定期输出性能报告 - 默认10秒间隔
report_interval = getattr(self, 'performance_report_interval', 600)
if self.frame_count % report_interval == 1:
self._print_performance_report()
except Exception as e:
print(f"VR更新错误: {e}")
import traceback
traceback.print_exc()
return task.cont
def _sync_gpu_if_needed(self):
"""可选的GPU同步 - 仅在需要时使用"""
try:
# 同步等待GPU完成如果需要
gsg = self.world.win.getGsg()
if gsg:
gsg.getEngine().syncFrame()
except Exception as e:
print(f"❌ GPU同步异常: {e}")
def simple_left_cb(self, cbdata):
"""简化的左眼渲染回调 - 精确控制渲染和提交"""
try:
# 🔍 精确测量渲染时间
import time
render_start = time.perf_counter()
# 触发实际渲染
cbdata.upcall()
# 计算真实渲染时间
self.left_render_time = (time.perf_counter() - render_start) * 1000 # 转换为毫秒
# 记录渲染次数
self.left_render_count += 1
# 📌 OpenVR 帧边界检查:防止同一 OpenVR 帧内重复渲染
if self.left_eye_last_render_frame == self.openvr_frame_id:
return # 已在当前 OpenVR 帧渲染过,跳过
# 🔧 OpenVR最佳实践左眼只渲染不立即提交
# 基于官方hellovr示例两眼都渲染完后再批量提交
self.left_eye_last_render_frame = self.openvr_frame_id
# 渐进式VR功能测试单独启用纹理提交时保留原逻辑
should_submit = self.vr_test_mode and self.test_mode_submit_texture and not self.test_mode_wait_poses
if should_submit:
# 测试模式:单独启用纹理提交时的兼容模式
if self.vr_compositor and self.vr_left_texture:
self.submit_texture(openvr.Eye_Left, self.vr_left_texture)
if self.vr_test_mode:
# 测试模式:始终触发屏幕显示更新
self._update_test_display()
except Exception as e:
print(f"左眼渲染回调错误: {e}")
def simple_right_cb(self, cbdata):
"""简化的右眼渲染回调 - 精确控制渲染和提交"""
try:
# 🔍 精确测量渲染时间
import time
render_start = time.perf_counter()
# 触发实际渲染
cbdata.upcall()
# 计算真实渲染时间
self.right_render_time = (time.perf_counter() - render_start) * 1000 # 转换为毫秒
# 记录渲染次数
self.right_render_count += 1
# 📌 OpenVR 帧边界检查:防止同一 OpenVR 帧内重复渲染
if self.right_eye_last_render_frame == self.openvr_frame_id:
return # 已在当前 OpenVR 帧渲染过,跳过
# 🔧 OpenVR最佳实践右眼渲染完成后批量提交两眼纹理
# 基于官方hellovr示例避免分散提交导致的VSync阻塞
self.right_eye_last_render_frame = self.openvr_frame_id
# 🔧 渐进式VR功能测试根据调试标志决定启用哪些功能
should_batch_submit = not self.vr_test_mode or self.test_mode_submit_texture
should_wait_poses = not self.vr_test_mode or self.test_mode_wait_poses
# 特殊处理:测试模式单独启用功能时的兼容模式
if self.vr_test_mode:
if self.test_mode_submit_texture and not self.test_mode_wait_poses:
# 单独测试纹理提交:使用原来的分散提交方式
if self.vr_compositor and self.vr_right_texture:
self.submit_texture(openvr.Eye_Right, self.vr_right_texture)
# 🚀 测试模式也需要PostPresentHandoff避免36FPS
try:
if hasattr(self.vr_compositor, 'postPresentHandoff'):
self.vr_compositor.postPresentHandoff()
elif hasattr(self.vr_compositor, 'PostPresentHandoff'):
self.vr_compositor.PostPresentHandoff()
except Exception as e:
pass # 测试模式静默忽略错误
should_batch_submit = False
elif self.test_mode_wait_poses and not self.test_mode_submit_texture:
# 单独测试姿态等待:不提交纹理
should_batch_submit = False
if should_batch_submit:
# 🚀 OpenVR最佳实践批量提交两眼纹理
# 这是解决36FPS问题的关键修复
self._batch_submit_textures()
# 🔧 关键修复移除Submit后立即WaitGetPoses的错误实现
# 根据OpenVR官方文档"Calling WaitGetPoses immediately after Submit is conspicuously wrong"
# WaitGetPoses应该在下一帧开始时通过update_vr_task调用不是Submit后立即调用
#
# if should_wait_poses:
# # 错误的实现Submit后立即获取姿态导致36FPS
# self._wait_get_poses_immediate() # ← 这是36FPS的根本原因
#
# 正确的实现让update_vr_task在下一帧开始时调用WaitGetPoses
if self.vr_test_mode:
# 测试模式始终更新性能HUD
self._update_test_performance_hud()
except Exception as e:
print(f"右眼渲染回调错误: {e}")
def _wait_get_poses(self):
"""调用VRCompositor的waitGetPoses来获取焦点和姿态数据"""
try:
if not self.vr_compositor or not self.poses:
return
# 调用waitGetPoses获取焦点和姿态数据
# 这个调用可能会阻塞直到下一个VR同步点
result = self.vr_compositor.waitGetPoses(self.poses, None)
# 检查姿态数据的有效性
valid_poses = 0
# 更新HMD姿态设备0通常是头显
if len(self.poses) > 0 and self.poses[0].bPoseIsValid:
valid_poses += 1
else:
# 如果HMD姿态无效不要频繁输出错误信息
if not hasattr(self, '_hmd_invalid_warning_shown'):
print("⚠️ HMD姿态数据无效")
self._hmd_invalid_warning_shown = True
# 更新控制器姿态
self.controller_poses.clear()
for device_id in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
if self.poses[device_id].bPoseIsValid:
device_class = self.vr_system.getTrackedDeviceClass(device_id)
if device_class == openvr.TrackedDeviceClass_Controller:
controller_matrix = self.poses[device_id].mDeviceToAbsoluteTracking
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
valid_poses += 1
# 性能监控 - 偶尔输出姿态状态
if self.frame_count % 600 == 1: # 每10秒输出一次@60fps
print(f"📊 VR姿态状态 - 有效姿态数: {valid_poses}, 总帧数: {self.frame_count}")
except Exception as e:
# 限制错误输出频率
if not hasattr(self, '_last_error_frame'):
self._last_error_frame = 0
if self.frame_count - self._last_error_frame > 300: # 每5秒最多输出一次错误
print(f"waitGetPoses失败: {e}")
self._last_error_frame = self.frame_count
# 记录姿态失败次数
self.pose_failures += 1
def _wait_get_poses_immediate(self):
"""立即获取VR姿态 - 修复ATW闪烁的关键方法双姿态版本"""
# 开始计时waitGetPoses操作
timing = self._start_timing('wait_poses')
try:
if not self.vr_compositor or not self.poses or not self.game_poses:
self._end_timing(timing)
return
# 关键修复:传递渲染姿态和游戏姿态数组
# 渲染姿态用于绘制游戏姿态用于逻辑避免ATW过度补偿
result = self.vr_compositor.waitGetPoses(self.poses, self.game_poses)
# 📌 waitGetPoses 成功后立即递增 OpenVR 帧ID新帧开始
self.openvr_frame_id += 1
# 结束计时
wait_time = self._end_timing(timing)
# 检查姿态数据的有效性
valid_poses = 0
# 更新HMD姿态设备0通常是头显
if len(self.poses) > 0 and self.poses[0].bPoseIsValid:
valid_poses += 1
else:
# 如果HMD姿态无效不要频繁输出错误信息
if not hasattr(self, '_hmd_invalid_warning_shown'):
print("⚠️ HMD姿态数据无效立即模式")
self._hmd_invalid_warning_shown = True
# 🚀 优化控制器姿态更新使用缓存避免每帧clear()和重新创建对象
for device_id in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
if self.poses[device_id].bPoseIsValid:
device_class = self.vr_system.getTrackedDeviceClass(device_id)
if device_class == openvr.TrackedDeviceClass_Controller:
controller_matrix = self.poses[device_id].mDeviceToAbsoluteTracking
# 检查是否已有此设备的缓存矩阵
if device_id not in self.controller_poses:
# 第一次检测到该控制器,创建新的缓存矩阵
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
else:
# 复用现有矩阵,只更新数值,避免创建新对象
cached_mat = self.controller_poses[device_id]
self._update_matrix_from_openvr(cached_mat, controller_matrix)
valid_poses += 1
else:
# 设备姿态无效,从字典中移除(如果存在)
if device_id in self.controller_poses:
# 将矩阵返回对象池
self._return_pooled_matrix(self.controller_poses[device_id])
del self.controller_poses[device_id]
# 🔧 关键修复:立即更新手柄和跟踪设备
# Running Start模式下必须在WaitGetPoses后立即更新避免手柄消失
self.update_tracked_devices()
# 调试信息 - 仅在第一次成功时输出
if not hasattr(self, '_dual_pose_mode_logged') and valid_poses > 0:
print(f"✅ 双姿态立即获取模式启用 - 有效姿态数: {valid_poses}")
print(" 渲染姿态用于绘制游戏姿态用于逻辑防止ATW过度补偿")
print(" 手柄和跟踪设备在WaitGetPoses后立即更新")
self._dual_pose_mode_logged = True
except Exception as e:
# 限制错误输出频率
if not hasattr(self, '_last_immediate_error_frame'):
self._last_immediate_error_frame = 0
if self.frame_count - self._last_immediate_error_frame > 300: # 每5秒最多输出一次错误
print(f"立即姿态获取失败: {e}")
self._last_immediate_error_frame = self.frame_count
self.pose_failures += 1
def _wait_get_poses_with_prediction(self):
"""使用预测时间获取VR姿态 - 优化性能的新策略"""
try:
if not self.vr_compositor or not self.poses or not self.game_poses:
return
# 使用预测时间获取姿态
# 预测时间通常为11-16ms对应下一个VR帧的时间
result = self.vr_compositor.waitGetPoses(self.poses, self.game_poses)
# 检查姿态数据的有效性
valid_poses = 0
# 更新HMD姿态设备0通常是头显
if len(self.poses) > 0 and self.poses[0].bPoseIsValid:
valid_poses += 1
else:
# 如果HMD姿态无效不要频繁输出错误信息
if not hasattr(self, '_hmd_invalid_warning_task'):
print("⚠️ HMD姿态数据无效更新任务模式")
self._hmd_invalid_warning_task = True
# 更新控制器姿态
self.controller_poses.clear()
for device_id in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
if self.poses[device_id].bPoseIsValid:
device_class = self.vr_system.getTrackedDeviceClass(device_id)
if device_class == openvr.TrackedDeviceClass_Controller:
controller_matrix = self.poses[device_id].mDeviceToAbsoluteTracking
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
valid_poses += 1
# 调试信息 - 仅在第一次成功时输出
if not hasattr(self, '_update_task_mode_logged') and valid_poses > 0:
print(f"✅ 更新任务姿态获取模式启用 - 有效姿态数: {valid_poses}")
print(f" 预测时间: {self.use_prediction_time*1000:.1f}ms")
self._update_task_mode_logged = True
except Exception as e:
# 限制错误输出频率
if not hasattr(self, '_last_task_error_frame'):
self._last_task_error_frame = 0
if self.frame_count - self._last_task_error_frame > 300: # 每5秒最多输出一次错误
print(f"更新任务姿态获取失败: {e}")
self._last_task_error_frame = self.frame_count
# 记录姿态失败次数
self.pose_failures += 1
def _cache_poses_for_next_frame(self):
"""缓存当前姿态供下一帧渲染使用 - 修复时序不匹配"""
try:
if not self.poses or len(self.poses) == 0:
return
# 如果是第一帧,直接使用当前姿态
if self._first_frame:
self._cached_render_poses = self.poses
self._first_frame = False
print("✓ 首帧姿态缓存已设置")
return
# 复制当前渲染姿态到缓存
# 下一帧将使用这些姿态进行渲染
poses_t = openvr.TrackedDevicePose_t * openvr.k_unMaxTrackedDeviceCount
cached_poses = poses_t()
# 复制姿态数据
for i in range(len(self.poses)):
cached_poses[i] = self.poses[i]
self._cached_render_poses = cached_poses
except Exception as e:
print(f"⚠️ 姿态缓存失败: {e}")
def _reset_waitgetposes_flag(self, task):
"""重置WaitGetPoses标记 - 确保下一帧可以调用WaitGetPoses"""
self._waitgetposes_called_this_frame = False
return task.done
def _update_tracking_data(self):
"""更新VR追踪数据"""
try:
# 获取设备姿态
poses = self.vr_system.getDeviceToAbsoluteTrackingPose(
openvr.TrackingUniverseStanding, 0.0, openvr.k_unMaxTrackedDeviceCount
)
# 更新HMD姿态设备0通常是头显
if poses[0].bPoseIsValid:
hmd_matrix = poses[0].mDeviceToAbsoluteTracking
self.hmd_pose = self._convert_openvr_matrix_to_panda(hmd_matrix)
# 更新控制器姿态
for device_id in range(1, openvr.k_unMaxTrackedDeviceCount):
if poses[device_id].bPoseIsValid:
device_class = self.vr_system.getTrackedDeviceClass(device_id)
if device_class == openvr.TrackedDeviceClass_Controller:
controller_matrix = poses[device_id].mDeviceToAbsoluteTracking
self.controller_poses[device_id] = self._convert_openvr_matrix_to_panda(controller_matrix)
except Exception as e:
print(f"更新追踪数据失败: {e}")
def _convert_openvr_matrix_to_panda(self, ovr_matrix):
"""将OpenVR矩阵转换为Panda3D矩阵 - 使用对象池优化
坐标系转换:
OpenVR: X右, Y上, -Z前右手坐标系
Panda3D: X右, Y前, Z上右手坐标系
转换规则:
OpenVR X → Panda3D X
OpenVR Y → Panda3D Z
OpenVR -Z → Panda3D Y
"""
# 🚀 使用对象池获取预分配的Mat4对象避免每帧创建新对象
mat = self._get_pooled_matrix()
# 修正的坐标转换矩阵
# OpenVR: X右, Y上, -Z前 → Panda3D: X右, Y前, Z上
# 转换规则: (ovr_x, ovr_y, ovr_z) → (panda_x, panda_y, panda_z)
# (ovr_x, ovr_y, ovr_z) → (ovr_x, -ovr_z, ovr_y)
# X轴行Panda3D的X轴对应OpenVR的X轴
mat.setCell(0, 0, ovr_matrix[0][0]) # X_x → X_x
mat.setCell(0, 1, ovr_matrix[0][1]) # X_y → X_y
mat.setCell(0, 2, ovr_matrix[0][2]) # X_z → X_z
mat.setCell(0, 3, ovr_matrix[0][3]) # 位移X分量
# Y轴行Panda3D的Y轴对应OpenVR的-Z轴
mat.setCell(1, 0, -ovr_matrix[2][0]) # -Z_x → Y_x
mat.setCell(1, 1, -ovr_matrix[2][1]) # -Z_y → Y_y
mat.setCell(1, 2, -ovr_matrix[2][2]) # -Z_z → Y_z
mat.setCell(1, 3, -ovr_matrix[2][3]) # 位移Y分量-Z位移
# Z轴行Panda3D的Z轴对应OpenVR的Y轴
mat.setCell(2, 0, ovr_matrix[1][0]) # Y_x → Z_x
mat.setCell(2, 1, ovr_matrix[1][1]) # Y_y → Z_y
mat.setCell(2, 2, ovr_matrix[1][2]) # Y_z → Z_z
mat.setCell(2, 3, ovr_matrix[1][3]) # 位移Z分量Y位移
# 齐次坐标
mat.setCell(3, 0, 0)
mat.setCell(3, 1, 0)
mat.setCell(3, 2, 0)
mat.setCell(3, 3, 1)
# 🚀 优化调试信息 - 避免创建Vec3对象减少GC压力
if not hasattr(self, '_coord_debug_counter'):
self._coord_debug_counter = 0
self._coord_debug_counter += 1
if self._coord_debug_counter % 600 == 1: # 每10秒输出一次@60fps
print(f"🔄 坐标系转换调试 (第{self._coord_debug_counter}帧)")
# 直接输出数值避免创建Vec3对象
ovr_x, ovr_y, ovr_z = ovr_matrix[0][3], ovr_matrix[1][3], ovr_matrix[2][3]
print(f" OpenVR原始位置: ({ovr_x:.3f}, {ovr_y:.3f}, {ovr_z:.3f})")
# 直接从矩阵读取数值避免创建Vec3对象
panda_x = mat.getCell(0, 3)
panda_y = mat.getCell(1, 3)
panda_z = mat.getCell(2, 3)
print(f" Panda3D转换位置: ({panda_x:.3f}, {panda_y:.3f}, {panda_z:.3f})")
# 手动验证转换OpenVR (x,y,z) → Panda3D (x,-z,y)
expected_x = ovr_x
expected_y = -ovr_z
expected_z = ovr_y
print(f" 预期转换结果: ({expected_x:.3f}, {expected_y:.3f}, {expected_z:.3f})")
# 计算误差避免创建Vec3对象
diff_x = panda_x - expected_x
diff_y = panda_y - expected_y
diff_z = panda_z - expected_z
diff_magnitude = (diff_x*diff_x + diff_y*diff_y + diff_z*diff_z)**0.5
if diff_magnitude < 0.001:
print(f" ✅ 坐标转换正确 (误差: {diff_magnitude:.6f})")
else:
print(f" ⚠️ 坐标转换可能有误 (误差: {diff_magnitude:.6f})")
print(f" 差异: ({diff_x:.6f}, {diff_y:.6f}, {diff_z:.6f})")
return mat
def update_hmd(self, pose):
"""
更新HMD锚点 - 基于参考实现
"""
try:
# 将OpenVR姿态转换为Panda3D矩阵
modelview = self.convert_mat(pose.mDeviceToAbsoluteTracking)
# 应用坐标系转换并设置HMD锚点
self.hmd_anchor.setMat(self.coord_mat_inv * modelview * self.coord_mat)
# 获取眼睛到头部的变换
view_left = self.convert_mat(self.vr_system.getEyeToHeadTransform(openvr.Eye_Left))
view_right = self.convert_mat(self.vr_system.getEyeToHeadTransform(openvr.Eye_Right))
# 设置眼睛锚点
self.left_eye_anchor.setMat(self.coord_mat_inv * view_left * self.coord_mat)
self.right_eye_anchor.setMat(self.coord_mat_inv * view_right * self.coord_mat)
except Exception as e:
print(f"更新HMD姿态失败: {e}")
def _update_camera_poses(self):
"""更新相机姿态 - 使用锚点系统简化处理"""
try:
# 使用锚点系统后,相机位置自动跟随锚点
# 只需要获取HMD姿态并更新锚点即可
# 从poses数组中获取HMD姿态
if hasattr(self, 'poses') and len(self.poses) > 0:
hmd_pose = self.poses[openvr.k_unTrackedDeviceIndex_Hmd]
if hmd_pose.bPoseIsValid:
self.update_hmd(hmd_pose)
else:
print("⚠️ HMD姿态数据无效")
except Exception as e:
print(f"更新相机姿态失败: {e}")
import traceback
traceback.print_exc()
def _update_camera_poses_with_cache(self):
"""使用缓存姿态更新相机 - 符合OpenVR时序假设"""
try:
# 使用缓存的姿态符合OpenVR的时序假设
# OpenVR假设你用上一次WaitGetPoses的姿态渲染当前提交的帧
if not self._cached_render_poses:
# 如果没有缓存姿态,回退到当前姿态(首帧情况)
print("⚠️ 没有缓存姿态,使用当前姿态")
return self._update_camera_poses()
# 从缓存姿态数组中获取HMD姿态
if len(self._cached_render_poses) > 0:
hmd_pose = self._cached_render_poses[openvr.k_unTrackedDeviceIndex_Hmd]
if hmd_pose.bPoseIsValid:
self.update_hmd(hmd_pose)
# 调试信息 - 验证缓存姿态使用
if not hasattr(self, '_cached_pose_logged'):
print("✅ 使用缓存姿态更新相机 - 符合OpenVR时序假设")
self._cached_pose_logged = True
else:
print("⚠️ 缓存的HMD姿态数据无效")
except Exception as e:
print(f"使用缓存姿态更新相机失败: {e}")
# 回退到正常更新方式
self._update_camera_poses()
import traceback
traceback.print_exc()
def enable_vr(self):
"""启用VR模式"""
if not self.is_vr_available():
print("❌ VR系统不可用")
return False
if not self.vr_initialized:
if not self.initialize_vr():
return False
self.vr_enabled = True
# 禁用主相机避免干扰VR渲染
self._disable_main_cam()
# VR性能优化使用Running Start模式Valve最佳实践
print("🚀 VR性能优化Running Start模式已启用")
print(" 优势在帧开始时获取姿态提供VSync前3ms的渲染时间")
print(" 注意Submit后立即调用WaitGetPoses是错误实现")
self.set_prediction_time(11.0) # 11ms预测时间 - OpenVR标准值平衡准确性和延迟
# 🚀 动态调整Qt Timer频率以支持VR
if hasattr(self.world, 'qtWidget') and self.world.qtWidget:
if hasattr(self.world.qtWidget, 'synchronizer'):
# 设置为144Hz让OpenVR控制实际渲染节奏
self.world.qtWidget.synchronizer.setInterval(int(1000/144))
print("✓ Qt Timer调整为144Hz让OpenVR控制VR渲染节奏")
print("✅ VR模式已启用")
return True
def disable_vr(self):
"""禁用VR模式"""
self.vr_enabled = False
# 清理手柄可视化(但保留控制器对象以便重新启用)
if hasattr(self, 'left_controller') and self.left_controller:
if hasattr(self.left_controller, 'visualizer') and self.left_controller.visualizer:
try:
self.left_controller.visualizer.cleanup()
self.left_controller.visualizer = None
print("✓ 左手控制器可视化已清理")
except Exception as e:
print(f"⚠️ 清理左手控制器可视化失败: {e}")
if hasattr(self, 'right_controller') and self.right_controller:
if hasattr(self.right_controller, 'visualizer') and self.right_controller.visualizer:
try:
self.right_controller.visualizer.cleanup()
self.right_controller.visualizer = None
print("✓ 右手控制器可视化已清理")
except Exception as e:
print(f"⚠️ 清理右手控制器可视化失败: {e}")
# 隐藏手柄锚点节点
if hasattr(self, 'left_controller') and self.left_controller and self.left_controller.anchor_node:
self.left_controller.anchor_node.hide()
if hasattr(self, 'right_controller') and self.right_controller and self.right_controller.anchor_node:
self.right_controller.anchor_node.hide()
# 恢复主相机
self._enable_main_cam()
# 恢复Qt Timer到60FPS
if hasattr(self.world, 'qtWidget') and self.world.qtWidget:
if hasattr(self.world.qtWidget, 'synchronizer'):
self.world.qtWidget.synchronizer.setInterval(int(1000/60))
print("✓ Qt Timer恢复为60Hz")
print("✅ VR模式已禁用手柄模型已隐藏")
def cleanup(self):
"""清理VR资源"""
try:
print("🔄 正在清理VR资源...")
# 停止VR任务
if self.vr_task:
self.world.taskMgr.remove(self.vr_task)
self.vr_task = None
# 🚀 恢复Python垃圾回收并清理对象池
if self._gc_disabled:
# 最后一次手动垃圾回收
collected = gc.collect()
print(f"🗑️ 最终GC清理: {collected} 个对象")
# 恢复自动垃圾回收
gc.enable()
self._gc_disabled = False
print("✅ Python垃圾回收已恢复为自动模式")
# 清理对象池
if hasattr(self, '_matrix_pool'):
pool_size = len(self._matrix_pool)
self._matrix_pool.clear()
print(f"🧹 Mat4对象池已清理: {pool_size} 个对象")
# 清理缓存
if hasattr(self, '_cached_matrices'):
self._cached_matrices.clear()
if hasattr(self, '_controller_poses_cache'):
self._controller_poses_cache.clear()
# 清理OpenVR Texture对象缓存
if hasattr(self, '_left_ovr_texture'):
self._left_ovr_texture = None
if hasattr(self, '_right_ovr_texture'):
self._right_ovr_texture = None
print("🧹 OpenVR Texture对象缓存已清理")
# 清理渲染缓冲区
if self.vr_left_eye_buffer:
self.vr_left_eye_buffer.removeAllDisplayRegions()
self.world.graphicsEngine.removeWindow(self.vr_left_eye_buffer)
self.vr_left_eye_buffer = None
if self.vr_right_eye_buffer:
self.vr_right_eye_buffer.removeAllDisplayRegions()
self.world.graphicsEngine.removeWindow(self.vr_right_eye_buffer)
self.vr_right_eye_buffer = None
# 清理相机
if self.vr_left_camera:
self.vr_left_camera.removeNode()
self.vr_left_camera = None
if self.vr_right_camera:
self.vr_right_camera.removeNode()
self.vr_right_camera = None
# 清理控制器
if hasattr(self, 'left_controller') and self.left_controller:
try:
self.left_controller.cleanup()
self.left_controller = None
print("✓ 左手控制器已清理")
except Exception as e:
print(f"⚠️ 清理左手控制器失败: {e}")
if hasattr(self, 'right_controller') and self.right_controller:
try:
self.right_controller.cleanup()
self.right_controller = None
print("✓ 右手控制器已清理")
except Exception as e:
print(f"⚠️ 清理右手控制器失败: {e}")
# 清理控制器字典
if hasattr(self, 'controllers'):
self.controllers.clear()
print("✓ 控制器字典已清理")
# 清理VR子系统
if hasattr(self, 'joystick_manager') and self.joystick_manager:
try:
self.joystick_manager.cleanup()
self.joystick_manager = None
print("✓ VR摇杆系统已清理")
except Exception as e:
print(f"⚠️ 清理VR摇杆系统失败: {e}")
if hasattr(self, 'teleport_system') and self.teleport_system:
try:
self.teleport_system.cleanup()
self.teleport_system = None
print("✓ VR传送系统已清理")
except Exception as e:
print(f"⚠️ 清理VR传送系统失败: {e}")
if hasattr(self, 'interaction_manager') and self.interaction_manager:
try:
self.interaction_manager.cleanup()
self.interaction_manager = None
print("✓ VR交互系统已清理")
except Exception as e:
print(f"⚠️ 清理VR交互系统失败: {e}")
if hasattr(self, 'action_manager') and self.action_manager:
try:
self.action_manager.cleanup()
self.action_manager = None
print("✓ VR动作系统已清理")
except Exception as e:
print(f"⚠️ 清理VR动作系统失败: {e}")
# 关闭OpenVR
if self.vr_system and OPENVR_AVAILABLE:
try:
openvr.shutdown()
except:
pass
self.vr_system = None
self.vr_enabled = False
self.vr_initialized = False
print("✅ VR资源清理完成")
except Exception as e:
print(f"⚠️ VR清理过程中出错: {e}")
def get_vr_status(self):
"""获取VR状态信息"""
return {
'available': self.is_vr_available(),
'initialized': self.vr_initialized,
'enabled': self.vr_enabled,
'eye_resolution': (self.eye_width, self.eye_height),
'device_count': len(self.controller_poses) + (1 if self.vr_enabled else 0),
'vr_fps': self.vr_fps,
'frame_count': self.frame_count,
'submit_failures': self.submit_failures,
'pose_failures': self.pose_failures
}
def _print_performance_report(self):
"""输出VR性能报告"""
if not self.performance_monitoring or not self.debug_output_enabled:
return
stats = self.get_performance_stats()
# 简短模式输出
if self.debug_mode == 'brief':
self._print_brief_performance_report(stats)
return
print("📊 ======= VR性能监控报告 =======")
# 帧率和帧时间信息
print(f"🎯 渲染性能:")
print(f" VR帧率: {stats['vr_fps']:.1f} FPS")
print(f" 平均帧时间: {stats['frame_time_avg']:.2f} ms")
print(f" 最小帧时间: {stats['frame_time_min']:.2f} ms")
print(f" 最大帧时间: {stats['frame_time_max']:.2f} ms")
print(f" 95%帧时间: {stats['frame_time_95th']:.2f} ms")
# 系统性能
print(f"💻 系统性能:")
print(f" CPU使用率: {stats['cpu_usage']:.1f}%")
print(f" 内存使用率: {stats['memory_usage']:.1f}%")
# GPU性能
print(f"🎮 GPU性能:")
if self.gputil_available or self.nvidia_ml_available:
print(f" GPU使用率: {stats['gpu_usage']:.1f}%")
print(f" 显存使用率: {stats['gpu_memory_usage']:.1f}%")
else:
print(f" GPU监控: 不可用 (需要安装 GPUtil 或 pynvml)")
print(f" 安装命令: pip install GPUtil nvidia-ml-py")
# GPU渲染时间OpenVR Frame Timing
if self.enable_gpu_timing:
print(f"⚡ GPU渲染时间:")
pipeline_stats = self._get_pipeline_stats()
gpu_stats = pipeline_stats.get('gpu_timing', {})
gpu_current = pipeline_stats.get('current', {})
# 检查是否有可用的GPU时间数据
has_gpu_data = any(
gpu_current.get(field, 0) > 0
for field in ['gpu_scene_render', 'gpu_total_render', 'gpu_pre_submit', 'gpu_post_submit', 'gpu_compositor_render']
)
if has_gpu_data:
# 显示GPU时间统计最近30帧平均
scene_render = gpu_stats.get('scene_render', {'avg': 0})
total_render = gpu_stats.get('total_render', {'avg': 0})
pre_submit = gpu_stats.get('pre_submit', {'avg': 0})
post_submit = gpu_stats.get('post_submit', {'avg': 0})
compositor = gpu_stats.get('compositor_render', {'avg': 0})
if scene_render['avg'] > 0:
print(f" GPU场景渲染: {scene_render['avg']:.2f}ms (min:{scene_render['min']:.1f}, max:{scene_render['max']:.1f})")
if total_render['avg'] > 0:
print(f" GPU总渲染时间: {total_render['avg']:.2f}ms (min:{total_render['min']:.1f}, max:{total_render['max']:.1f})")
if pre_submit['avg'] > 0:
print(f" GPU提交前时间: {pre_submit['avg']:.2f}ms (min:{pre_submit['min']:.1f}, max:{pre_submit['max']:.1f})")
if post_submit['avg'] > 0:
print(f" GPU提交后时间: {post_submit['avg']:.2f}ms (min:{post_submit['min']:.1f}, max:{post_submit['max']:.1f})")
if compositor['avg'] > 0:
print(f" GPU合成器时间: {compositor['avg']:.2f}ms (min:{compositor['min']:.1f}, max:{compositor['max']:.1f})")
# 显示当前帧GPU时间
print(f"🔍 当前帧GPU时间:")
if gpu_current.get('gpu_scene_render', 0) > 0:
print(f" 场景渲染: {gpu_current['gpu_scene_render']:.2f}ms")
if gpu_current.get('gpu_total_render', 0) > 0:
print(f" 总渲染: {gpu_current['gpu_total_render']:.2f}ms")
if gpu_current.get('gpu_compositor_render', 0) > 0:
print(f" 合成器: {gpu_current['gpu_compositor_render']:.2f}ms")
# GPU时间瓶颈分析
current_total = gpu_current.get('gpu_total_render', 0)
current_scene = gpu_current.get('gpu_scene_render', 0)
if current_total > 12.0: # 假设72fps目标留出一些余量
print(f" ⚠️ GPU总渲染时间过长: {current_total:.1f}ms")
elif current_scene > 8.0:
print(f" ⚠️ GPU场景渲染时间偏高: {current_scene:.1f}ms")
else:
print(f" GPU渲染时间: 暂无数据")
if self.gpu_timing_failure_count > 0:
print(f" 获取失败次数: {self.gpu_timing_failure_count}")
else:
print(f" 等待OpenVR Frame Timing数据...")
else:
print(f"⚡ GPU渲染时间: 已禁用")
# VR特定指标
print(f"🥽 VR指标:")
print(f" 总帧数: {stats['frame_count']}")
print(f" 提交失败: {stats['submit_failures']}")
print(f" 姿态失败: {stats['pose_failures']}")
# 计算失败率
if stats['frame_count'] > 0:
submit_fail_rate = (stats['submit_failures'] / stats['frame_count']) * 100
pose_fail_rate = (stats['pose_failures'] / stats['frame_count']) * 100
print(f" 提交失败率: {submit_fail_rate:.2f}%")
print(f" 姿态失败率: {pose_fail_rate:.2f}%")
# 渲染管线监控
if self.enable_pipeline_monitoring:
pipeline_stats = self._get_pipeline_stats()
print(f"⚙️ 渲染管线分析:")
print(f" 当前分辨率: {pipeline_stats['vr_info']['eye_resolution'][0]}x{pipeline_stats['vr_info']['eye_resolution'][1]}")
print(f" 推荐分辨率: {pipeline_stats['vr_info']['recommended_resolution'][0]}x{pipeline_stats['vr_info']['recommended_resolution'][1]}")
print(f" 显示频率: {pipeline_stats['vr_info']['display_frequency']:.1f} Hz")
# VSync和时序信息
if pipeline_stats['vr_info']['target_frame_time_ms'] > 0:
print(f"🎯 VSync时序:")
print(f" 目标帧时间: {pipeline_stats['vr_info']['target_frame_time_ms']:.2f}ms")
print(f" VSync到光子: {pipeline_stats['vr_info']['vsync_to_photons_ms']:.2f}ms")
print(f" VSync窗口: ±{pipeline_stats['vr_info']['vsync_window_ms']:.2f}ms")
print(f" 异步重投影: {'启用' if pipeline_stats['vr_info']['async_reprojection'] else '禁用'}")
print(f" 运动平滑: {'启用' if pipeline_stats['vr_info']['motion_smoothing'] else '禁用'}")
# 分析帧时间是否在目标范围内
current_frame_time = stats['frame_time_avg']
target_frame_time = pipeline_stats['vr_info']['target_frame_time_ms']
if current_frame_time > 0 and target_frame_time > 0:
frame_time_ratio = current_frame_time / target_frame_time
if frame_time_ratio > 1.1:
print(f" ⚠️ 帧时间超标: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)")
elif frame_time_ratio < 0.9:
print(f" ✅ 帧时间充裕: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)")
else:
print(f" ✓ 帧时间正常: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)")
# 🔧 性能优化诊断
print(f"🔧 优化诊断:")
current = pipeline_stats.get('current', {}) if self.enable_pipeline_monitoring else {}
# 🔍 渲染回调诊断 - 新增
self._print_render_callback_diagnostics()
# waitGetPoses时序分析
wait_poses_time = current.get('wait_poses', 0)
if wait_poses_time > 10:
print(f" ⚠️ waitGetPoses时间过长: {wait_poses_time:.1f}ms")
print(f" 可能原因: 错过VSync窗口建议调整预测时间")
elif wait_poses_time > 5:
print(f" ⚠️ waitGetPoses时间偏高: {wait_poses_time:.1f}ms")
else:
print(f" ✓ waitGetPoses时间正常: {wait_poses_time:.1f}ms")
# CPU-GPU并行度分析 - 增强诊断
gpu_total = current.get('gpu_total_render', 0)
cpu_render_left = getattr(self, 'left_render_time', 0)
cpu_render_right = getattr(self, 'right_render_time', 0)
cpu_render_total = cpu_render_left + cpu_render_right
print(f" 真实渲染时间对比:")
print(f" CPU左眼: {cpu_render_left:.2f}ms")
print(f" CPU右眼: {cpu_render_right:.2f}ms")
print(f" CPU总计: {cpu_render_total:.2f}ms")
print(f" GPU总计: {gpu_total:.2f}ms")
if cpu_render_total < 1.0:
print(f" ⚠️ CPU渲染时间异常短: {cpu_render_total:.2f}ms")
print(f" 可能原因: 渲染回调未正确执行或渲染被跳过")
elif gpu_total > 0 and cpu_render_total > 0:
ratio = gpu_total / cpu_render_total
if ratio > 10:
print(f" ⚠️ GPU严重等待CPU: 比例{ratio:.1f}:1")
print(f" 建议: 检查OpenGL命令提交时机和渲染状态")
elif ratio > 3:
print(f" ⚠️ GPU等待CPU: 比例{ratio:.1f}:1")
print(f" 建议: 检查OpenGL命令提交是否及时")
else:
print(f" ✓ CPU-GPU时间匹配: 比例{ratio:.1f}:1")
# 预测时间诊断
current_prediction = self.use_prediction_time * 1000
print(f" 预测时间设置: {current_prediction:.1f}ms")
if current_prediction > 15:
print(f" 建议: 预测时间较高可能增加waitGetPoses延迟")
elif current_prediction < 8:
print(f" 注意: 预测时间较低,可能影响姿态准确性")
else:
print(f" ✓ 预测时间在合理范围内")
# 优化状态总结
optimization_score = 0
if wait_poses_time < 8:
optimization_score += 1
if stats['vr_fps'] > 60:
optimization_score += 1
if stats.get('frame_time_avg', 0) < target_frame_time * 1.1:
optimization_score += 1
if optimization_score >= 2:
print(f" ✅ 优化效果良好 ({optimization_score}/3)")
else:
print(f" ⚠️ 仍有优化空间 ({optimization_score}/3)")
print(f"🕐 各阶段耗时 (最近{self.pipeline_history_size}帧平均):")
print(f" waitGetPoses: {pipeline_stats['wait_poses']['avg']:.2f}ms (min:{pipeline_stats['wait_poses']['min']:.1f}, max:{pipeline_stats['wait_poses']['max']:.1f})")
print(f" 渲染总计: {pipeline_stats['render']['avg']:.2f}ms (min:{pipeline_stats['render']['min']:.1f}, max:{pipeline_stats['render']['max']:.1f})")
print(f" 纹理提交: {pipeline_stats['submit']['avg']:.2f}ms (min:{pipeline_stats['submit']['min']:.1f}, max:{pipeline_stats['submit']['max']:.1f})")
if pipeline_stats['sync_wait']['avg'] > 0:
print(f" 同步等待: {pipeline_stats['sync_wait']['avg']:.2f}ms (min:{pipeline_stats['sync_wait']['min']:.1f}, max:{pipeline_stats['sync_wait']['max']:.1f})")
print(f"🔍 当前帧详情:")
print(f" 左眼渲染: {pipeline_stats['current']['left_render']:.2f}ms")
print(f" 右眼渲染: {pipeline_stats['current']['right_render']:.2f}ms")
print(f" 姿态获取: {pipeline_stats['current']['wait_poses']:.2f}ms")
# 显示Running Start模式信息
print(f"🎯 Running Start模式:")
print(f" 模式: Valve Running Start - 帧开始时获取姿态")
print(f" 优势: VSync前3ms获取姿态提供充足渲染时间")
print(f" 预测时间: {self.use_prediction_time * 1000:.1f}ms")
# 分析最大瓶颈
current = pipeline_stats['current']
bottleneck_analysis = []
if current['wait_poses'] > 5.0:
bottleneck_analysis.append(f"姿态获取耗时过长({current['wait_poses']:.1f}ms)")
if current['total_render'] > 8.0:
bottleneck_analysis.append(f"渲染耗时过长({current['total_render']:.1f}ms)")
if current['submit'] > 2.0:
bottleneck_analysis.append(f"纹理提交耗时过长({current['submit']:.1f}ms)")
if bottleneck_analysis:
print(f"🚨 瓶颈分析:")
for analysis in bottleneck_analysis:
print(f" ⚠️ {analysis}")
# 性能建议
self._print_performance_recommendations(stats)
print("===============================")
def _print_performance_recommendations(self, stats):
"""根据性能数据输出优化建议"""
print(f"💡 性能建议:")
recommendations = []
# FPS相关建议
if stats['vr_fps'] < 60:
recommendations.append(" ⚠️ VR帧率过低可能影响体验")
# 帧时间相关建议
if stats['frame_time_avg'] > 16.7: # 60fps = 16.7ms
recommendations.append(" ⚠️ 平均帧时间过高,建议降低渲染质量")
if stats['frame_time_max'] > 50:
recommendations.append(" ⚠️ 检测到严重卡顿检查CPU/GPU负载")
# CPU建议
if stats['cpu_usage'] > 80:
recommendations.append(" 🔴 CPU使用率过高可能存在CPU瓶颈")
elif stats['cpu_usage'] > 60:
recommendations.append(" 🟡 CPU使用率偏高注意监控")
# 内存建议
if stats['memory_usage'] > 85:
recommendations.append(" 🔴 内存使用率过高,可能影响性能")
# GPU建议
if self.gputil_available or self.nvidia_ml_available:
if stats['gpu_usage'] > 95:
recommendations.append(" 🔴 GPU使用率接近满载存在GPU瓶颈")
if stats['gpu_memory_usage'] > 90:
recommendations.append(" 🔴 显存使用率过高,可能需要降低纹理质量")
# GPU渲染时间建议
if self.enable_gpu_timing:
if self.gpu_total_render_ms > 12.0:
recommendations.append(" ⚠️ GPU总渲染时间过长建议优化场景复杂度")
if self.gpu_scene_render_ms > 8.0:
recommendations.append(" ⚠️ GPU场景渲染时间偏高考虑降低渲染质量")
if self.gpu_compositor_render_ms > 3.0:
recommendations.append(" ⚠️ GPU合成器时间过长检查VR设置或叠加层")
if self.gpu_timing_failure_count > 100:
recommendations.append(" ⚠️ GPU时间统计频繁失败可能需要更新OpenVR")
# 失败率建议
submit_fail_rate = (stats['submit_failures'] / max(stats['frame_count'], 1)) * 100
if submit_fail_rate > 1:
recommendations.append(" ⚠️ VR帧提交失败率较高检查VR系统状态")
if not recommendations:
recommendations.append(" ✅ 性能表现良好,无明显问题")
for rec in recommendations:
print(rec)
def _print_brief_performance_report(self, stats):
"""输出简短的VR性能报告"""
# 创建一行简短摘要
summary = f"🥽 VR性能: {stats['vr_fps']:.1f}fps"
if stats['frame_time_avg'] > 0:
summary += f" | 帧时间: {stats['frame_time_avg']:.1f}ms"
if self.psutil_available:
summary += f" | CPU: {stats['cpu_usage']:.0f}%"
summary += f" | 内存: {stats['memory_usage']:.0f}%"
# 显示GPU信息如果库不可用则显示提示
if self.gputil_available or self.nvidia_ml_available:
summary += f" | GPU: {stats['gpu_usage']:.0f}%"
else:
summary += " | GPU: N/A"
# 添加失败率指示
if stats['frame_count'] > 0:
submit_fail_rate = (stats['submit_failures'] / stats['frame_count']) * 100
if submit_fail_rate > 0.1:
summary += f" | 提交失败: {submit_fail_rate:.1f}%"
# 添加管线关键信息
if self.enable_pipeline_monitoring:
pipeline_stats = self._get_pipeline_stats()
current = pipeline_stats['current']
# 显示关键瓶颈
if current['wait_poses'] > 5.0:
summary += f" | 姿态: {current['wait_poses']:.1f}ms⚠"
elif current['wait_poses'] > 0:
summary += f" | 姿态: {current['wait_poses']:.1f}ms"
if current['total_render'] > 8.0:
summary += f" | 渲染: {current['total_render']:.1f}ms⚠"
elif current['total_render'] > 0:
summary += f" | 渲染: {current['total_render']:.1f}ms"
# 添加GPU渲染时间信息
if self.enable_gpu_timing:
gpu_total = current.get('gpu_total_render', 0)
gpu_scene = current.get('gpu_scene_render', 0)
if gpu_total > 12.0:
summary += f" | GPU: {gpu_total:.1f}ms⚠"
elif gpu_total > 0:
summary += f" | GPU: {gpu_total:.1f}ms"
elif gpu_scene > 0:
summary += f" | GPU场景: {gpu_scene:.1f}ms"
# 显示目标帧时间对比
vr_info = pipeline_stats['vr_info']
if vr_info['target_frame_time_ms'] > 0:
target = vr_info['target_frame_time_ms']
current_avg = stats['frame_time_avg']
if current_avg > target * 1.1:
summary += f" | 目标:{target:.0f}ms⚠"
else:
summary += f" | 目标:{target:.0f}ms"
# 性能状态指示
if stats['vr_fps'] < 72:
summary += " ⚠️"
elif stats['vr_fps'] > 85:
summary += ""
print(summary)
# 注意原来的left_cb和right_cb函数已被删除
# 它们的功能已集成到_handle_vr_rendering_and_submit方法中
# 注意_safe_submit_texture方法已删除
# VR纹理提交现在完全由Panda3D的renderFrame()自动处理
def submit_texture(self, eye, texture):
"""优化的VR纹理提交 - 使用缓存的纹理ID避免重复prepareNow"""
try:
if not self.vr_compositor:
print("❌ VR compositor不可用")
self.submit_failures += 1
return
# 🚀 关键修复:防止同一帧重复提交
current_frame = getattr(self, 'frame_count', 0)
if not hasattr(self, '_last_submit_frame'):
self._last_submit_frame = {}
# 检查当前帧是否已经提交过此眼睛的纹理
if eye in self._last_submit_frame and self._last_submit_frame[eye] == current_frame:
# 静默跳过避免spam输出
return
# 记录当前帧提交
self._last_submit_frame[eye] = current_frame
# 🚀 关键优化直接使用缓存的纹理ID避免重复prepareNow
if eye == openvr.Eye_Left:
handle = self.left_texture_id
eye_name = "左眼"
elif eye == openvr.Eye_Right:
handle = self.right_texture_id
eye_name = "右眼"
else:
print(f"❌ 未知的眼睛类型: {eye}")
self.submit_failures += 1
return
# 检查缓存的纹理ID是否有效
if not handle or handle <= 0:
print(f"{eye_name}纹理ID缓存无效: {handle}")
print(" 这可能表示纹理准备失败需要检查_prepare_and_cache_textures()")
self.submit_failures += 1
return
# ❌ 移除gsg.flush()调用 - 基于OpenVR官方实践
# gsg.flush()等同于OpenGL的glFlush()导致强制CPU-GPU同步
# 每帧调用2次左右眼是GPU周期性峰值的主要原因
# 参考OpenVR社区经验表明同步调用是性能杀手
#
# gsg = self.world.win.getGsg()
# if gsg and hasattr(gsg, 'flush'):
# try:
# gsg.flush()
# except Exception as flush_error:
# if not hasattr(self, '_gsg_flush_error_logged'):
# print(f"⚠️ GSG刷新失败: {flush_error}")
# self._gsg_flush_error_logged = True
# 🚀 关键优化使用缓存的OpenVR Texture对象避免每帧创建
if eye == openvr.Eye_Left:
ovr_texture = self._left_ovr_texture
else:
ovr_texture = self._right_ovr_texture
# 检查缓存对象是否存在(向后兼容)
if ovr_texture is None:
# 备用方案:如果缓存对象不存在,创建新的(性能较差)
ovr_texture = openvr.Texture_t()
ovr_texture.eType = openvr.TextureType_OpenGL
ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
if not hasattr(self, '_texture_fallback_warned'):
print("⚠️ 使用Texture对象备用方案性能次优")
self._texture_fallback_warned = True
# 只更新handle其他属性已预设置
ovr_texture.handle = handle
# 提交到VR系统
error = self.vr_compositor.submit(eye, ovr_texture)
# 检查错误
if error and error != openvr.VRCompositorError_None:
print(f"⚠️ VR{eye_name}纹理提交错误: {error}")
self.submit_failures += 1
else:
# 只在第一次成功时输出
if not hasattr(self, '_optimized_submit_success_logged'):
print(f"✅ 优化版VR纹理提交成功! 使用缓存ID避免重复prepareNow")
print(f" {eye_name}纹理ID: {handle}")
self._optimized_submit_success_logged = True
# 🔧 智能GPU同步策略 - 减少不必要的flush调用
# 只有在检测到性能问题或每N帧时才强制flush
if eye == openvr.Eye_Right:
self._smart_gpu_sync()
except Exception as e:
print(f"❌ VR纹理提交异常: {e}")
import traceback
traceback.print_exc()
self.submit_failures += 1
def _smart_gpu_sync(self):
"""智能GPU同步策略 - 只在必要时进行同步"""
try:
# 初始化同步控制变量
if not hasattr(self, '_last_gpu_sync_frame'):
self._last_gpu_sync_frame = 0
self._gpu_sync_interval = 60 # 每60帧强制同步一次1秒@60FPS
self._performance_based_sync = True
current_frame = getattr(self, 'frame_count', 0)
# 策略1: 性能自适应同步
if self._performance_based_sync:
# 如果渲染时间过长,增加同步频率
total_render_time = getattr(self, 'left_render_time', 0) + getattr(self, 'right_render_time', 0)
if total_render_time > 15.0: # 如果总渲染时间超过15ms
# 高负载时更频繁同步
sync_interval = 30 # 每30帧同步
elif total_render_time > 10.0:
sync_interval = 45 # 中等负载
else:
sync_interval = self._gpu_sync_interval # 正常负载
should_sync = (current_frame - self._last_gpu_sync_frame) >= sync_interval
else:
# 策略2: 固定间隔同步
should_sync = (current_frame - self._last_gpu_sync_frame) >= self._gpu_sync_interval
# 执行同步
if should_sync:
gsg = self.world.win.getGsg()
if gsg and hasattr(gsg, 'flush'):
try:
gsg.flush()
self._last_gpu_sync_frame = current_frame
# 只在首次或Debug时输出
if not hasattr(self, '_smart_sync_logged') or self.debug_output_enabled:
if not hasattr(self, '_smart_sync_logged'):
print(f"🔧 智能GPU同步已启用 - 间隔:{sync_interval}")
self._smart_sync_logged = True
elif self.debug_output_enabled and current_frame % 600 == 1:
print(f"🔧 智能同步触发 (帧#{current_frame}, 间隔:{sync_interval})")
except Exception as flush_error:
if not hasattr(self, '_smart_sync_error_logged'):
print(f"⚠️ 智能GPU同步失败: {flush_error}")
self._smart_sync_error_logged = True
except Exception as e:
print(f"⚠️ 智能GPU同步策略异常: {e}")
def _disable_main_cam(self):
"""禁用主相机 - 基于参考实现"""
try:
# 保存原始相机状态
if not hasattr(self, '_original_camera_parent'):
self._original_camera_parent = self.world.camera.getParent()
# 创建空节点并将主相机重新附加到它
self._empty_world = NodePath("empty_world")
self.world.camera.reparentTo(self._empty_world)
print("✓ 主相机已禁用")
except Exception as e:
print(f"⚠️ 禁用主相机失败: {e}")
def _enable_main_cam(self):
"""恢复主相机 - 基于参考实现"""
try:
# 恢复原始相机状态
if hasattr(self, '_original_camera_parent') and self._original_camera_parent:
self.world.camera.reparentTo(self._original_camera_parent)
else:
# 如果没有保存的父节点重新附加到render
self.world.camera.reparentTo(self.world.render)
# 清理空世界节点
if hasattr(self, '_empty_world'):
self._empty_world.removeNode()
delattr(self, '_empty_world')
print("✓ 主相机已恢复")
except Exception as e:
print(f"⚠️ 恢复主相机失败: {e}")
def _initialize_controllers(self):
"""初始化VR手柄控制器"""
try:
print("🎮 正在初始化VR手柄控制器...")
# 创建左右手柄控制器实例
self.left_controller = LeftController(self)
self.right_controller = RightController(self)
# 检测现有连接的控制器
self._detect_controllers()
print("✓ VR手柄控制器初始化完成")
except Exception as e:
print(f"⚠️ VR手柄初始化失败: {e}")
import traceback
traceback.print_exc()
def _detect_controllers(self):
"""检测并连接VR控制器"""
if not self.vr_system:
return
try:
for device_index in range(openvr.k_unMaxTrackedDeviceCount):
# 检查设备是否已连接
if not self.vr_system.isTrackedDeviceConnected(device_index):
continue
# 获取设备类型
device_class = self.vr_system.getTrackedDeviceClass(device_index)
if device_class == openvr.TrackedDeviceClass_Controller:
# 获取控制器角色
role = self.vr_system.getControllerRoleForTrackedDeviceIndex(device_index)
if role == openvr.TrackedControllerRole_LeftHand and self.left_controller:
self.left_controller.set_device_index(device_index)
self.controllers[device_index] = self.left_controller
# 为设备创建锚点
self._create_tracked_device_anchor(device_index, 'left_controller')
elif role == openvr.TrackedControllerRole_RightHand and self.right_controller:
self.right_controller.set_device_index(device_index)
self.controllers[device_index] = self.right_controller
# 为设备创建锚点
self._create_tracked_device_anchor(device_index, 'right_controller')
print(f"🎮 检测到 {len(self.controllers)} 个控制器")
except Exception as e:
print(f"⚠️ 控制器检测失败: {e}")
def _create_tracked_device_anchor(self, device_index, name):
"""为跟踪设备创建锚点节点"""
if not self.tracking_space:
print(f"⚠️ 无法为设备 {device_index} 创建锚点 - tracking_space未初始化")
return
try:
# 获取设备模型名称
if self.vr_system:
model_name = self.vr_system.getStringTrackedDeviceProperty(
device_index,
openvr.Prop_RenderModelName_String
)
anchor_name = f"{device_index}:{model_name}:{name}"
else:
anchor_name = f"{device_index}:{name}"
# 创建锚点节点
device_anchor = self.tracking_space.attachNewNode(anchor_name)
self.tracked_device_anchors[device_index] = device_anchor
print(f"✓ 为设备 {device_index} 创建锚点: {anchor_name}")
except Exception as e:
print(f"⚠️ 创建设备锚点失败: {e}")
def update_tracked_devices(self):
"""更新所有跟踪设备的姿态 - 基于参考实现"""
if not self.poses or not self.vr_system:
return
try:
# 更新每个已连接的控制器
for device_index, controller in self.controllers.items():
if device_index < len(self.poses):
pose_data = self.poses[device_index]
# 更新控制器姿态
controller.update_pose(pose_data)
# 更新控制器输入状态
controller.update_input_state(self.vr_system)
# 更新其他跟踪设备的锚点
for device_index in range(1, min(len(self.poses), openvr.k_unMaxTrackedDeviceCount)):
if device_index in self.tracked_device_anchors:
pose_data = self.poses[device_index]
if pose_data.bPoseIsValid:
# 转换姿态矩阵
modelview = self.convert_mat(pose_data.mDeviceToAbsoluteTracking)
final_matrix = self.coord_mat_inv * modelview * self.coord_mat
# 更新锚点变换
anchor = self.tracked_device_anchors[device_index]
anchor.setMat(final_matrix)
anchor.show()
else:
# 姿态无效,隐藏锚点
self.tracked_device_anchors[device_index].hide()
except Exception as e:
if self.frame_count % 300 == 0: # 每5秒输出一次错误
print(f"⚠️ 更新跟踪设备失败: {e}")
def get_controller_by_role(self, role):
"""根据角色获取控制器
Args:
role: 'left''right'
Returns:
VRController实例或None
"""
if role == 'left':
return self.left_controller
elif role == 'right':
return self.right_controller
return None
def are_controllers_connected(self):
"""检查是否有控制器连接"""
return len(self.controllers) > 0
def get_connected_controllers(self):
"""获取所有连接的控制器列表"""
return list(self.controllers.values())
def trigger_controller_haptic(self, role, duration=0.001, strength=1.0):
"""触发控制器震动反馈
Args:
role: 'left', 'right''both'
duration: 震动持续时间(秒)
strength: 震动强度 (0.0-1.0)
"""
if role in ['left', 'both'] and self.left_controller:
self.left_controller.trigger_haptic_feedback(duration, strength)
if role in ['right', 'both'] and self.right_controller:
self.right_controller.trigger_haptic_feedback(duration, strength)
# VR动作系统便捷方法
def is_trigger_pressed(self, hand='any'):
"""检查扳机是否被按下
Args:
hand: 'left', 'right', 'any'
"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('trigger', device_path)
return pressed
def is_trigger_just_pressed(self, hand='any'):
"""检查扳机是否刚刚被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_just_pressed('trigger', device_path)
return pressed
def is_grip_pressed(self, hand='any'):
"""检查握把是否被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('grip', device_path)
return pressed
def is_grip_just_pressed(self, hand='any'):
"""检查握把是否刚刚被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_just_pressed('grip', device_path)
return pressed
def is_menu_pressed(self, hand='any'):
"""检查菜单按钮是否被按下"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
pressed, _ = self.action_manager.is_digital_action_pressed('menu', device_path)
return pressed
def is_trackpad_touched(self, hand='any'):
"""检查触摸板是否被触摸"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
touched, _ = self.action_manager.is_digital_action_pressed('trackpad_touch', device_path)
return touched
def get_trackpad_position(self, hand='any'):
"""获取触摸板位置
Returns:
Vec2或None: 触摸板位置 (-1到1的范围)
"""
device_path = None
if hand == 'left':
device_path = '/user/hand/left'
elif hand == 'right':
device_path = '/user/hand/right'
value, _ = self.action_manager.get_analog_action_value('trackpad', device_path)
return value
# VR交互系统便捷方法
def get_selected_object(self, hand='any'):
"""获取指定手选中的对象
Args:
hand: 'left', 'right', 'any'
Returns:
选中的对象节点或None
"""
if hand == 'any':
# 返回任意手选中的对象
for controller in self.get_connected_controllers():
selected = self.interaction_manager.get_selected_object(controller.name)
if selected:
return selected
return None
else:
return self.interaction_manager.get_selected_object(hand)
def get_grabbed_object(self, hand='any'):
"""获取指定手抓取的对象
Args:
hand: 'left', 'right', 'any'
Returns:
抓取的对象节点或None
"""
if hand == 'any':
# 返回任意手抓取的对象
for controller in self.get_connected_controllers():
grabbed = self.interaction_manager.get_grabbed_object(controller.name)
if grabbed:
return grabbed
return None
else:
return self.interaction_manager.get_grabbed_object(hand)
def is_grabbing_object(self, hand='any'):
"""检查是否正在抓取对象
Args:
hand: 'left', 'right', 'any'
Returns:
bool: 是否正在抓取
"""
if hand == 'any':
# 检查任意手是否正在抓取
for controller in self.get_connected_controllers():
if self.interaction_manager.is_grabbing(controller.name):
return True
return False
else:
return self.interaction_manager.is_grabbing(hand)
def force_release_all_grabs(self):
"""强制释放所有抓取的对象"""
self.interaction_manager.force_release_all()
def add_interactable_object(self, object_node):
"""将对象标记为可交互
Args:
object_node: 要标记的对象节点
"""
self.interaction_manager._add_collision_to_object(object_node)
def _disable_async_reprojection(self):
"""禁用异步重投影 - 备选修复方案"""
try:
# 尝试通过OpenVR设置禁用异步重投影
if hasattr(openvr, 'VRSettings'):
settings = openvr.VRSettings()
if settings:
# 禁用异步重投影
error = settings.setBool("steamvr", "enableAsyncReprojection", False)
if error == openvr.VRSettingsError_None:
print("✅ 异步重投影已禁用")
else:
print(f"⚠️ 禁用异步重投影失败: 设置错误 {error}")
else:
print("⚠️ 无法获取VR设置接口")
else:
print("⚠️ OpenVR设置接口不可用")
except Exception as e:
print(f"⚠️ 禁用异步重投影失败: {e}")
def enable_async_reprojection_disable(self):
"""启用异步重投影禁用选项 - 用户可调用的方法"""
self.disable_async_reprojection = True
print("📝 异步重投影禁用选项已启用将在下次VR初始化时生效")
def disable_async_reprojection_disable(self):
"""禁用异步重投影禁用选项 - 恢复默认行为"""
self.disable_async_reprojection = False
print("📝 异步重投影禁用选项已关闭将使用默认ATW行为")
def _start_timing(self, operation_name):
"""开始计时操作 - 性能优化版本"""
if not self.enable_pipeline_monitoring or self.performance_mode_enabled:
return None
import time
# 🚀 性能优化:减少字典创建,只在必要时创建
return {
'operation': operation_name,
'start_time': time.perf_counter()
}
def _end_timing(self, timing_data):
"""结束计时并记录结果 - 性能优化版本"""
if not self.enable_pipeline_monitoring or self.performance_mode_enabled or not timing_data:
return 0.0
import time
elapsed = (time.perf_counter() - timing_data['start_time']) * 1000 # 转换为毫秒
# 保存到相应的历史记录
operation = timing_data['operation']
if operation == 'wait_poses':
self.wait_poses_time = elapsed
self.wait_poses_times.append(elapsed)
if len(self.wait_poses_times) > self.pipeline_history_size:
self.wait_poses_times.pop(0)
elif operation == 'left_render':
self.left_render_time = elapsed
elif operation == 'right_render':
self.right_render_time = elapsed
elif operation == 'submit':
self.submit_time = elapsed
self.submit_times.append(elapsed)
if len(self.submit_times) > self.pipeline_history_size:
self.submit_times.pop(0)
elif operation == 'sync_wait':
self.vr_sync_wait_time = elapsed
self.sync_wait_times.append(elapsed)
if len(self.sync_wait_times) > self.pipeline_history_size:
self.sync_wait_times.pop(0)
# 计算总渲染时间
total_render = self.left_render_time + self.right_render_time
self.render_times.append(total_render)
if len(self.render_times) > self.pipeline_history_size:
self.render_times.pop(0)
return elapsed
def _get_pipeline_stats(self):
"""获取渲染管线统计信息"""
def get_stats(times_list):
if not times_list:
return {'avg': 0.0, 'min': 0.0, 'max': 0.0}
return {
'avg': sum(times_list) / len(times_list),
'min': min(times_list),
'max': max(times_list)
}
# 计算GPU时间统计
def get_gpu_field_stats(field_name):
"""从GPU时间历史记录中提取特定字段的统计信息"""
values = []
for gpu_data in self.gpu_timing_history:
if field_name in gpu_data:
values.append(gpu_data[field_name])
return get_stats(values)
return {
'wait_poses': get_stats(self.wait_poses_times),
'render': get_stats(self.render_times),
'submit': get_stats(self.submit_times),
'sync_wait': get_stats(self.sync_wait_times),
'gpu_timing': {
'scene_render': get_gpu_field_stats('scene_render'),
'pre_submit': get_gpu_field_stats('pre_submit'),
'post_submit': get_gpu_field_stats('post_submit'),
'total_render': get_gpu_field_stats('total_render'),
'compositor_render': get_gpu_field_stats('compositor_render'),
'frame_interval': get_gpu_field_stats('frame_interval')
},
'current': {
'wait_poses': self.wait_poses_time,
'left_render': self.left_render_time,
'right_render': self.right_render_time,
'submit': self.submit_time,
'sync_wait': self.vr_sync_wait_time,
'total_render': self.left_render_time + self.right_render_time,
'gpu_scene_render': self.gpu_scene_render_ms,
'gpu_pre_submit': self.gpu_pre_submit_ms,
'gpu_post_submit': self.gpu_post_submit_ms,
'gpu_total_render': self.gpu_total_render_ms,
'gpu_compositor_render': self.gpu_compositor_render_ms,
'gpu_frame_interval': self.gpu_client_frame_interval_ms
},
'vr_info': {
'eye_resolution': self.current_eye_resolution,
'recommended_resolution': self.recommended_eye_resolution,
'display_frequency': self.vr_display_frequency,
'vsync_enabled': self.vr_vsync_enabled,
'target_frame_time_ms': self.target_frame_time_ms,
'vsync_to_photons_ms': self.vsync_to_photons_ms,
'vsync_window_ms': self.vsync_window_ms,
'async_reprojection': self.async_reprojection_enabled,
'motion_smoothing': self.motion_smoothing_enabled,
'gpu_timing_enabled': self.enable_gpu_timing,
'gpu_timing_failures': self.gpu_timing_failure_count
}
}
def set_prediction_time(self, prediction_time_ms):
"""设置预测时间仅用于update_task策略
Args:
prediction_time_ms: 预测时间单位毫秒通常8-16ms
"""
prediction_time_s = prediction_time_ms / 1000.0
if prediction_time_s < 0.005 or prediction_time_s > 0.020:
print(f"⚠️ 预测时间超出推荐范围(5-20ms): {prediction_time_ms}ms")
old_time = self.use_prediction_time * 1000
self.use_prediction_time = prediction_time_s
print(f"✓ VR预测时间已设置: {old_time:.1f}ms → {prediction_time_ms:.1f}ms")
def test_pipeline_monitoring(self):
"""测试管线监控功能 - 用于调试和验证"""
print("🔧 正在测试VR管线监控功能...")
try:
# 测试基本信息获取
print("📊 基本信息:")
print(f" 当前分辨率: {self.current_eye_resolution}")
print(f" 显示频率: {self.vr_display_frequency} Hz")
print(f" 目标帧时间: {self.target_frame_time_ms:.2f}ms")
# Running Start模式信息
print("🎯 Running Start模式:")
print(f" 预测时间: {self.use_prediction_time * 1000:.1f}ms")
print(f" 模式: Valve Running Start - 帧开始时获取姿态")
# 测试管线统计
if self.enable_pipeline_monitoring:
pipeline_stats = self._get_pipeline_stats()
print("⚙️ 管线统计:")
print(f" waitGetPoses历史: {len(self.wait_poses_times)}")
print(f" 渲染历史: {len(self.render_times)}")
print(f" 提交历史: {len(self.submit_times)}")
# 测试性能报告
print("📋 生成详细性能报告:")
self._print_performance_report()
print("✅ 管线监控功能测试完成")
else:
print("⚠️ 管线监控已禁用,无法测试统计功能")
except Exception as e:
print(f"❌ 测试管线监控功能时发生错误: {e}")
import traceback
traceback.print_exc()
def _init_performance_monitoring(self):
"""初始化性能监控库"""
self.psutil_available = False
self.gputil_available = False
self.nvidia_ml_available = False
try:
import psutil
self.psutil = psutil
self.psutil_available = True
print("✓ psutil性能监控库已加载")
except ImportError:
print("⚠️ psutil库未安装CPU和内存监控将不可用")
try:
import GPUtil
self.gputil = GPUtil
self.gputil_available = True
print("✓ GPUtil GPU监控库已加载")
except ImportError:
print("⚠️ GPUtil库未安装GPU监控将不可用")
try:
import pynvml
self.pynvml = pynvml
pynvml.nvmlInit()
self.nvidia_ml_available = True
print("✓ NVIDIA-ML GPU监控库已加载")
except ImportError:
print("⚠️ pynvml库未安装NVIDIA GPU详细监控将不可用")
except Exception as e:
print(f"⚠️ NVIDIA-ML初始化失败: {e}")
def _get_gpu_frame_timing(self, frames_ago=0):
"""获取GPU渲染时间统计
Args:
frames_ago: 获取多少帧之前的数据0表示当前帧
Returns:
dict: GPU时间数据如果获取失败返回None
"""
if not self.enable_gpu_timing or not self.vr_compositor:
return None
try:
# 调用OpenVR的getFrameTiming API
result, timing = self.vr_compositor.getFrameTiming(framesAgo=frames_ago)
if not result:
self.gpu_timing_failure_count += 1
if self.gpu_timing_failure_count % 300 == 1: # 每5秒输出一次错误
print("⚠️ OpenVR getFrameTiming调用失败")
return None
# 提取GPU时间数据单位毫秒
gpu_data = {}
# 检查timing对象是否有GPU时间相关的属性
if hasattr(timing, 'm_flSceneRenderGpuMs'):
gpu_data['scene_render'] = timing.m_flSceneRenderGpuMs
self.gpu_scene_render_ms = timing.m_flSceneRenderGpuMs
if hasattr(timing, 'm_flPreSubmitGpuMs'):
gpu_data['pre_submit'] = timing.m_flPreSubmitGpuMs
self.gpu_pre_submit_ms = timing.m_flPreSubmitGpuMs
if hasattr(timing, 'm_flPostSubmitGpuMs'):
gpu_data['post_submit'] = timing.m_flPostSubmitGpuMs
self.gpu_post_submit_ms = timing.m_flPostSubmitGpuMs
if hasattr(timing, 'm_flTotalRenderGpuMs'):
gpu_data['total_render'] = timing.m_flTotalRenderGpuMs
self.gpu_total_render_ms = timing.m_flTotalRenderGpuMs
if hasattr(timing, 'm_flCompositorRenderGpuMs'):
gpu_data['compositor_render'] = timing.m_flCompositorRenderGpuMs
self.gpu_compositor_render_ms = timing.m_flCompositorRenderGpuMs
if hasattr(timing, 'm_flClientFrameIntervalMs'):
gpu_data['frame_interval'] = timing.m_flClientFrameIntervalMs
self.gpu_client_frame_interval_ms = timing.m_flClientFrameIntervalMs
# 将GPU时间数据添加到历史记录
if gpu_data:
self.gpu_timing_history.append(gpu_data)
if len(self.gpu_timing_history) > self.gpu_timing_history_size:
self.gpu_timing_history.pop(0)
# 调试信息 - 仅在第一次成功时输出
if not hasattr(self, '_gpu_timing_success_logged'):
available_fields = list(gpu_data.keys())
print(f"✅ GPU时间统计已启用 - 可用字段: {available_fields}")
self._gpu_timing_success_logged = True
return gpu_data
except AttributeError as e:
# OpenVR Python绑定可能不包含某些字段
if self.gpu_timing_failure_count == 0:
print(f"⚠️ GPU时间统计部分功能不可用: {e}")
print(" 这可能是由于OpenVR Python绑定版本问题")
self.gpu_timing_failure_count += 1
return None
except Exception as e:
self.gpu_timing_failure_count += 1
if self.gpu_timing_failure_count % 300 == 1: # 每5秒输出一次错误
print(f"⚠️ 获取GPU时间统计失败: {e}")
return None
def _update_performance_metrics(self):
"""更新系统性能指标"""
if not self.performance_monitoring:
return
import time
current_time = time.time()
# 限制更新频率
if current_time - self.last_performance_check < self.performance_check_interval:
return
self.last_performance_check = current_time
try:
# 更新CPU和内存使用率
if self.psutil_available:
self.cpu_usage = self.psutil.cpu_percent(interval=None)
memory = self.psutil.virtual_memory()
self.memory_usage = memory.percent
# 更新GPU使用率
self._update_gpu_metrics()
except Exception as e:
if self.frame_count % 600 == 0: # 每10秒输出一次错误
print(f"⚠️ 性能监控更新失败: {e}")
def _update_gpu_metrics(self):
"""更新GPU相关指标"""
try:
# 方法1: 使用GPUtil
if self.gputil_available:
gpus = self.gputil.getGPUs()
if gpus:
gpu = gpus[0] # 使用第一个GPU
self.gpu_usage = gpu.load * 100
self.gpu_memory_usage = gpu.memoryUtil * 100
# 方法2: 使用NVIDIA-ML (更精确)
elif self.nvidia_ml_available:
try:
handle = self.pynvml.nvmlDeviceGetHandleByIndex(0)
# GPU使用率
utilization = self.pynvml.nvmlDeviceGetUtilizationRates(handle)
self.gpu_usage = utilization.gpu
# GPU内存使用率
memory_info = self.pynvml.nvmlDeviceGetMemoryInfo(handle)
self.gpu_memory_usage = (memory_info.used / memory_info.total) * 100
except Exception as e:
# NVIDIA-ML可能无法在某些系统上工作
pass
except Exception as e:
# GPU监控失败但不影响VR功能
pass
def _track_frame_time(self):
"""记录帧时间 - 性能优化版本"""
import time
current_time = time.time()
if hasattr(self, '_last_frame_time'):
frame_time = (current_time - self._last_frame_time) * 1000 # 转换为毫秒
# 🚀 性能优化:性能模式下跳过列表操作以减少内存分配
if not self.performance_mode_enabled:
# 添加到帧时间历史
self.frame_times.append(frame_time)
# 限制历史长度
if len(self.frame_times) > self.max_frame_time_history:
self.frame_times.pop(0)
self._last_frame_time = current_time
def get_performance_stats(self):
"""获取详细的性能统计信息"""
stats = {
'vr_fps': self.vr_fps,
'frame_count': self.frame_count,
'submit_failures': self.submit_failures,
'pose_failures': self.pose_failures,
'cpu_usage': self.cpu_usage,
'memory_usage': self.memory_usage,
'gpu_usage': self.gpu_usage,
'gpu_memory_usage': self.gpu_memory_usage,
}
# 计算帧时间统计
if self.frame_times:
stats['frame_time_avg'] = sum(self.frame_times) / len(self.frame_times)
stats['frame_time_min'] = min(self.frame_times)
stats['frame_time_max'] = max(self.frame_times)
stats['frame_time_95th'] = sorted(self.frame_times)[int(len(self.frame_times) * 0.95)]
else:
stats['frame_time_avg'] = 0
stats['frame_time_min'] = 0
stats['frame_time_max'] = 0
stats['frame_time_95th'] = 0
return stats
def enable_performance_monitoring(self):
"""启用性能监控"""
self.performance_monitoring = True
print("✓ VR性能监控已启用")
def disable_performance_monitoring(self):
"""禁用性能监控"""
self.performance_monitoring = False
print("✓ VR性能监控已禁用")
def enable_gpu_timing_monitoring(self):
"""启用GPU时间监控"""
self.enable_gpu_timing = True
print("✓ VR GPU时间监控已启用")
def disable_gpu_timing_monitoring(self):
"""禁用GPU时间监控"""
self.enable_gpu_timing = False
print("✓ VR GPU时间监控已禁用")
def set_performance_check_interval(self, interval):
"""设置性能检查间隔
Args:
interval: 检查间隔建议0.1-2.0之间
"""
if 0.1 <= interval <= 5.0:
self.performance_check_interval = interval
print(f"✓ 性能监控间隔设置为 {interval:.1f}")
else:
print("⚠️ 性能监控间隔应在0.1-5.0秒之间")
def set_frame_time_history_size(self, size):
"""设置帧时间历史记录大小
Args:
size: 历史记录大小帧数建议30-300之间
"""
if 10 <= size <= 1000:
self.max_frame_time_history = size
# 清理超出的历史记录
if len(self.frame_times) > size:
self.frame_times = self.frame_times[-size:]
print(f"✓ 帧时间历史记录大小设置为 {size}")
else:
print("⚠️ 帧时间历史记录大小应在10-1000帧之间")
def set_performance_report_interval(self, frames):
"""设置性能报告输出间隔
Args:
frames: 帧数间隔建议300-7200之间5秒-2分钟@60fps
"""
if 300 <= frames <= 7200:
# 修改_update_vr中的报告间隔
print(f"✓ 性能报告间隔设置为每 {frames} 帧(约 {frames/60:.1f} 秒@60fps")
# 这里可以添加一个实例变量来控制
self.performance_report_interval = frames
else:
print("⚠️ 性能报告间隔应在300-7200帧之间")
def get_performance_monitoring_config(self):
"""获取当前性能监控配置"""
return {
'enabled': self.performance_monitoring,
'check_interval': self.performance_check_interval,
'frame_history_size': self.max_frame_time_history,
'report_interval': getattr(self, 'performance_report_interval', 1800),
'psutil_available': self.psutil_available,
'gputil_available': self.gputil_available,
'nvidia_ml_available': self.nvidia_ml_available
}
def print_performance_monitoring_status(self):
"""输出性能监控状态"""
config = self.get_performance_monitoring_config()
print("🔧 ===== VR性能监控配置 =====")
print(f" 监控状态: {'✅ 启用' if config['enabled'] else '❌ 禁用'}")
print(f" 检查间隔: {config['check_interval']:.1f}")
print(f" 帧时间历史: {config['frame_history_size']}")
print(f" 报告间隔: {config['report_interval']}")
print(f" 可用库:")
print(f" psutil (CPU/内存): {'' if config['psutil_available'] else ''}")
print(f" GPUtil (GPU): {'' if config['gputil_available'] else ''}")
print(f" NVIDIA-ML (GPU): {'' if config['nvidia_ml_available'] else ''}")
print("=============================")
def force_performance_report(self):
"""强制输出一次性能报告"""
print("🔄 手动触发性能报告...")
self._print_performance_report()
def reset_performance_counters(self):
"""重置性能计数器"""
self.frame_count = 0
self.last_fps_check = 0
self.last_fps_time = 0
self.vr_fps = 0
self.submit_failures = 0
self.pose_failures = 0
self.frame_times.clear()
print("✅ 性能计数器已重置")
def get_current_performance_summary(self):
"""获取当前性能摘要(简短版本)"""
stats = self.get_performance_stats()
summary = f"VR性能: {stats['vr_fps']:.1f}fps"
if stats['frame_time_avg'] > 0:
summary += f" | 帧时间: {stats['frame_time_avg']:.1f}ms"
if self.psutil_available:
summary += f" | CPU: {stats['cpu_usage']:.0f}%"
summary += f" | 内存: {stats['memory_usage']:.0f}%"
# 显示GPU信息如果库不可用则显示提示
if self.gputil_available or self.nvidia_ml_available:
summary += f" | GPU: {stats['gpu_usage']:.0f}%"
else:
summary += " | GPU: N/A"
return summary
def _print_render_callback_diagnostics(self):
"""输出渲染回调诊断信息 - 包含优化效果分析"""
try:
print(f"🔍 DrawCallback渲染诊断:")
# 回调次数统计
left_count = self.left_render_count
right_count = self.right_render_count
print(f" 渲染次数: 左眼={left_count}, 右眼={right_count}")
if left_count == 0 and right_count == 0:
print(f" ❌ VR缓冲区未被渲染 - 这是严重问题!")
return
# 渲染次数平衡性检查
if abs(left_count - right_count) > 5:
print(f" ⚠️ 左右眼渲染次数不平衡: 差异={abs(left_count - right_count)}")
else:
print(f" ✓ 左右眼渲染次数平衡")
# 🔧 真实渲染时间分析 - 显示优化效果
left_render_time = getattr(self, 'left_render_time', 0)
right_render_time = getattr(self, 'right_render_time', 0)
total_render_time = left_render_time + right_render_time
print(f" ⏱️ 精确渲染时间测量:")
print(f" 左眼cbdata.upcall(): {left_render_time:.2f}ms")
print(f" 右眼cbdata.upcall(): {right_render_time:.2f}ms")
print(f" 总计: {total_render_time:.2f}ms")
# 渲染性能评估
if total_render_time > 16.0: # 超过60FPS时间
print(f" 🔴 渲染时间过长: {total_render_time:.1f}ms (目标<13.9ms@72Hz)")
print(f" 建议: 检查RenderPipeline优化是否生效")
elif total_render_time > 10.0:
print(f" 🟡 渲染时间偏高: {total_render_time:.1f}ms (可接受)")
else:
print(f" 🟢 渲染性能良好: {total_render_time:.1f}ms")
# 🔧 渲染管线优化状态检查
self._check_rendering_optimizations()
# OpenGL状态诊断
self._diagnose_opengl_state()
except Exception as e:
print(f" 渲染回调诊断失败: {e}")
def _check_rendering_optimizations(self):
"""检查渲染优化状态"""
try:
print(f" 🔧 渲染优化状态:")
# 检查RenderPipeline优化
if hasattr(self.world, 'render_pipeline') and self.world.render_pipeline:
print(f" RenderPipeline: 已检测并优化")
else:
print(f" RenderPipeline: 未检测到使用基础Panda3D")
# 检查GPU同步优化
if hasattr(self, '_smart_sync_logged'):
last_sync_frame = getattr(self, '_last_gpu_sync_frame', 0)
current_frame = getattr(self, 'frame_count', 0)
frames_since_sync = current_frame - last_sync_frame
print(f" 智能GPU同步: 已启用 (距离上次同步: {frames_since_sync}帧)")
else:
print(f" 智能GPU同步: 未初始化")
# 检查对象池状态
matrix_pool_status = self.get_object_pool_status()
print(f" 对象池: Mat4={matrix_pool_status['matrix_pool_size']}/{matrix_pool_status['matrix_pool_capacity']}")
# 检查垃圾回收控制
gc_status = self.get_debug_status()
if gc_status['gc_disabled']:
print(f" GC控制: 已启用手动模式 (间隔:{gc_status['manual_gc_interval']}帧)")
else:
print(f" GC控制: 自动模式")
except Exception as e:
print(f" 优化状态检查失败: {e}")
def _diagnose_opengl_state(self):
"""诊断OpenGL渲染状态"""
try:
# 检查VR缓冲区状态
if hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer:
left_gsg = self.vr_left_eye_buffer.getGsg()
left_valid = self.vr_left_eye_buffer.isValid()
print(f" 左眼缓冲区: {'有效' if left_valid else '无效'}")
if hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer:
right_gsg = self.vr_right_eye_buffer.getGsg()
right_valid = self.vr_right_eye_buffer.isValid()
print(f" 右眼缓冲区: {'有效' if right_valid else '无效'}")
# 检查纹理准备状态
if hasattr(self, 'textures_prepared'):
print(f" 纹理准备状态: {'已准备' if self.textures_prepared else '未准备'}")
# 检查纹理ID缓存
if hasattr(self, 'left_texture_id') and hasattr(self, 'right_texture_id'):
left_id = self.left_texture_id or 0
right_id = self.right_texture_id or 0
print(f" 纹理ID缓存: 左眼={left_id}, 右眼={right_id}")
if left_id == 0 or right_id == 0:
print(f" ⚠️ 检测到无效的纹理ID这可能导致提交失败")
else:
print(f" ✓ 纹理ID缓存正常")
# 检查场景渲染状态
if hasattr(self.world, 'render') and self.world.render:
render_children = len(self.world.render.getChildren())
print(f" 场景节点数: {render_children}")
if render_children == 0:
print(f" ⚠️ 场景为空,可能导致渲染时间异常短")
except Exception as e:
print(f" OpenGL状态诊断失败: {e}")
def enable_debug_output(self):
"""启用调试输出"""
self.debug_output_enabled = True
print("✓ VR调试输出已启用")
def disable_debug_output(self):
"""禁用调试输出"""
self.debug_output_enabled = False
print("✓ VR调试输出已禁用")
def set_debug_mode(self, mode):
"""设置调试模式
Args:
mode: 'brief''detailed'
"""
if mode in ['brief', 'detailed']:
self.debug_mode = mode
print(f"✓ VR调试模式设置为: {mode}")
else:
print("⚠️ 调试模式只能是 'brief''detailed'")
def toggle_debug_output(self):
"""切换调试输出状态"""
self.debug_output_enabled = not self.debug_output_enabled
status = "启用" if self.debug_output_enabled else "禁用"
print(f"✓ VR调试输出已{status}")
return self.debug_output_enabled
def get_debug_status(self):
"""获取调试状态"""
return {
'debug_enabled': self.debug_output_enabled,
'debug_mode': self.debug_mode,
'performance_monitoring': self.performance_monitoring,
'report_interval_frames': getattr(self, 'performance_report_interval', 600),
'report_interval_seconds': getattr(self, 'performance_report_interval', 600) / 60, # 假设60fps
'gc_control_enabled': self._gc_control_enabled,
'gc_disabled': self._gc_disabled,
'manual_gc_interval': self._manual_gc_interval,
}
# ====== Python垃圾回收控制方法 ======
def enable_gc_control(self):
"""启用垃圾回收控制 - 减少VR渲染期间的GC峰值"""
if not self._gc_control_enabled:
self._gc_control_enabled = True
if not self._gc_disabled:
gc.disable()
self._gc_disabled = True
print("✅ VR垃圾回收控制已启用")
else:
print(" VR垃圾回收控制已经启用")
def disable_gc_control(self):
"""禁用垃圾回收控制 - 恢复自动垃圾回收"""
if self._gc_control_enabled:
self._gc_control_enabled = False
if self._gc_disabled:
gc.enable()
self._gc_disabled = False
print("✅ VR垃圾回收控制已禁用恢复自动垃圾回收")
else:
print(" VR垃圾回收控制已经禁用")
def set_manual_gc_interval(self, frames):
"""设置手动垃圾回收间隔
Args:
frames: 帧数间隔 (建议100-600)
"""
if 50 <= frames <= 1800:
old_interval = self._manual_gc_interval
self._manual_gc_interval = frames
print(f"✅ 手动GC间隔: {old_interval}{frames}")
else:
print("⚠️ GC间隔应在50-1800帧之间")
def force_manual_gc(self):
"""强制执行一次垃圾回收"""
collected = gc.collect()
print(f"🗑️ 强制GC: 清理了 {collected} 个对象")
return collected
def get_object_pool_status(self):
"""获取对象池状态"""
return {
'matrix_pool_size': len(self._matrix_pool) if hasattr(self, '_matrix_pool') else 0,
'matrix_pool_capacity': getattr(self, '_matrix_pool_size', 0),
'cached_controllers': len(self.controller_poses),
'cached_matrices': len(getattr(self, '_cached_matrices', {})),
}
# ====== VR分辨率缩放和质量预设系统 ======
def set_resolution_scale(self, scale):
"""设置VR分辨率缩放系数
Args:
scale: 缩放系数 (0.3-1.0)0.75表示75%分辨率
"""
if not (0.3 <= scale <= 1.0):
print(f"⚠️ 分辨率缩放系数应在0.3-1.0之间,当前: {scale}")
return False
old_scale = self.resolution_scale
self.resolution_scale = scale
# 如果VR已初始化重新创建缓冲区
if self.vr_initialized:
self._apply_resolution_scale()
print(f"✓ VR分辨率缩放: {old_scale}{scale}")
pixel_reduction = (1 - scale**2) * 100
print(f"📊 像素减少: {pixel_reduction:.1f}%")
return True
def set_quality_preset(self, preset_name):
"""设置VR质量预设
Args:
preset_name: 'performance', 'balanced', 'quality'
"""
if preset_name not in self.quality_presets:
print(f"⚠️ 未知的质量预设: {preset_name}")
print(f" 可用预设: {list(self.quality_presets.keys())}")
return False
old_preset = self.current_quality_preset
self.current_quality_preset = preset_name
scale = self.quality_presets[preset_name]
print(f"🎯 切换VR质量预设: {old_preset}{preset_name}")
return self.set_resolution_scale(scale)
def cycle_quality_preset(self):
"""循环切换质量预设"""
presets = list(self.quality_presets.keys())
current_index = presets.index(self.current_quality_preset)
next_index = (current_index + 1) % len(presets)
next_preset = presets[next_index]
return self.set_quality_preset(next_preset)
def _apply_resolution_scale(self):
"""应用分辨率缩放重新创建VR缓冲区"""
try:
# 计算新的分辨率
self.scaled_eye_width = int(self.base_eye_width * self.resolution_scale)
self.scaled_eye_height = int(self.base_eye_height * self.resolution_scale)
# 更新当前分辨率
self.eye_width = self.scaled_eye_width
self.eye_height = self.scaled_eye_height
self.current_eye_resolution = (self.eye_width, self.eye_height)
print(f"🔄 重新创建VR缓冲区...")
print(f" 新分辨率: {self.eye_width}x{self.eye_height}")
# 清理旧的缓冲区
self._cleanup_vr_buffers()
# 重新创建缓冲区
if self._create_vr_buffers():
# 重新设置相机
self._setup_vr_cameras()
print("✅ VR缓冲区重新创建成功")
return True
else:
print("❌ VR缓冲区重新创建失败")
return False
except Exception as e:
print(f"❌ 应用分辨率缩放失败: {e}")
import traceback
traceback.print_exc()
return False
def _cleanup_vr_buffers(self):
"""清理旧的VR缓冲区"""
try:
# 清理左眼缓冲区
if hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer:
self.vr_left_eye_buffer.removeAllDisplayRegions()
self.world.graphicsEngine.removeWindow(self.vr_left_eye_buffer)
self.vr_left_eye_buffer = None
# 清理右眼缓冲区
if hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer:
self.vr_right_eye_buffer.removeAllDisplayRegions()
self.world.graphicsEngine.removeWindow(self.vr_right_eye_buffer)
self.vr_right_eye_buffer = None
# 清理相机
if hasattr(self, 'vr_left_camera') and self.vr_left_camera:
self.vr_left_camera.removeNode()
self.vr_left_camera = None
if hasattr(self, 'vr_right_camera') and self.vr_right_camera:
self.vr_right_camera.removeNode()
self.vr_right_camera = None
except Exception as e:
print(f"⚠️ 清理VR缓冲区时出错: {e}")
def get_resolution_info(self):
"""获取分辨率相关信息"""
return {
'base_resolution': (self.base_eye_width, self.base_eye_height),
'current_resolution': (self.eye_width, self.eye_height),
'resolution_scale': self.resolution_scale,
'current_preset': self.current_quality_preset,
'available_presets': self.quality_presets,
'pixel_reduction_percent': (1 - self.resolution_scale**2) * 100
}
def print_resolution_info(self):
"""输出分辨率信息"""
info = self.get_resolution_info()
print("🔧 ===== VR分辨率信息 =====")
print(f" 推荐分辨率: {info['base_resolution'][0]}x{info['base_resolution'][1]}")
print(f" 当前分辨率: {info['current_resolution'][0]}x{info['current_resolution'][1]}")
print(f" 缩放系数: {info['resolution_scale']}")
print(f" 当前预设: {info['current_preset']}")
print(f" 像素减少: {info['pixel_reduction_percent']:.1f}%")
print(" 可用预设:")
for name, scale in info['available_presets'].items():
marker = "" if name == info['current_preset'] else " "
print(f" {marker} {name}: {scale} ({scale*100:.0f}%)")
print("==========================")
# ====== 性能模式控制方法 ======
def enable_performance_mode(self):
"""手动启用性能模式 - 立即禁用详细监控以提升性能"""
if not self.performance_mode_enabled:
self.performance_mode_enabled = True
print("🎯 性能模式已手动启用 - 禁用详细监控以提升性能")
print(" 现在将减少每帧对象创建显著提升VR性能稳定性")
else:
print(" 性能模式已经启用")
def disable_performance_mode(self):
"""禁用性能模式 - 重新启用详细监控(用于调试)"""
if self.performance_mode_enabled:
self.performance_mode_enabled = False
print("🔍 性能模式已禁用 - 重新启用详细监控")
print(" 注意这将增加每帧对象创建可能影响VR性能")
else:
print(" 性能模式已经禁用")
def set_performance_mode_trigger_frame(self, frame_count):
"""设置性能模式自动触发的帧数
Args:
frame_count: 触发帧数 (建议300-1200)
"""
if 100 <= frame_count <= 3600:
old_trigger = self.performance_mode_trigger_frame
self.performance_mode_trigger_frame = frame_count
print(f"✅ 性能模式触发帧数: {old_trigger}{frame_count}")
else:
print("⚠️ 触发帧数应在100-3600之间")
def get_performance_mode_status(self):
"""获取性能模式状态"""
return {
'performance_mode_enabled': self.performance_mode_enabled,
'trigger_frame': self.performance_mode_trigger_frame,
'current_frame': self.frame_count,
'will_trigger_at_frame': self.performance_mode_trigger_frame if not self.performance_mode_enabled else None,
'gc_interval_normal': self._manual_gc_interval,
'gc_interval_performance': self._manual_gc_interval * 2,
}
# ====== VR测试模式 ======
def enable_vr_test_mode(self, display_mode='stereo'):
"""启用VR测试模式 - 将VR渲染直接显示在屏幕上
Args:
display_mode: 显示模式
- 'stereo': 左右眼并排显示
- 'left': 只显示左眼
- 'right': 只显示右眼
"""
if not self.is_vr_available():
print("❌ VR系统不可用无法启动测试模式")
return False
try:
print(f"🧪 启动VR测试模式 - 显示模式: {display_mode}")
# 初始化VR系统如果还没初始化
if not self.vr_initialized:
if not self.initialize_vr():
print("❌ VR初始化失败")
return False
# 🔧 关键修复:确保测试模式的纹理资源已初始化
# 这解决了测试模式纹理提交失败导致的36FPS问题
print("🔧 检查VR测试模式纹理资源...")
if not self._ensure_test_mode_textures():
print("❌ VR测试模式纹理资源初始化失败")
return False
# 设置测试模式
self.vr_test_mode = True
self.test_display_mode = display_mode
# 启用VR渲染但不提交给OpenVR
if not self.vr_enabled:
# 启用VR渲染流程但会在回调中跳过提交
self.vr_enabled = True
self._disable_main_cam()
# 设置高帧率用于测试
if hasattr(self.world, 'qtWidget') and self.world.qtWidget:
if hasattr(self.world.qtWidget, 'synchronizer'):
self.world.qtWidget.synchronizer.setInterval(int(1000/144))
print("✓ 测试模式Qt Timer设置为144Hz")
# 初始化测试显示系统
if not self._initialize_test_display():
print("❌ 测试显示系统初始化失败")
return False
# 创建性能HUD
if not self._initialize_test_performance_hud():
print("❌ 性能HUD初始化失败")
return False
# 恢复主相机以查看测试内容
self._enable_main_cam()
# 重置HUD更新计数器
self.hud_update_counter = 0
print("✅ VR测试模式已启用")
print(" - VR内容将显示在屏幕上")
print(" - 不会向OpenVR提交纹理")
print(" - 可以准确测量纯渲染性能")
print(f" - 当前显示模式: {display_mode}")
return True
except Exception as e:
print(f"❌ 启动VR测试模式失败: {e}")
import traceback
traceback.print_exc()
return False
def _ensure_test_mode_textures(self):
"""确保VR测试模式的纹理资源已正确初始化
这解决了测试模式启用纹理提交时的36FPS问题
- VR测试模式可能跳过了VR渲染缓冲区的初始化
- submit_texture()需要有效的texture ID和OpenVR Texture对象
- 如果未初始化会导致提交失败,造成帧率减半
"""
try:
print(" 检查VR渲染缓冲区...")
# 检查VR渲染缓冲区是否存在
buffers_exist = (
hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer and
hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer
)
if not buffers_exist:
print(" ⚠️ VR渲染缓冲区不存在正在创建...")
if not self._setup_vr_render_buffers():
print(" ❌ VR渲染缓冲区创建失败")
return False
print(" ✅ VR渲染缓冲区创建成功")
else:
print(" ✅ VR渲染缓冲区已存在")
# 检查纹理ID是否已缓存
print(" 检查纹理ID缓存...")
texture_ids_cached = (
hasattr(self, 'left_texture_id') and self.left_texture_id and self.left_texture_id > 0 and
hasattr(self, 'right_texture_id') and self.right_texture_id and self.right_texture_id > 0
)
if not texture_ids_cached:
print(" ⚠️ 纹理ID未缓存正在准备纹理...")
if not self._prepare_and_cache_textures():
print(" ❌ 纹理准备和缓存失败")
return False
print(f" ✅ 纹理ID缓存成功 - 左眼:{self.left_texture_id}, 右眼:{self.right_texture_id}")
else:
print(f" ✅ 纹理ID已缓存 - 左眼:{self.left_texture_id}, 右眼:{self.right_texture_id}")
# 检查OpenVR Texture对象是否已创建
print(" 检查OpenVR Texture对象...")
ovr_textures_exist = (
hasattr(self, '_left_ovr_texture') and self._left_ovr_texture and
hasattr(self, '_right_ovr_texture') and self._right_ovr_texture
)
if not ovr_textures_exist:
print(" ⚠️ OpenVR Texture对象未创建正在创建...")
self._create_cached_ovr_textures()
print(" ✅ OpenVR Texture对象创建成功")
else:
print(" ✅ OpenVR Texture对象已存在")
print(" ✅ VR测试模式纹理资源检查完成可安全启用纹理提交")
return True
except Exception as e:
print(f" ❌ VR测试模式纹理资源检查失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_cached_ovr_textures(self):
"""创建缓存的OpenVR Texture对象 - 避免每帧创建新对象"""
try:
import openvr
self._left_ovr_texture = openvr.Texture_t()
self._right_ovr_texture = openvr.Texture_t()
# 设置固定属性(这些不变)
self._left_ovr_texture.eType = openvr.TextureType_OpenGL
self._left_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
self._right_ovr_texture.eType = openvr.TextureType_OpenGL
self._right_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
print("✅ OpenVR Texture对象缓存已创建")
except Exception as e:
print(f"⚠️ OpenVR Texture对象创建失败: {e}")
# 不抛出异常,使用备用方案
def _batch_submit_textures(self):
"""批量提交两眼纹理 - OpenVR最佳实践
基于官方hellovr示例的实现
- 两眼都渲染完成后,快速连续提交
- 减少submit阻塞时间避免错过VSync窗口
- 这是解决36FPS问题的关键
"""
try:
if not self.vr_compositor:
return False
# 检查纹理是否准备好
if not (self.vr_left_texture and self.vr_right_texture):
return False
# 🚀 关键:快速连续提交两眼,最小化阻塞时间
# 这符合OpenVR官方示例的做法
success_left = False
success_right = False
# 提交左眼纹理
try:
self.submit_texture(openvr.Eye_Left, self.vr_left_texture)
success_left = True
except Exception as e:
print(f"❌ 批量提交左眼失败: {e}")
# 立即提交右眼纹理(不等待)
try:
self.submit_texture(openvr.Eye_Right, self.vr_right_texture)
success_right = True
except Exception as e:
print(f"❌ 批量提交右眼失败: {e}")
# 🚀 关键修复调用PostPresentHandoff解除compositor阻塞
# 这是解决36FPS问题的核心 - 确保compositor不会等待VSync
if success_left and success_right:
try:
# PostPresentHandoff告诉compositor我们已完成帧处理
# 防止compositor等待下一个VSync周期
if hasattr(self.vr_compositor, 'postPresentHandoff'):
self.vr_compositor.postPresentHandoff()
elif hasattr(self.vr_compositor, 'PostPresentHandoff'):
self.vr_compositor.PostPresentHandoff()
else:
# 备用方案如果没有PostPresentHandoff记录警告
if not hasattr(self, '_post_present_warning_logged'):
print("⚠️ PostPresentHandoff方法未找到可能影响时序")
self._post_present_warning_logged = True
except Exception as handoff_error:
if not hasattr(self, '_handoff_error_logged'):
print(f"⚠️ PostPresentHandoff调用失败: {handoff_error}")
self._handoff_error_logged = True
# 记录批量提交状态(仅首次成功时)
if not hasattr(self, '_batch_submit_success_logged'):
print("✅ OpenVR批量提交模式+PostPresentHandoff已启用")
print(" 这应该解决36FPS → 72FPS的问题")
self._batch_submit_success_logged = True
return True
else:
return False
except Exception as e:
print(f"❌ 批量提交纹理失败: {e}")
return False
def disable_vr_test_mode(self):
"""禁用VR测试模式"""
try:
print("🧪 禁用VR测试模式...")
# 清理测试显示
self._cleanup_test_display()
# 清理性能HUD
self._cleanup_test_performance_hud()
# 关闭测试模式
self.vr_test_mode = False
self.test_mode_initialized = False
# 重置HUD更新计数器
self.hud_update_counter = 0
# 恢复正常VR模式或禁用VR
if self.vr_enabled:
print(" 选择: [1] 恢复正常VR模式 [2] 完全禁用VR")
# 这里可以让用户选择现在默认禁用VR
self.disable_vr()
print("✅ VR测试模式已禁用")
return True
except Exception as e:
print(f"❌ 禁用VR测试模式失败: {e}")
return False
def switch_test_display_mode(self, display_mode):
"""切换测试显示模式
Args:
display_mode: 'stereo', 'left', 'right'
"""
if not self.vr_test_mode:
print("⚠️ 请先启用VR测试模式")
return False
if display_mode not in ['stereo', 'left', 'right']:
print(f"⚠️ 无效的显示模式: {display_mode}")
return False
print(f"🧪 切换显示模式: {self.test_display_mode}{display_mode}")
# 如果切换模式,需要清理旧的显示并重置标志位
if self.test_display_mode != display_mode:
self._cleanup_test_display()
self.test_display_mode = display_mode
# 更新显示
self._update_test_display()
return True
def _initialize_test_display(self):
"""初始化测试显示系统"""
try:
print("🔧 正在初始化测试显示系统...")
# 导入必要的Panda3D组件
from panda3d.core import CardMaker, PandaNode
# 创建显示四边形
cm = CardMaker("vr_test_display")
if self.test_display_mode == 'stereo':
# 并排显示:左眼在左半屏,右眼在右半屏
cm.setFrame(-1, 1, -0.5, 0.5) # 全屏
else:
# 单眼显示:占据整个屏幕
cm.setFrame(-1, 1, -1, 1)
# 创建节点
self.test_display_quad = self.world.render2d.attachNewNode(cm.generate())
# 设置初始纹理
self._update_test_display()
# 设置渲染状态
self.test_display_quad.setTransparency(0) # 不透明
self.test_display_quad.setBin("background", 0) # 背景层
self.test_mode_initialized = True
print("✅ 测试显示系统初始化完成")
return True
except Exception as e:
print(f"❌ 测试显示系统初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _update_test_display(self):
"""更新测试显示内容"""
try:
if not self.test_mode_initialized:
return
# 根据显示模式设置纹理
if self.test_display_mode == 'left':
if not self.test_display_quad:
return
if self.vr_left_texture:
self.test_display_quad.setTexture(self.vr_left_texture)
elif self.test_display_mode == 'right':
if not self.test_display_quad:
return
if self.vr_right_texture:
self.test_display_quad.setTexture(self.vr_right_texture)
elif self.test_display_mode == 'stereo':
# 立体显示:只在第一次创建,后续只更新纹理
if not self.stereo_display_created:
self._create_stereo_display()
else:
# 只更新纹理,不重新创建
if self.test_display_quad and self.vr_left_texture:
self.test_display_quad.setTexture(self.vr_left_texture)
if self.test_right_quad and self.vr_right_texture:
self.test_right_quad.setTexture(self.vr_right_texture)
except Exception as e:
print(f"⚠️ 更新测试显示失败: {e}")
def _create_stereo_display(self):
"""创建左右眼并排显示"""
try:
# 如果已经创建,直接返回
if self.stereo_display_created and self.test_display_quad and self.test_right_quad:
return
# 移除旧的显示
if self.test_display_quad:
self.test_display_quad.removeNode()
if self.test_right_quad:
self.test_right_quad.removeNode()
from panda3d.core import CardMaker
# 创建左眼显示
left_cm = CardMaker("vr_test_left")
left_cm.setFrame(-1, 0, -1, 1) # 左半屏
left_quad = self.world.render2d.attachNewNode(left_cm.generate())
if self.vr_left_texture:
left_quad.setTexture(self.vr_left_texture)
# 创建右眼显示
right_cm = CardMaker("vr_test_right")
right_cm.setFrame(0, 1, -1, 1) # 右半屏
right_quad = self.world.render2d.attachNewNode(right_cm.generate())
if self.vr_right_texture:
right_quad.setTexture(self.vr_right_texture)
# 保存引用
self.test_display_quad = left_quad # 保存左眼引用
self.test_right_quad = right_quad # 额外保存右眼引用
self.stereo_display_created = True # 标记已创建
print("✓ 立体显示已创建")
except Exception as e:
print(f"⚠️ 创建立体显示失败: {e}")
self.stereo_display_created = False
def _cleanup_test_display(self):
"""清理测试显示"""
try:
if self.test_display_quad:
self.test_display_quad.removeNode()
self.test_display_quad = None
if hasattr(self, 'test_right_quad') and self.test_right_quad:
self.test_right_quad.removeNode()
self.test_right_quad = None
# 重置立体显示标志位
self.stereo_display_created = False
print("✓ 测试显示已清理")
except Exception as e:
print(f"⚠️ 清理测试显示失败: {e}")
def _initialize_test_performance_hud(self):
"""初始化性能HUD"""
try:
print("🔧 正在初始化性能HUD...")
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import TextNode
# 创建性能文本显示
self.test_performance_text = OnscreenText(
text="初始化中...",
pos=(-0.95, 0.9), # 左上角,调整到屏幕内
scale=0.05,
fg=(1, 1, 0, 1), # 黄色
align=TextNode.ALeft,
shadow=(0, 0, 0, 0.5), # 黑色阴影
parent=self.world.render2d
)
print("✅ 性能HUD初始化完成")
return True
except Exception as e:
print(f"❌ 性能HUD初始化失败: {e}")
return False
def _update_test_performance_hud(self):
"""更新性能HUD显示限制更新频率避免重影"""
try:
if not self.test_performance_text or not self.vr_test_mode:
return
# 增加计数器
self.hud_update_counter += 1
# 只在指定间隔更新文本,避免重影
if self.hud_update_counter < self.hud_update_interval:
return
# 重置计数器
self.hud_update_counter = 0
# 收集性能数据
left_render_time = getattr(self, 'left_render_time', 0)
right_render_time = getattr(self, 'right_render_time', 0)
total_render_time = left_render_time + right_render_time
# 计算FPS
current_fps = self.vr_fps if hasattr(self, 'vr_fps') else 0
# 获取系统性能
cpu_usage = getattr(self, 'cpu_usage', 0)
memory_usage = getattr(self, 'memory_usage', 0)
gpu_usage = getattr(self, 'gpu_usage', 0)
# 构建显示文本
hud_text = f"""VR TEST MODE - {self.test_display_mode.upper()}
Render Performance:
FPS: {current_fps:.1f}
Left Eye: {left_render_time:.2f}ms
Right Eye: {right_render_time:.2f}ms
Total: {total_render_time:.2f}ms
System Performance:
CPU: {cpu_usage:.1f}%
Memory: {memory_usage:.1f}%
GPU: {gpu_usage:.1f}%
Render Count:
Left Eye: {getattr(self, 'left_render_count', 0)}
Right Eye: {getattr(self, 'right_render_count', 0)}
Target: <13.9ms@72Hz
Status: {'GOOD' if total_render_time < 10 else 'HIGH' if total_render_time < 16 else 'SLOW'}
Hotkeys:
F1=Left F2=Right F3=SideBySide
ESC=Exit Test Mode"""
# 更新文本
self.test_performance_text.setText(hud_text)
except Exception as e:
print(f"⚠️ 更新性能HUD失败: {e}")
def _cleanup_test_performance_hud(self):
"""清理性能HUD"""
try:
if self.test_performance_text:
self.test_performance_text.destroy()
self.test_performance_text = None
print("✓ 性能HUD已清理")
except Exception as e:
print(f"⚠️ 清理性能HUD失败: {e}")
def get_test_mode_status(self):
"""获取测试模式状态"""
if not self.vr_test_mode:
return None
left_render_time = getattr(self, 'left_render_time', 0)
right_render_time = getattr(self, 'right_render_time', 0)
total_render_time = left_render_time + right_render_time
return {
'test_mode_enabled': self.vr_test_mode,
'display_mode': self.test_display_mode,
'vr_fps': getattr(self, 'vr_fps', 0),
'left_render_time': left_render_time,
'right_render_time': right_render_time,
'total_render_time': total_render_time,
'left_render_count': getattr(self, 'left_render_count', 0),
'right_render_count': getattr(self, 'right_render_count', 0),
'performance_rating': ('excellent' if total_render_time < 10 else
'good' if total_render_time < 16 else 'poor')
}
def set_test_mode_features(self, submit_texture=None, wait_poses=None):
"""设置测试模式功能开关 - 用于渐进式调试
Args:
submit_texture: 是否在测试模式启用纹理提交True/False/None=保持当前)
wait_poses: 是否在测试模式启用waitGetPosesTrue/False/None=保持当前)
"""
if submit_texture is not None:
self.test_mode_submit_texture = submit_texture
if wait_poses is not None:
self.test_mode_wait_poses = wait_poses
print(f"🔧 VR测试模式功能设置:")
print(f" 纹理提交: {'启用' if self.test_mode_submit_texture else '禁用'}")
print(f" 姿态等待: {'启用' if self.test_mode_wait_poses else '禁用'}")
# 如果当前在测试模式,输出预期效果
if self.vr_test_mode:
print(f" 当前测试模式已启用,设置将立即生效")
else:
print(f" 设置已保存,将在下次启用测试模式时生效")
def get_test_mode_features(self):
"""获取当前测试模式功能设置"""
return {
'submit_texture': self.test_mode_submit_texture,
'wait_poses': self.test_mode_wait_poses,
'test_mode_active': self.vr_test_mode
}
def run_vr_performance_test(self, duration_seconds=30, display_mode='stereo'):
"""运行VR性能测试 - 简单的测试入口方法
Args:
duration_seconds: 测试持续时间(秒)
display_mode: 显示模式 ('stereo', 'left', 'right')
Returns:
dict: 测试结果统计
"""
print("🧪 ======= VR性能测试开始 =======")
print(f" 测试模式: {display_mode}")
print(f" 测试时长: {duration_seconds}")
print(" 按ESC键提前退出测试")
print(" F1=左眼 F2=右眼 F3=并排显示")
print("=================================")
# 启动测试模式
if not self.enable_vr_test_mode(display_mode):
print("❌ VR测试模式启动失败")
return None
# 记录测试开始状态
import time
test_start_time = time.time()
start_frame_count = getattr(self, 'frame_count', 0)
print("✅ VR测试模式已启动")
print(" - VR内容直接显示在屏幕上")
print(" - 无OpenVR纹理提交开销")
print(" - 实时性能监控已启用")
print(f" - 当前显示: {display_mode.upper()}模式")
print("\n开始性能测试...")
# 等待测试完成(用户手动停止或超时)
print(f"\n📊 测试将运行 {duration_seconds}")
print(" 实时性能数据显示在屏幕左上角")
print(" 观察帧率和渲染时间变化")
# 这里可以添加自动停止逻辑,但现在让用户手动控制
print(f"\n⏰ 请观察 {duration_seconds} 秒后手动调用 disable_vr_test_mode() 停止测试")
print(" 或者随时调用 get_test_mode_status() 查看当前状态")
return {
'status': 'running',
'start_time': test_start_time,
'start_frame': start_frame_count,
'display_mode': display_mode,
'instructions': {
'stop_test': 'vr_manager.disable_vr_test_mode()',
'check_status': 'vr_manager.get_test_mode_status()',
'switch_mode': 'vr_manager.switch_test_display_mode("left/right/stereo")'
}
}