""" 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: 是否在测试模式启用waitGetPoses(True/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")' } }