diff --git a/QPanda3D/Panda3DWorld.py b/QPanda3D/Panda3DWorld.py index 78193075..d7012fc9 100644 --- a/QPanda3D/Panda3DWorld.py +++ b/QPanda3D/Panda3DWorld.py @@ -70,6 +70,14 @@ class Panda3DWorld(ShowBase): loadPrcFileData("", f"win-size {width} {height}") loadPrcFileData("", "win-fixed-size #f") # 允许窗口调整大小 + # 🚀 VR性能优化配置 + loadPrcFileData("", "prefer-single-buffer true") # 减少缓冲区交换开销 + loadPrcFileData("", "gl-force-flush false") # 避免强制glFlush导致的性能损失 + loadPrcFileData("", "sync-video false") # 禁用默认VSync,让OpenVR控制 + loadPrcFileData("", "support-stencil false") # 禁用不必要的模板缓冲区 + loadPrcFileData("", "clock-mode non-real-time") # 禁用Panda3D帧率控制,让OpenVR控制 + # loadPrcFileData("", "gl-debug true") # 调试时可启用OpenGL调试 + if (is_fullscreen): loadPrcFileData("", "fullscreen #t") diff --git a/core/vr_controller.py b/core/vr_controller.py index 3b352207..e293d07f 100644 --- a/core/vr_controller.py +++ b/core/vr_controller.py @@ -60,6 +60,12 @@ class VRController(DirectObject): self.touchpad_pos = Vec3(0, 0, 0) self.touchpad_touched = False + # 摇杆状态 - 用于传送和转向交互 + self.joystick_pos = Vec3(0, 0, 0) # 摇杆位置 (x, y, 0) + self.joystick_touched = False # 摇杆是否被触摸 + self.joystick_pressed = False # 摇杆是否被按下 + self.previous_joystick_pos = Vec3(0, 0, 0) # 上一帧摇杆位置 + # 3D节点和可视化 self.anchor_node = None self.visualizer = None @@ -146,33 +152,97 @@ class VRController(DirectObject): if not self.is_connected or not OPENVR_AVAILABLE or not vr_system: return - # 保存上一帧的按钮状态 + # 保存上一帧的按钮状态和摇杆位置 self.previous_button_states = self.button_states.copy() + self.previous_joystick_pos = Vec3(self.joystick_pos) # 获取控制器状态 try: result, state = vr_system.getControllerState(self.device_index) if result: - # 更新按钮状态 + # 更新按钮状态 - 使用正确的OpenVR属性名 for i in range(openvr.k_EButton_Max): button_mask = 1 << i - self.button_states[i] = (state.rButtonPressed & button_mask) != 0 + # OpenVR Python绑定中使用ulButtonPressed而不是rButtonPressed + self.button_states[i] = (state.ulButtonPressed & button_mask) != 0 + + # 更新轴状态(扳机、握把、触摸板、摇杆) + # 兼容不同版本的OpenVR Python绑定 + axis_data = None + if hasattr(state, 'rAxis'): + axis_data = state.rAxis # 旧版本使用rAxis + elif hasattr(state, 'vAxis'): + axis_data = state.vAxis # 新版本使用vAxis + + if axis_data is not None and len(axis_data) > 0: + # 调试输出 - 显示所有轴数据(仅当有变化时) + self._debug_axis_data(axis_data) - # 更新轴状态(扳机、握把、触摸板) - if len(state.rAxis) > 0: # 扳机轴通常在axis[1].x - if len(state.rAxis) > 1: - self.trigger_value = state.rAxis[1].x + if len(axis_data) > 1: + self.trigger_value = axis_data[1].x - # 触摸板轴通常在axis[0] - if len(state.rAxis) > 0: - self.touchpad_pos = Vec3(state.rAxis[0].x, state.rAxis[0].y, 0) + # 触摸板/摇杆轴通常在axis[0] + if len(axis_data) > 0: + self.touchpad_pos = Vec3(axis_data[0].x, axis_data[0].y, 0) + # 摇杆和触摸板通常使用同一个轴,但可以区分设备类型 + self.joystick_pos = Vec3(axis_data[0].x, axis_data[0].y, 0) - # 触摸板触摸状态 - self.touchpad_touched = (state.rButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 + # 额外检查其他轴(某些控制器可能将摇杆分配到不同轴) + if len(axis_data) > 2: + # 有些控制器可能在axis[2]有摇杆数据 + axis2_magnitude = abs(axis_data[2].x) + abs(axis_data[2].y) + if axis2_magnitude > 0.1: # 如果有显著输入,使用这个轴作为摇杆 + self.joystick_pos = Vec3(axis_data[2].x, axis_data[2].y, 0) + + # 检查axis[3]和axis[4](Quest控制器可能使用这些轴) + for axis_idx in range(3, min(len(axis_data), 5)): + axis_magnitude = abs(axis_data[axis_idx].x) + abs(axis_data[axis_idx].y) + if axis_magnitude > 0.1: # 如果有显著输入 + # 覆盖之前的摇杆数据,使用最有活动的轴 + self.joystick_pos = Vec3(axis_data[axis_idx].x, axis_data[axis_idx].y, 0) + # 调试输出 + if not hasattr(self, '_last_axis_notify'): + self._last_axis_notify = {} + if self._last_axis_notify.get(axis_idx, 0) == 0: + print(f"🎮 {self.name}手检测到axis[{axis_idx}]活动: ({axis_data[axis_idx].x:.3f}, {axis_data[axis_idx].y:.3f})") + self._last_axis_notify[axis_idx] = 60 # 60帧后再次提醒 + else: + self._last_axis_notify[axis_idx] -= 1 + + # 触摸板和摇杆触摸状态 + self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 + + # 摇杆触摸状态(检查多个可能的按钮) + joystick_touch_mask = 0 + if hasattr(openvr, 'k_EButton_Joystick'): + joystick_touch_mask |= (1 << openvr.k_EButton_Joystick) + if hasattr(openvr, 'k_EButton_Thumbstick'): + joystick_touch_mask |= (1 << openvr.k_EButton_Thumbstick) + + self.joystick_touched = (state.ulButtonTouched & joystick_touch_mask) != 0 or self.touchpad_touched + + # 摇杆按下状态 + joystick_press_mask = 0 + if hasattr(openvr, 'k_EButton_Joystick'): + joystick_press_mask |= (1 << openvr.k_EButton_Joystick) + if hasattr(openvr, 'k_EButton_Thumbstick'): + joystick_press_mask |= (1 << openvr.k_EButton_Thumbstick) + + self.joystick_pressed = (state.ulButtonPressed & joystick_press_mask) != 0 except Exception as e: - print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}") + # 减少错误输出频率 + if not hasattr(self, '_last_input_error_frame'): + self._last_input_error_frame = 0 + + # 获取当前帧数(通过VR管理器) + current_frame = getattr(self.vr_manager, 'frame_count', 0) + + # 每5秒最多输出一次错误(300帧@60fps) + if current_frame - self._last_input_error_frame > 300: + print(f"⚠️ 更新{self.name}手柄输入状态失败: {e}") + self._last_input_error_frame = current_frame def is_button_pressed(self, button_id): """检查按钮是否被按下""" @@ -248,12 +318,92 @@ class VRController(DirectObject): return Vec3(0, 0, 0) def get_forward_direction(self): - """获取手柄指向的方向向量""" + """获取手柄指向的方向向量(包含视角转向)""" if self.anchor_node: - # Y轴正方向为前方 - return self.anchor_node.getMat().getRow3(1).getXyz().normalized() + # 获取相对于世界坐标系的方向,包含tracking_space的旋转 + if hasattr(self.vr_manager, 'world') and self.vr_manager.world: + # 使用世界变换,包含所有父节点的旋转 + world_transform = self.anchor_node.getMat(self.vr_manager.world.render) + forward = Vec3(world_transform.getRow3(1)) # Y轴 = 前方 + else: + # 备选:使用局部变换 + forward = Vec3(self.anchor_node.getMat().getRow3(1)) + + if forward.length() > 0: + return forward.normalized() return Vec3(0, 1, 0) + def is_joystick_touched(self): + """检查摇杆是否被触摸""" + return self.joystick_touched + + def is_joystick_pressed(self): + """检查摇杆是否被按下""" + return self.joystick_pressed + + def get_joystick_position(self): + """获取摇杆位置 + + Returns: + Vec3: 摇杆位置 (x, y, 0),范围 [-1, 1] + """ + return Vec3(self.joystick_pos) + + def get_joystick_delta(self): + """获取摇杆位置变化 + + Returns: + Vec3: 摇杆位置变化向量 + """ + return self.joystick_pos - self.previous_joystick_pos + + def is_joystick_moved(self, threshold=0.01): + """检查摇杆是否移动 + + Args: + threshold: 移动阈值 + + Returns: + bool: 是否移动 + """ + delta = self.get_joystick_delta() + return delta.length() > threshold + + def _debug_axis_data(self, axis_data): + """调试输出轴数据""" + try: + # 只在有活动时输出调试信息 + has_activity = False + active_axes = [] + + for i, axis in enumerate(axis_data): + magnitude = abs(axis.x) + abs(axis.y) + if magnitude > 0.01: # 检测到活动 + has_activity = True + active_axes.append(f"axis[{i}]: ({axis.x:.3f}, {axis.y:.3f})") + + if has_activity: + # 初始化调试计数器 + if not hasattr(self, '_debug_axis_counter'): + self._debug_axis_counter = 0 + + self._debug_axis_counter += 1 + + # 每30帧输出一次详细信息 + if self._debug_axis_counter % 30 == 1: + print(f"🔍 {self.name}手轴数据调试:") + print(f" 总轴数: {len(axis_data)}") + print(f" 活跃轴: {', '.join(active_axes)}") + + # 显示所有轴的当前值(不管是否活跃) + all_axes = [] + for i, axis in enumerate(axis_data): + all_axes.append(f"[{i}]:({axis.x:.3f},{axis.y:.3f})") + print(f" 所有轴: {' '.join(all_axes)}") + + except Exception as e: + print(f"⚠️ 轴数据调试失败: {e}") + def cleanup(self): """清理资源""" self.ignoreAll() diff --git a/core/vr_joystick.py b/core/vr_joystick.py new file mode 100644 index 00000000..feaa1cae --- /dev/null +++ b/core/vr_joystick.py @@ -0,0 +1,701 @@ +""" +VR摇杆交互系统模块 + +提供类似SteamVR的摇杆交互功能: +- 摇杆左右转向(旋转视角) +- 摇杆向前传送预览(抛物线轨迹) +- 松开摇杆执行传送 +- 死区处理和平滑控制 +""" + +import math +from panda3d.core import Vec2, Vec3, Vec4 +from direct.showbase.DirectObject import DirectObject +try: + import openvr + OPENVR_AVAILABLE = True +except ImportError: + OPENVR_AVAILABLE = False + + +class VRJoystickManager(DirectObject): + """VR摇杆管理器 - 处理手柄摇杆的转向和传送功能""" + + def __init__(self, vr_manager): + """初始化VR摇杆管理器 + + Args: + vr_manager: VR管理器实例 + """ + super().__init__() + + self.vr_manager = vr_manager + self.teleport_system = None # 传送系统引用,稍后初始化 + + # 摇杆参数配置 + self.deadzone = 0.15 # 摇杆死区 (0-1) + self.turn_threshold = 0.3 # 转向激活阈值 + self.teleport_threshold = 0.5 # 传送激活阈值 + self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度 + self.smooth_turning = True # 是否平滑转向 + self.snap_turn_angle = 30.0 # 分段转向角度(度) + + # 摇杆状态跟踪 + self.left_joystick_state = JoystickState() + self.right_joystick_state = JoystickState() + + # 转向状态 + self.left_turn_cooldown = 0.0 # 分段转向冷却时间 + self.right_turn_cooldown = 0.0 + self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒) + + # 传送状态 + self.active_teleport_controller = None # 正在传送的控制器 + self.teleport_preview_active = False # 传送预览是否激活 + + # 互斥状态管理 - 防止同时触发多种操作 + self.interaction_mode = 'none' # 当前交互模式: 'none', 'turning', 'teleporting' + self.left_controller_mode = 'none' # 左手控制器状态 + self.right_controller_mode = 'none' # 右手控制器状态 + self.mode_lock_timeout = 0.1 # 模式锁定超时时间(秒) + self.left_mode_timer = 0.0 # 左手模式计时器 + self.right_mode_timer = 0.0 # 右手模式计时器 + + print("✓ VR摇杆管理器初始化完成") + + def initialize(self, teleport_system): + """初始化摇杆系统 + + Args: + teleport_system: VR传送系统实例 + """ + self.teleport_system = teleport_system + print("✅ VR摇杆系统初始化成功") + + def update(self, dt): + """更新摇杆系统 - 每帧调用 + + Args: + dt: 帧间隔时间(秒) + """ + if not self.vr_manager.are_controllers_connected(): + return + + # 调试计数器 + if not hasattr(self, '_debug_frame_count'): + self._debug_frame_count = 0 + print("🎮 VR摇杆系统开始更新") + + self._debug_frame_count += 1 + + # 更新转向冷却时间 + if self.left_turn_cooldown > 0: + self.left_turn_cooldown -= dt + if self.right_turn_cooldown > 0: + self.right_turn_cooldown -= dt + + # 更新互斥状态计时器 + self._update_interaction_modes(dt) + + # 处理左手控制器摇杆 + if self.vr_manager.left_controller: + self._update_controller_joystick( + self.vr_manager.left_controller, + self.left_joystick_state, + 'left', + dt + ) + + # 处理右手控制器摇杆 + if self.vr_manager.right_controller: + self._update_controller_joystick( + self.vr_manager.right_controller, + self.right_joystick_state, + 'right', + dt + ) + + # 每5秒输出一次状态报告 + if self._debug_frame_count % 300 == 1: # 假设60fps + self._print_debug_status() + + def _update_controller_joystick(self, controller, joystick_state, hand, dt): + """更新单个控制器的摇杆状态 + + Args: + controller: VR控制器实例 + joystick_state: 摇杆状态对象 + hand: 'left' 或 'right' + dt: 帧间隔时间 + """ + # 获取摇杆输入 + joystick_input = self._get_joystick_input(controller, hand) + if joystick_input is None: + return + + # 应用死区 + filtered_input = self._apply_deadzone(joystick_input) + + # 更新摇杆状态 + joystick_state.update(filtered_input) + + # 处理转向(左右移动) + self._handle_turning(filtered_input, hand, dt) + + # 处理传送(向前移动) + self._handle_teleport(controller, filtered_input, joystick_state, hand) + + def _update_interaction_modes(self, dt): + """更新互斥交互模式状态""" + # 更新左手模式计时器 + if self.left_mode_timer > 0: + self.left_mode_timer -= dt + if self.left_mode_timer <= 0: + self.left_controller_mode = 'none' + + # 更新右手模式计时器 + if self.right_mode_timer > 0: + self.right_mode_timer -= dt + if self.right_mode_timer <= 0: + self.right_controller_mode = 'none' + + # 更新全局交互模式 + if self.left_controller_mode == 'none' and self.right_controller_mode == 'none': + if self.interaction_mode != 'none': + # 所有操作结束,恢复自由模式 + self.interaction_mode = 'none' + print("🔓 摇杆交互模式解锁,恢复自由操作") + + def _set_controller_mode(self, hand, mode): + """设置控制器交互模式 + + Args: + hand: 'left' 或 'right' + mode: 'turning', 'teleporting', 'none' + """ + if hand == 'left': + if self.left_controller_mode != mode: + old_mode = self.left_controller_mode + self.left_controller_mode = mode + self.left_mode_timer = self.mode_lock_timeout + if mode != 'none': + print(f"🔒 左手控制器: {old_mode} → {mode}模式") + else: + print(f"🔓 左手控制器解锁: {old_mode} → 自由") + else: + if self.right_controller_mode != mode: + old_mode = self.right_controller_mode + self.right_controller_mode = mode + self.right_mode_timer = self.mode_lock_timeout + if mode != 'none': + print(f"🔒 右手控制器: {old_mode} → {mode}模式") + else: + print(f"🔓 右手控制器解锁: {old_mode} → 自由") + + # 更新全局模式 + if mode != 'none' and self.interaction_mode != mode: + self.interaction_mode = mode + + def _can_use_mode(self, hand, requested_mode): + """检查是否可以使用指定的交互模式 + + Args: + hand: 'left' 或 'right' + requested_mode: 'turning' 或 'teleporting' + + Returns: + bool: 是否可以使用该模式 + """ + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + + # 如果当前控制器已经是该模式,允许继续 + if current_mode == requested_mode: + return True + + # 如果当前控制器是空闲的,且全局模式兼容,允许切换 + if current_mode == 'none': + if self.interaction_mode == 'none' or self.interaction_mode == requested_mode: + return True + + # 其他情况不允许 + return False + + def _get_joystick_input(self, controller, hand): + """获取摇杆输入 + + Args: + controller: VR控制器实例 + hand: 'left' 或 'right' + + Returns: + Vec2: 摇杆位置 (x, y) 或 None + """ + # 直接从控制器读取摇杆输入(绕过动作系统) + joystick_input = Vec2(0, 0) + + # 优先读取joystick_pos + if hasattr(controller, 'joystick_pos') and controller.joystick_pos: + joystick_input = Vec2(controller.joystick_pos.x, controller.joystick_pos.y) + # 检查是否有有效输入 + if joystick_input.length() > 0.01: + # 调试输出 - 仅在有输入时显示 + if not hasattr(self, '_last_debug_time'): + self._last_debug_time = 0 + self._debug_counter = 0 + + self._debug_counter += 1 + # 每30帧输出一次调试信息 + if self._debug_counter % 30 == 1: + print(f"🎮 {hand}手摇杆输入: ({joystick_input.x:.3f}, {joystick_input.y:.3f})") + + return joystick_input + + # 备选方案:读取touchpad_pos(Quest等设备) + if hasattr(controller, 'touchpad_pos') and controller.touchpad_pos: + touchpad_input = Vec2(controller.touchpad_pos.x, controller.touchpad_pos.y) + # 检查是否有有效输入 + if touchpad_input.length() > 0.01: + # 调试输出 - 仅在有输入时显示 + if not hasattr(self, '_last_debug_time'): + self._last_debug_time = 0 + self._debug_counter = 0 + + self._debug_counter += 1 + # 每30帧输出一次调试信息 + if self._debug_counter % 30 == 1: + print(f"🎮 {hand}手触摸板输入: ({touchpad_input.x:.3f}, {touchpad_input.y:.3f})") + + return touchpad_input + + # 可选:尝试从动作系统获取(如果可用) + if (self.vr_manager.action_manager and + hasattr(self.vr_manager.action_manager, 'get_analog_action_value')): + try: + device_path = f'/user/hand/{hand}' + + # 尝试摇杆 + joystick_value, _ = self.vr_manager.action_manager.get_analog_action_value('joystick', device_path) + if joystick_value is not None: + return Vec2(joystick_value.x, joystick_value.y) + + # 尝试触摸板 + trackpad_value, _ = self.vr_manager.action_manager.get_analog_action_value('trackpad', device_path) + if trackpad_value is not None: + return Vec2(trackpad_value.x, trackpad_value.y) + except Exception: + # 静默忽略动作系统错误 + pass + + return Vec2(0, 0) + + def _apply_deadzone(self, input_vec): + """应用摇杆死区 + + Args: + input_vec: 原始摇杆输入 + + Returns: + Vec2: 应用死区后的输入 + """ + magnitude = input_vec.length() + + if magnitude < self.deadzone: + return Vec2(0, 0) + + # 重新映射到 [0, 1] 范围 + normalized_magnitude = (magnitude - self.deadzone) / (1.0 - self.deadzone) + normalized_magnitude = min(normalized_magnitude, 1.0) + + if magnitude > 0: + direction = input_vec / magnitude + return direction * normalized_magnitude + + return Vec2(0, 0) + + def _handle_turning(self, input_vec, hand, dt): + """处理摇杆转向 + + Args: + input_vec: 摇杆输入向量 + hand: 'left' 或 'right' + dt: 帧间隔时间 + """ + # 检查是否超过转向阈值 + if abs(input_vec.x) < self.turn_threshold: + # 没有转向输入,重置该控制器的转向模式 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode == 'turning': + self._set_controller_mode(hand, 'none') + return + + # 检查是否可以使用转向模式 + if not self._can_use_mode(hand, 'turning'): + # 当前控制器被传送锁定,忽略转向输入 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode != 'turning': + print(f"⛔ {hand}手转向被阻止 - 当前模式: {current_mode}") + return + + # 激活转向模式 + self._set_controller_mode(hand, 'turning') + + # 检查冷却时间(分段转向) + cooldown = self.left_turn_cooldown if hand == 'left' else self.right_turn_cooldown + + if self.smooth_turning: + # 平滑转向 - 反转方向:摇杆右移(+x)应该向右转(-angle) + turn_amount = -input_vec.x * self.turn_sensitivity * dt + self._apply_rotation(turn_amount) + + # 调试输出转向 + if not hasattr(self, '_turn_debug_counter'): + self._turn_debug_counter = 0 + self._turn_debug_counter += 1 + if self._turn_debug_counter % 60 == 1: # 每秒输出一次 + print(f"🔄 {hand}手转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°") + else: + # 分段转向 + if cooldown <= 0: + # 反转方向:摇杆右移应该向右转 + turn_amount = -self.snap_turn_angle if input_vec.x > 0 else self.snap_turn_angle + self._apply_rotation(turn_amount) + + print(f"🔄 {hand}手分段转向: 输入={input_vec.x:.3f}, 角度={turn_amount:.1f}°") + + # 设置冷却时间 + if hand == 'left': + self.left_turn_cooldown = self.snap_turn_cooldown + else: + self.right_turn_cooldown = self.snap_turn_cooldown + + def _apply_rotation(self, angle_degrees): + """应用旋转到VR跟踪空间 + + Args: + angle_degrees: 旋转角度(度) + """ + if not self.vr_manager.tracking_space: + return + + # 绕Z轴旋转(垂直轴) + current_h = self.vr_manager.tracking_space.getH() + new_h = current_h + angle_degrees + self.vr_manager.tracking_space.setH(new_h) + + def _handle_teleport(self, controller, input_vec, joystick_state, hand): + """处理摇杆传送 + + Args: + controller: VR控制器实例 + input_vec: 摇杆输入向量 + joystick_state: 摇杆状态对象 + hand: 'left' 或 'right' + """ + # 检查Y轴向前移动是否超过阈值 + forward_input = input_vec.y + + if forward_input > self.teleport_threshold: + # 检查是否可以使用传送模式 + if not self._can_use_mode(hand, 'teleporting'): + # 当前控制器被转向锁定,忽略传送输入 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode != 'teleporting': + print(f"⛔ {hand}手传送被阻止 - 当前模式: {current_mode}") + return + + # 激活传送模式 + self._set_controller_mode(hand, 'teleporting') + + # 开始或更新传送预览 + if not joystick_state.teleport_active: + joystick_state.teleport_active = True + self.active_teleport_controller = controller + self.teleport_preview_active = True + + # 触发震动反馈 + controller.trigger_haptic_feedback(0.002, 0.3) + + # 计算传送方向 + direction = self._calculate_teleport_direction(controller, input_vec) + + # 更新传送预览 + if self.teleport_system: + if joystick_state.teleport_just_started: + self.teleport_system.start_teleport_preview(controller, direction) + joystick_state.teleport_just_started = False + else: + self.teleport_system.update_teleport_preview(controller, direction) + + else: + # 检查是否需要执行传送 + if joystick_state.teleport_active: + # 松开摇杆,执行传送 + self._execute_teleport(controller) + joystick_state.teleport_active = False + joystick_state.teleport_just_started = True + + # 重置该控制器的传送模式 + self._set_controller_mode(hand, 'none') + else: + # 没有传送输入,检查是否需要重置传送模式 + current_mode = self.left_controller_mode if hand == 'left' else self.right_controller_mode + if current_mode == 'teleporting': + self._set_controller_mode(hand, 'none') + + def _calculate_teleport_direction(self, controller, input_vec): + """计算传送方向向量 - 基于手柄姿态 + + Args: + controller: VR控制器实例 + input_vec: 摇杆输入向量(仅用于激活,不影响方向) + + Returns: + Vec3: 世界坐标中的传送方向 + """ + # 方法1:直接使用手柄指向(推荐) + if controller and hasattr(controller, 'get_forward_direction'): + try: + # 获取手柄的前向方向 + controller_forward = controller.get_forward_direction() + + # 确保方向向量在水平面上 + horizontal_direction = Vec3(controller_forward.x, controller_forward.y, 0) + + if horizontal_direction.length() > 0: + horizontal_direction.normalize() + + # 调试输出(每30帧输出一次) + if not hasattr(self, '_direction_debug_counter'): + self._direction_debug_counter = 0 + + self._direction_debug_counter += 1 + if self._direction_debug_counter % 30 == 1: + # 获取当前tracking_space的旋转角度用于调试 + tracking_rotation = 0 + if self.vr_manager.tracking_space: + tracking_rotation = self.vr_manager.tracking_space.getH() + + print(f"🎯 综合传送方向: 视角旋转({tracking_rotation:.1f}°) + 手柄姿态({controller_forward.x:.2f},{controller_forward.y:.2f},{controller_forward.z:.2f}) → 最终方向({horizontal_direction.x:.2f},{horizontal_direction.y:.2f})") + + return horizontal_direction + + except Exception as e: + print(f"⚠️ 获取手柄方向失败: {e}") + + # 备选方案:使用玩家朝向(保留原逻辑作为备选) + print("🔄 使用备选传送方向计算...") + + # 获取玩家当前朝向的变换矩阵 + player_transform = None + + # 优先使用头显的世界变换 + if self.vr_manager.hmd_anchor and hasattr(self.vr_manager, 'world') and self.vr_manager.world: + player_transform = self.vr_manager.hmd_anchor.getMat(self.vr_manager.world.render) + # 备选:使用tracking_space的世界变换 + elif self.vr_manager.tracking_space and hasattr(self.vr_manager, 'world') and self.vr_manager.world: + player_transform = self.vr_manager.tracking_space.getMat(self.vr_manager.world.render) + + if player_transform: + # 从变换矩阵提取前向向量 + forward = Vec3(player_transform.getRow3(1)) # Y轴 = 前向 + + # 确保向量在水平面上 + forward.z = 0 + if forward.length() > 0: + forward.normalize() + return forward + + # 最终备选方案:默认前向 + print("⚠️ 无法获取任何朝向,使用默认方向") + return Vec3(0, 1, 0) + + def _execute_teleport(self, controller): + """执行传送 + + Args: + controller: VR控制器实例 + """ + if self.teleport_system and self.teleport_preview_active: + success = self.teleport_system.execute_teleport() + + if success: + # 传送成功,触发震动反馈 + controller.trigger_haptic_feedback(0.005, 0.8) + else: + # 传送失败,触发不同的震动反馈 + controller.trigger_haptic_feedback(0.001, 0.2) + + # 停止传送预览 + self.teleport_system.stop_teleport_preview() + self.teleport_preview_active = False + self.active_teleport_controller = None + + def set_turning_mode(self, smooth=True): + """设置转向模式 + + Args: + smooth: True为平滑转向,False为分段转向 + """ + self.smooth_turning = smooth + print(f"✓ 转向模式设置为: {'平滑转向' if smooth else '分段转向'}") + + def set_turn_sensitivity(self, sensitivity): + """设置转向灵敏度 + + Args: + sensitivity: 转向灵敏度(度/秒) + """ + self.turn_sensitivity = max(10.0, min(180.0, sensitivity)) + print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒") + + def apply_config(self, config): + """应用配置 + + Args: + config: VRJoystickConfig配置实例 + """ + try: + self.deadzone = config.deadzone + self.turn_threshold = config.turn_threshold + self.teleport_threshold = config.teleport_threshold + self.turn_sensitivity = config.turn_sensitivity + self.smooth_turning = (config.turn_mode.value == 'smooth') + self.snap_turn_angle = config.snap_turn_angle + self.snap_turn_cooldown = config.snap_turn_cooldown + + # 应用传送系统配置 + if self.teleport_system and hasattr(config, 'teleport_range'): + self.teleport_system.teleport_range = config.teleport_range + if hasattr(config, 'teleport_arc_resolution'): + self.teleport_system.arc_resolution = config.teleport_arc_resolution + if hasattr(config, 'teleport_initial_velocity'): + self.teleport_system.initial_velocity = config.teleport_initial_velocity + if hasattr(config, 'min_teleport_distance'): + self.teleport_system.min_teleport_distance = config.min_teleport_distance + + print("✅ 摇杆配置已成功应用") + + except Exception as e: + print(f"⚠️ 应用配置失败: {e}") + + def get_current_config(self): + """获取当前配置 + + Returns: + dict: 当前配置参数 + """ + return { + 'deadzone': self.deadzone, + 'turn_threshold': self.turn_threshold, + 'teleport_threshold': self.teleport_threshold, + 'turn_sensitivity': self.turn_sensitivity, + 'smooth_turning': self.smooth_turning, + 'snap_turn_angle': self.snap_turn_angle, + 'snap_turn_cooldown': self.snap_turn_cooldown + } + + def _print_debug_status(self): + """打印调试状态信息""" + try: + print("🔍 ======= VR摇杆调试状态 =======") + + # 控制器连接状态 + left_connected = self.vr_manager.left_controller is not None + right_connected = self.vr_manager.right_controller is not None + print(f"📱 控制器状态: 左手={left_connected}, 右手={right_connected}") + + # 检查控制器属性 + if left_connected: + left_ctrl = self.vr_manager.left_controller + has_joystick = hasattr(left_ctrl, 'joystick_pos') + has_touchpad = hasattr(left_ctrl, 'touchpad_pos') + print(f"📊 左手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}") + + if has_joystick and left_ctrl.joystick_pos: + pos = left_ctrl.joystick_pos + print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if has_touchpad and left_ctrl.touchpad_pos: + pos = left_ctrl.touchpad_pos + print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if right_connected: + right_ctrl = self.vr_manager.right_controller + has_joystick = hasattr(right_ctrl, 'joystick_pos') + has_touchpad = hasattr(right_ctrl, 'touchpad_pos') + print(f"📊 右手控制器: joystick_pos={has_joystick}, touchpad_pos={has_touchpad}") + + if has_joystick and right_ctrl.joystick_pos: + pos = right_ctrl.joystick_pos + print(f" 当前摇杆位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + if has_touchpad and right_ctrl.touchpad_pos: + pos = right_ctrl.touchpad_pos + print(f" 当前触摸板位置: ({pos.x:.3f}, {pos.y:.3f}, {pos.z:.3f})") + + # 摇杆配置 + print(f"⚙️ 摇杆配置:") + print(f" 死区: {self.deadzone}") + print(f" 转向阈值: {self.turn_threshold}") + print(f" 传送阈值: {self.teleport_threshold}") + print(f" 转向模式: {'平滑' if self.smooth_turning else '分段'}") + + # 动作系统状态 + action_mgr_available = (self.vr_manager.action_manager is not None) + print(f"🎯 动作系统: {'可用' if action_mgr_available else '不可用'}") + + # 传送系统状态 + teleport_available = (self.teleport_system is not None) + print(f"🚀 传送系统: {'可用' if teleport_available else '不可用'}") + + print("🔍 ==============================") + + except Exception as e: + print(f"⚠️ 调试状态输出失败: {e}") + + def cleanup(self): + """清理摇杆系统资源""" + try: + # 停止任何活跃的传送预览 + if self.teleport_preview_active and self.teleport_system: + self.teleport_system.stop_teleport_preview() + + self.teleport_preview_active = False + self.active_teleport_controller = None + + self.ignoreAll() + print("🧹 VR摇杆系统已清理") + + except Exception as e: + print(f"⚠️ 清理摇杆系统失败: {e}") + + +class JoystickState: + """摇杆状态跟踪类""" + + def __init__(self): + self.current_input = Vec2(0, 0) # 当前摇杆输入 + self.previous_input = Vec2(0, 0) # 上一帧摇杆输入 + self.teleport_active = False # 传送是否激活 + self.teleport_just_started = True # 传送是否刚开始 + + def update(self, new_input): + """更新摇杆状态 + + Args: + new_input: 新的摇杆输入 + """ + self.previous_input = Vec2(self.current_input) + self.current_input = Vec2(new_input) + + def is_input_changed(self, threshold=0.01): + """检查输入是否发生变化 + + Args: + threshold: 变化阈值 + + Returns: + bool: 是否发生变化 + """ + diff = self.current_input - self.previous_input + return diff.length() > threshold \ No newline at end of file diff --git a/core/vr_joystick_config.py b/core/vr_joystick_config.py new file mode 100644 index 00000000..13401e59 --- /dev/null +++ b/core/vr_joystick_config.py @@ -0,0 +1,268 @@ +""" +VR摇杆配置模块 + +提供摇杆交互的配置选项和预设: +- 不同的转向模式和灵敏度设置 +- 传送参数调整 +- 用户体验优化选项 +""" + +from enum import Enum + + +class TurnMode(Enum): + """转向模式枚举""" + SMOOTH = "smooth" # 平滑转向 + SNAP = "snap" # 分段转向 + + +class JoystickProfile(Enum): + """摇杆配置预设""" + COMFORTABLE = "comfortable" # 舒适模式 - 低灵敏度,平滑转向 + STANDARD = "standard" # 标准模式 - 中等灵敏度,分段转向 + GAMING = "gaming" # 游戏模式 - 高灵敏度,快速响应 + ACCESSIBLE = "accessible" # 无障碍模式 - 更大死区,更高阈值 + + +class VRJoystickConfig: + """VR摇杆配置类""" + + def __init__(self): + """初始化默认配置""" + # 基础参数 + self.deadzone = 0.15 # 摇杆死区 (0-1) + self.turn_threshold = 0.3 # 转向激活阈值 + self.teleport_threshold = 0.5 # 传送激活阈值 + + # 转向设置 + self.turn_mode = TurnMode.SNAP # 转向模式 + self.turn_sensitivity = 250.0 # 转向灵敏度(度/秒)- 增加速度 + self.snap_turn_angle = 30.0 # 分段转向角度(度) + self.snap_turn_cooldown = 0.3 # 分段转向间隔(秒) + + # 传送设置 + self.teleport_range = 20.0 # 最大传送距离 + self.teleport_arc_resolution = 50 # 抛物线精度 + self.teleport_initial_velocity = 10.0 # 传送初始速度 + self.min_teleport_distance = 1.0 # 最小传送距离 + + # 反馈设置 + self.haptic_feedback_enabled = True # 是否启用震动反馈 + self.teleport_start_haptic = (0.002, 0.3) # 传送开始震动 (时长, 强度) + self.teleport_success_haptic = (0.005, 0.8) # 传送成功震动 + self.teleport_fail_haptic = (0.001, 0.2) # 传送失败震动 + + # 可视化设置 + self.show_teleport_arc = True # 显示传送抛物线 + self.show_teleport_target = True # 显示传送目标标记 + self.arc_valid_color = (0.2, 0.9, 0.2, 0.8) # 有效抛物线颜色 + self.arc_invalid_color = (0.9, 0.2, 0.2, 0.8) # 无效抛物线颜色 + + def apply_profile(self, profile: JoystickProfile): + """应用预设配置 + + Args: + profile: 配置预设 + """ + if profile == JoystickProfile.COMFORTABLE: + self._apply_comfortable_profile() + elif profile == JoystickProfile.STANDARD: + self._apply_standard_profile() + elif profile == JoystickProfile.GAMING: + self._apply_gaming_profile() + elif profile == JoystickProfile.ACCESSIBLE: + self._apply_accessible_profile() + + def _apply_comfortable_profile(self): + """舒适模式 - 适合长时间使用""" + self.deadzone = 0.2 + self.turn_threshold = 0.4 + self.teleport_threshold = 0.6 + self.turn_mode = TurnMode.SMOOTH + self.turn_sensitivity = 100.0 # 适中的转向速度 + self.snap_turn_cooldown = 0.4 + print("✓ 已应用舒适模式配置") + + def _apply_standard_profile(self): + """标准模式 - 平衡的体验""" + self.deadzone = 0.15 + self.turn_threshold = 0.3 + self.teleport_threshold = 0.5 + self.turn_mode = TurnMode.SNAP + self.turn_sensitivity = 150.0 # 更快的转向速度 + self.snap_turn_angle = 30.0 + self.snap_turn_cooldown = 0.3 + print("✓ 已应用标准模式配置") + + def _apply_gaming_profile(self): + """游戏模式 - 快速响应""" + self.deadzone = 0.1 + self.turn_threshold = 0.2 + self.teleport_threshold = 0.4 + self.turn_mode = TurnMode.SMOOTH + self.turn_sensitivity = 200.0 # 高速转向 + self.snap_turn_cooldown = 0.2 + print("✓ 已应用游戏模式配置") + + def _apply_accessible_profile(self): + """无障碍模式 - 更容易控制""" + self.deadzone = 0.25 + self.turn_threshold = 0.5 + self.teleport_threshold = 0.7 + self.turn_mode = TurnMode.SNAP + self.turn_sensitivity = 80.0 # 较慢但可控的转向 + self.snap_turn_angle = 45.0 + self.snap_turn_cooldown = 0.5 + print("✓ 已应用无障碍模式配置") + + def set_turn_mode(self, mode: TurnMode): + """设置转向模式 + + Args: + mode: 转向模式 + """ + self.turn_mode = mode + print(f"✓ 转向模式设置为: {mode.value}") + + def set_turn_sensitivity(self, sensitivity: float): + """设置转向灵敏度 + + Args: + sensitivity: 转向灵敏度(度/秒) + """ + self.turn_sensitivity = max(10.0, min(180.0, sensitivity)) + print(f"✓ 转向灵敏度设置为: {self.turn_sensitivity}度/秒") + + def set_deadzone(self, deadzone: float): + """设置摇杆死区 + + Args: + deadzone: 死区大小 (0-1) + """ + self.deadzone = max(0.0, min(0.5, deadzone)) + print(f"✓ 摇杆死区设置为: {self.deadzone}") + + def set_teleport_range(self, range_meters: float): + """设置传送范围 + + Args: + range_meters: 传送范围(米) + """ + self.teleport_range = max(5.0, min(50.0, range_meters)) + print(f"✓ 传送范围设置为: {self.teleport_range}米") + + def enable_haptic_feedback(self, enabled: bool): + """启用或禁用震动反馈 + + Args: + enabled: 是否启用 + """ + self.haptic_feedback_enabled = enabled + print(f"✓ 震动反馈: {'启用' if enabled else '禁用'}") + + def get_config_dict(self): + """获取配置字典 + + Returns: + dict: 配置参数字典 + """ + return { + 'deadzone': self.deadzone, + 'turn_threshold': self.turn_threshold, + 'teleport_threshold': self.teleport_threshold, + 'turn_mode': self.turn_mode.value, + 'turn_sensitivity': self.turn_sensitivity, + 'snap_turn_angle': self.snap_turn_angle, + 'snap_turn_cooldown': self.snap_turn_cooldown, + 'teleport_range': self.teleport_range, + 'haptic_feedback_enabled': self.haptic_feedback_enabled + } + + def apply_to_joystick_manager(self, joystick_manager): + """将配置应用到摇杆管理器 + + Args: + joystick_manager: VRJoystickManager实例 + """ + try: + joystick_manager.deadzone = self.deadzone + joystick_manager.turn_threshold = self.turn_threshold + joystick_manager.teleport_threshold = self.teleport_threshold + joystick_manager.turn_sensitivity = self.turn_sensitivity + joystick_manager.smooth_turning = (self.turn_mode == TurnMode.SMOOTH) + joystick_manager.snap_turn_angle = self.snap_turn_angle + joystick_manager.snap_turn_cooldown = self.snap_turn_cooldown + + # 应用传送系统配置 + if hasattr(joystick_manager, 'teleport_system') and joystick_manager.teleport_system: + teleport_sys = joystick_manager.teleport_system + teleport_sys.teleport_range = self.teleport_range + teleport_sys.arc_resolution = self.teleport_arc_resolution + teleport_sys.initial_velocity = self.teleport_initial_velocity + teleport_sys.min_teleport_distance = self.min_teleport_distance + + print("✅ 配置已成功应用到摇杆管理器") + + except Exception as e: + print(f"⚠️ 应用配置失败: {e}") + + +# 预定义的配置实例 +COMFORTABLE_CONFIG = VRJoystickConfig() +COMFORTABLE_CONFIG.apply_profile(JoystickProfile.COMFORTABLE) + +STANDARD_CONFIG = VRJoystickConfig() +STANDARD_CONFIG.apply_profile(JoystickProfile.STANDARD) + +GAMING_CONFIG = VRJoystickConfig() +GAMING_CONFIG.apply_profile(JoystickProfile.GAMING) + +ACCESSIBLE_CONFIG = VRJoystickConfig() +ACCESSIBLE_CONFIG.apply_profile(JoystickProfile.ACCESSIBLE) + + +def create_custom_config(**kwargs): + """创建自定义配置 + + Args: + **kwargs: 配置参数 + + Returns: + VRJoystickConfig: 自定义配置实例 + """ + config = VRJoystickConfig() + + for key, value in kwargs.items(): + if hasattr(config, key): + setattr(config, key, value) + else: + print(f"⚠️ 未知的配置参数: {key}") + + return config + + +def print_usage_guide(): + """打印使用指南""" + print("🎮 ======= VR摇杆交互使用指南 =======") + print() + print("📋 基本操作:") + print(" • 摇杆左右移动 → 转向(旋转视角)") + print(" • 摇杆向前推 → 显示传送抛物线") + print(" • 松开摇杆 → 执行传送到落点") + print() + print("⚙️ 配置选项:") + print(" • 舒适模式 → 低灵敏度,适合长时间使用") + print(" • 标准模式 → 平衡体验,推荐大多数用户") + print(" • 游戏模式 → 高灵敏度,快速响应") + print(" • 无障碍模式 → 更大死区,容易控制") + print() + print("🎯 使用建议:") + print(" • 首次使用建议从标准模式开始") + print(" • 根据个人喜好调整转向模式(平滑/分段)") + print(" • 可以随时调整死区和灵敏度") + print(" • 传送范围可根据场景大小调整") + print() + + +if __name__ == "__main__": + print_usage_guide() \ No newline at end of file diff --git a/core/vr_manager.py b/core/vr_manager.py index dbd9f59d..ab1bc036 100644 --- a/core/vr_manager.py +++ b/core/vr_manager.py @@ -9,6 +9,7 @@ VR管理器模块 """ import sys +import gc import numpy as np from panda3d.core import ( WindowProperties, GraphicsPipe, FrameBufferProperties, @@ -27,10 +28,12 @@ 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): @@ -56,11 +59,38 @@ class VRManager(DirectObject): 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.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 @@ -68,6 +98,21 @@ class VRManager(DirectObject): 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 @@ -89,23 +134,248 @@ class VRManager(DirectObject): 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动作系统 - self.action_manager = VRActionManager(self) + # VR动作系统 - 可选择禁用(用于API兼容性问题) + self.disable_action_system = True # 禁用动作系统,使用直接控制器输入 - # VR交互系统 - self.interaction_manager = VRInteractionManager(self) + 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矩阵 - 基于参考实现 @@ -149,6 +419,11 @@ class VRManager(DirectObject): 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: @@ -162,13 +437,98 @@ class VRManager(DirectObject): return False # 获取推荐的渲染目标尺寸 - self.eye_width, self.eye_height = self.vr_system.getRecommendedRenderTargetSize() - print(f"✓ VR渲染目标尺寸: {self.eye_width}x{self.eye_height}") + base_width, base_height = self.vr_system.getRecommendedRenderTargetSize() + self.base_eye_width = base_width + self.base_eye_height = base_height - # 创建OpenVR姿态数组 + # 应用分辨率缩放 + 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() - print("✓ VR姿态数组已创建") + self.poses = poses_t() # 渲染姿态(预测的) + self.game_poses = poses_t() # 游戏逻辑姿态(当前的) + print("✓ VR渲染和游戏姿态数组已创建") # 创建VR渲染缓冲区 if not self._create_vr_buffers(): @@ -180,16 +540,59 @@ class VRManager(DirectObject): print("❌ 设置VR相机失败") return False + # 优化VR渲染管线 + self._optimize_vr_rendering() + # 初始化手柄控制器 self._initialize_controllers() - # 初始化动作系统 - if not self.action_manager.initialize(): - print("⚠️ VR动作系统初始化失败,但VR系统将继续运行") + # 初始化动作系统 - 仅在启用时初始化 + 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 not self.interaction_manager.initialize(): - print("⚠️ VR交互系统初始化失败,但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() @@ -205,8 +608,14 @@ class VRManager(DirectObject): return False def _create_vr_buffers(self): - """创建VR渲染缓冲区 - 基于参考实现""" + """创建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( @@ -243,8 +652,14 @@ class VRManager(DirectObject): self.vr_right_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) # 深蓝色背景便于调试 self.vr_right_eye_buffer.setActive(True) - print("✓ VR渲染缓冲区创建成功") - return 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}") @@ -261,8 +676,63 @@ class VRManager(DirectObject): 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渲染缓冲区 - 基于参考实现""" + """创建VR渲染缓冲区 - 基于参考实现 + 性能诊断""" # 设置帧缓冲属性 fbprops = FrameBufferProperties() fbprops.setRgbaBits(1, 1, 1, 1) @@ -273,13 +743,61 @@ class VRManager(DirectObject): 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: @@ -311,16 +829,18 @@ class VRManager(DirectObject): 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) - left_dr.setDrawCallback(PythonCallbackObject(self.left_cb)) + # 恢复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) - right_dr.setDrawCallback(PythonCallbackObject(self.right_cb)) + # 恢复DrawCallback以精确控制渲染时机 + right_dr.setDrawCallback(PythonCallbackObject(self.simple_right_cb)) print("✓ VR相机锚点层级系统设置完成") return True @@ -361,23 +881,110 @@ class VRManager(DirectObject): 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) - self.vr_task = self.world.taskMgr.add(self._update_vr, "update_vr") - print("✓ VR更新任务已启动") + # 设置高优先级(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更新任务 - 每帧调用""" + """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() @@ -388,27 +995,53 @@ class VRManager(DirectObject): self.last_fps_check = self.frame_count self.last_fps_time = current_time - # 优化的VR更新顺序: - # 1. 立即调用 waitGetPoses 获取最新的姿态数据 - # 这确保我们使用最新数据而不是上一帧的数据 - self._wait_get_poses() - - # 2. 更新相机位置(使用刚获取的最新姿态数据) + # 📌 使用刚获取的姿态更新相机(而不是旧姿态) self._update_camera_poses() - # 3. 更新手柄和其他跟踪设备 - self.update_tracked_devices() + # 输出策略信息(仅第一次) + if not hasattr(self, '_new_architecture_logged'): + print("✅ 新架构已启用 - waitGetPoses 在任务开始阻塞,参考 panda3d-openvr") + print(" 这确保每个 VSync 周期只渲染一次,解决 AlreadySubmitted 错误") + self._new_architecture_logged = True - # 4. 更新VR动作状态 - self.action_manager.update_actions() + # 更新VR动作状态 - 仅在启用时更新 + if not self.disable_action_system and self.action_manager: + self.action_manager.update_actions() - # 5. 更新VR交互系统 - self.interaction_manager.update() + # 更新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)) - # 定期输出性能报告 - if self.frame_count % 1800 == 1: # 每30秒@60fps输出一次性能报告 + 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: @@ -418,6 +1051,125 @@ class VRManager(DirectObject): 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: @@ -466,6 +1218,167 @@ class VRManager(DirectObject): # 记录姿态失败次数 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: @@ -491,7 +1404,7 @@ class VRManager(DirectObject): print(f"更新追踪数据失败: {e}") def _convert_openvr_matrix_to_panda(self, ovr_matrix): - """将OpenVR矩阵转换为Panda3D矩阵 + """将OpenVR矩阵转换为Panda3D矩阵 - 使用对象池优化 坐标系转换: OpenVR: X右, Y上, -Z前(右手坐标系) @@ -502,7 +1415,8 @@ class VRManager(DirectObject): OpenVR Y → Panda3D Z OpenVR -Z → Panda3D Y """ - mat = Mat4() + # 🚀 使用对象池获取预分配的Mat4对象,避免每帧创建新对象 + mat = self._get_pooled_matrix() # 修正的坐标转换矩阵 # OpenVR: X右, Y上, -Z前 → Panda3D: X右, Y前, Z上 @@ -533,7 +1447,7 @@ class VRManager(DirectObject): 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 @@ -541,36 +1455,33 @@ class VRManager(DirectObject): if self._coord_debug_counter % 600 == 1: # 每10秒输出一次@60fps print(f"🔄 坐标系转换调试 (第{self._coord_debug_counter}帧)") - # 输出原始OpenVR矩阵信息 - ovr_pos = Vec3(ovr_matrix[0][3], ovr_matrix[1][3], ovr_matrix[2][3]) - print(f" OpenVR原始位置: {ovr_pos}") + # 直接输出数值,避免创建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})") - # 输出转换后的Panda3D矩阵信息 - # 正确的方法:从矩阵中读取位移(第4列,前3行) - panda_pos = Vec3(mat.getCell(0, 3), mat.getCell(1, 3), mat.getCell(2, 3)) - print(f" Panda3D转换位置: {panda_pos}") + # 直接从矩阵读取数值,避免创建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) - manual_converted_pos = Vec3(ovr_pos.x, -ovr_pos.z, ovr_pos.y) - print(f" 手动转换结果: {manual_converted_pos}") + # 手动验证转换: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})") - # 检查我们的矩阵是否正确设置了位置 - print(f" 矩阵位置元素: [{mat.getCell(0,3)}, {mat.getCell(1,3)}, {mat.getCell(2,3)}]") + # 计算误差,避免创建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 - # 验证转换是否正确 - expected_panda_pos = manual_converted_pos - print(f" 预期Panda3D位置: {expected_panda_pos}") - - # 检查转换是否正确 - diff = panda_pos - expected_panda_pos - diff_magnitude = diff.length() if diff_magnitude < 0.001: print(f" ✅ 坐标转换正确 (误差: {diff_magnitude:.6f})") else: print(f" ⚠️ 坐标转换可能有误 (误差: {diff_magnitude:.6f})") - print(f" 差异向量: {diff}") - print(f" 实际矩阵第4行: [{mat.getCell(3,0)}, {mat.getCell(3,1)}, {mat.getCell(3,2)}, {mat.getCell(3,3)}]") + print(f" 差异: ({diff_x:.6f}, {diff_y:.6f}, {diff_z:.6f})") return mat @@ -615,125 +1526,37 @@ class VRManager(DirectObject): import traceback traceback.print_exc() - def _submit_frames_to_vr(self): - """将渲染帧提交给VR系统""" + def _update_camera_poses_with_cache(self): + """使用缓存姿态更新相机 - 符合OpenVR时序假设""" try: - if not self.vr_compositor: - return + # 使用缓存的姿态,符合OpenVR的时序假设 + # OpenVR假设你用上一次WaitGetPoses的姿态渲染当前提交的帧 - # 使用我们创建的纹理对象 - left_texture = self.vr_left_texture - right_texture = self.vr_right_texture + if not self._cached_render_poses: + # 如果没有缓存姿态,回退到当前姿态(首帧情况) + print("⚠️ 没有缓存姿态,使用当前姿态") + return self._update_camera_poses() - if left_texture and right_texture: - # 确保纹理已准备并获取OpenGL纹理ID - gsg = self.world.win.getGsg() - prepared_objects = gsg.getPreparedObjects() + # 从缓存姿态数组中获取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) - # 准备纹理 - 使用更简单的方法 - try: - # 方法1: 尝试prepareNow - if hasattr(left_texture, 'prepareNow'): - left_texture.prepareNow(0, prepared_objects, gsg) - if hasattr(right_texture, 'prepareNow'): - right_texture.prepareNow(0, prepared_objects, gsg) - except Exception as prep_error: - print(f"纹理准备失败: {prep_error}") - # 继续尝试,可能纹理已经准备好了 - - # 获取OpenGL纹理ID - left_texture_id = self._get_texture_opengl_id(left_texture, prepared_objects, gsg) - right_texture_id = self._get_texture_opengl_id(right_texture, prepared_objects, gsg) - - # 检查是否成功获取了纹理ID - if left_texture_id is not None and left_texture_id > 0 and right_texture_id is not None and right_texture_id > 0: - try: - # 创建OpenVR纹理结构 - left_eye_texture = openvr.Texture_t() - left_eye_texture.handle = int(left_texture_id) - left_eye_texture.eType = openvr.TextureType_OpenGL - left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - - right_eye_texture = openvr.Texture_t() - right_eye_texture.handle = int(right_texture_id) - right_eye_texture.eType = openvr.TextureType_OpenGL - right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma - - # 提交到VR系统 - error_left = self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture) - error_right = self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture) - - # 检查提交结果 - 在Python OpenVR中,None表示成功 - left_success = (error_left is None or error_left == openvr.VRCompositorError_None) - right_success = (error_right is None or error_right == openvr.VRCompositorError_None) - - if not left_success: - print(f"⚠️ 左眼纹理提交错误: {error_left}") - self.submit_failures += 1 - if not right_success: - print(f"⚠️ 右眼纹理提交错误: {error_right}") - self.submit_failures += 1 - - # 如果两个都成功了,输出成功信息(仅第一次) - if not hasattr(self, '_first_submit_success'): - if left_success and right_success: - print("✅ VR纹理提交成功!缓冲区应该显示场景内容。") - print(f" 左眼纹理ID: {left_texture_id}, 右眼纹理ID: {right_texture_id}") - self._first_submit_success = True - - except Exception as submit_error: - print(f"❌ VR纹理提交过程失败: {submit_error}") - if "DoNotHaveFocus" in str(submit_error): - print("🔍 这通常意味着另一个VR应用程序正在使用VR系统") - self.submit_failures += 1 + # 调试信息 - 验证缓存姿态使用 + if not hasattr(self, '_cached_pose_logged'): + print("✅ 使用缓存姿态更新相机 - 符合OpenVR时序假设") + self._cached_pose_logged = True else: - # 只在第一次失败时输出警告,避免太多日志 - if not hasattr(self, '_texture_id_warning_shown'): - print("⚠️ 无法获取有效的纹理OpenGL ID,跳过VR帧提交") - print(" 这可能是因为纹理尚未正确准备到GPU") - self._texture_id_warning_shown = True + print("⚠️ 缓存的HMD姿态数据无效") except Exception as e: - print(f"提交VR帧失败: {e}") + print(f"使用缓存姿态更新相机失败: {e}") + # 回退到正常更新方式 + self._update_camera_poses() import traceback traceback.print_exc() - def _get_texture_opengl_id(self, texture, prepared_objects, gsg): - """获取纹理的OpenGL ID""" - try: - # 方法1: 使用prepareNow获取正确的纹理上下文 - texture_context = texture.prepareNow(0, prepared_objects, gsg) - if texture_context and hasattr(texture_context, 'getNativeId'): - native_id = texture_context.getNativeId() - if native_id > 0: - print(f"✓ 成功获取纹理 {texture.getName()} 的OpenGL ID: {native_id}") - return native_id - - # 方法2: 备选方案 - 通过prepared_objects获取texture context - if hasattr(prepared_objects, 'getTextureContext'): - texture_context = prepared_objects.getTextureContext(texture) - if texture_context and hasattr(texture_context, 'getNativeId'): - native_id = texture_context.getNativeId() - if native_id > 0: - print(f"✓ 备选方法获取纹理 {texture.getName()} 的OpenGL ID: {native_id}") - return native_id - - # 方法3: 尝试texture对象本身的方法 - if hasattr(texture, 'getNativeId'): - native_id = texture.getNativeId() - if native_id > 0: - print(f"✓ 直接获取纹理 {texture.getName()} 的OpenGL ID: {native_id}") - return native_id - - # 如果所有方法都失败 - print(f"❌ 无法获取纹理 {texture.getName()} 的OpenGL ID") - return None - - except Exception as e: - print(f"获取纹理OpenGL ID失败: {e}") - import traceback - traceback.print_exc() - return None def enable_vr(self): """启用VR模式""" @@ -750,6 +1573,19 @@ class VRManager(DirectObject): # 禁用主相机避免干扰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 @@ -757,10 +1593,42 @@ class VRManager(DirectObject): """禁用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() - print("✅ VR模式已禁用") + # 恢复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资源""" @@ -772,6 +1640,36 @@ class VRManager(DirectObject): 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() @@ -792,6 +1690,61 @@ class VRManager(DirectObject): 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: @@ -824,109 +1777,467 @@ class VRManager(DirectObject): def _print_performance_report(self): """输出VR性能报告""" - print("📊 === VR性能报告 ===") - print(f" VR帧率: {self.vr_fps:.1f} FPS") - print(f" 总帧数: {self.frame_count}") - print(f" 提交失败: {self.submit_failures}") - print(f" 姿态失败: {self.pose_failures}") + 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 self.frame_count > 0: - submit_fail_rate = (self.submit_failures / self.frame_count) * 100 - pose_fail_rate = (self.pose_failures / self.frame_count) * 100 + 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}%") - print("========================") + # 渲染管线监控 + 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") - def left_cb(self, cbdata): - """左眼渲染回调 - 基于参考实现""" - # 执行实际的渲染工作 - cbdata.upcall() - # 根据提交策略决定是否立即提交 - if not self.submit_together: - # 分别提交模式:左眼渲染完成后立即提交 - self.submit_texture(openvr.Eye_Left, self.vr_left_texture) + # 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 '禁用'}") - def right_cb(self, cbdata): - """右眼渲染回调 - 基于参考实现""" - # 执行实际的渲染工作 - cbdata.upcall() - # 根据提交策略决定提交方式 - if self.submit_together: - # 同时提交模式:右眼渲染完成后同时提交左右眼 - self.submit_texture(openvr.Eye_Left, self.vr_left_texture) - self.submit_texture(openvr.Eye_Right, self.vr_right_texture) + # 分析帧时间是否在目标范围内 + 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: - # 分别提交模式:只提交右眼 - self.submit_texture(openvr.Eye_Right, self.vr_right_texture) + 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 - 基于参考实现,增强调试信息""" + """优化的VR纹理提交 - 使用缓存的纹理ID,避免重复prepareNow""" try: if not self.vr_compositor: print("❌ VR compositor不可用") self.submit_failures += 1 return - # 获取graphics state guardian和prepared objects - gsg = self.world.win.getGsg() - if not gsg: - print("❌ 无法获取GraphicsStateGuardian") + # 🚀 关键修复:防止同一帧重复提交 + 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 - prepared_objects = gsg.getPreparedObjects() - if not prepared_objects: - print("❌ 无法获取PreparedObjects") + # 检查缓存的纹理ID是否有效 + if not handle or handle <= 0: + print(f"❌ {eye_name}纹理ID缓存无效: {handle}") + print(" 这可能表示纹理准备失败,需要检查_prepare_and_cache_textures()") self.submit_failures += 1 return - # 准备纹理并获取更详细的错误信息 - if not texture: - print("❌ 纹理对象为空") - 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 - print(f"🔍 准备纹理: {texture.getName()}, 大小: {texture.getXSize()}x{texture.getYSize()}") + # 🚀 关键优化:使用缓存的OpenVR Texture对象,避免每帧创建 + if eye == openvr.Eye_Left: + ovr_texture = self._left_ovr_texture + else: + ovr_texture = self._right_ovr_texture - texture_context = texture.prepareNow(0, prepared_objects, gsg) - if not texture_context: - print("❌ prepareNow返回空的texture_context") - self.submit_failures += 1 - return - - handle = texture_context.getNativeId() - print(f"🔍 获取OpenGL纹理句柄: {handle}") - - if handle != 0: + # 检查缓存对象是否存在(向后兼容) + if ovr_texture is None: + # 备用方案:如果缓存对象不存在,创建新的(性能较差) ovr_texture = openvr.Texture_t() - ovr_texture.handle = handle 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 - eye_name = "左眼" if eye == openvr.Eye_Left else "右眼" - print(f"🔍 提交{eye_name}纹理到VR, 句柄: {handle}") + # 只更新handle,其他属性已预设置 + ovr_texture.handle = handle - # 提交到VR系统 - error = self.vr_compositor.submit(eye, ovr_texture) + # 提交到VR系统 + error = self.vr_compositor.submit(eye, ovr_texture) - print(f"🔍 VR提交结果: {error}") - - # 检查错误 - if error and error != openvr.VRCompositorError_None: - print(f"⚠️ VR纹理提交错误代码: {error}") - self.submit_failures += 1 - else: - # 只在第一次成功时输出 - if not hasattr(self, '_submit_success_logged'): - print(f"✅ VR纹理提交成功! {eye_name}") - self._submit_success_logged = True - else: - print(f"❌ 无法获取纹理OpenGL句柄: handle = {handle}") - print(f" 纹理状态: 已准备={texture.isPrepared(prepared_objects)}") - print(f" 纹理格式: {texture.getFormat()}") + # 检查错误 + 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}") @@ -934,6 +2245,59 @@ class VRManager(DirectObject): 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: @@ -1276,4 +2640,1560 @@ class VRManager(DirectObject): Args: object_node: 要标记的对象节点 """ - self.interaction_manager._add_collision_to_object(object_node) \ No newline at end of file + 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")' + } + } \ No newline at end of file diff --git a/core/vr_teleport.py b/core/vr_teleport.py new file mode 100644 index 00000000..25dd7b53 --- /dev/null +++ b/core/vr_teleport.py @@ -0,0 +1,411 @@ +""" +VR传送系统模块 + +提供VR传送功能: +- 抛物线轨迹计算和可视化 +- 传送点有效性检测 +- 传送执行 +- 可视化反馈(抛物线、落点标记) +""" + +import math +from panda3d.core import ( + Vec3, Vec4, Mat4, Point3, CollisionRay, CollisionTraverser, + CollisionNode, CollisionHandlerQueue, BitMask32, NodePath, + CollisionSphere, CollisionPlane, LineSegs, GeomNode, Material, + RenderState, TransparencyAttrib, ColorAttrib +) +from direct.showbase.DirectObject import DirectObject + + +class VRTeleportSystem(DirectObject): + """VR传送系统 - 处理传送功能和可视化""" + + def __init__(self, vr_manager): + """初始化VR传送系统 + + Args: + vr_manager: VR管理器实例 + """ + super().__init__() + + self.vr_manager = vr_manager + self.world = vr_manager.world if hasattr(vr_manager, 'world') else None + + # 传送参数 + self.teleport_range = 20.0 # 最大传送距离 + self.arc_resolution = 50 # 抛物线精度 + self.gravity = -9.8 # 重力系数 + self.initial_velocity = 10.0 # 初始速度 + self.min_teleport_distance = 1.0 # 最小传送距离 + + # 可视化元素 + self.teleport_arc_node = None # 抛物线节点 + self.teleport_target_node = None # 落点标记节点 + self.teleport_invalid_node = None # 无效位置标记 + + # 传送状态 + self.is_teleport_active = False # 是否正在预览传送 + self.teleport_target_pos = None # 传送目标位置 + self.teleport_valid = False # 传送位置是否有效 + self.active_controller = None # 正在使用传送的控制器 + + # 碰撞检测 + self.teleport_collision_traverser = CollisionTraverser() + self.teleport_collision_queue = CollisionHandlerQueue() + + # 可视化颜色 + self.valid_arc_color = Vec4(0.2, 0.9, 0.2, 0.8) # 绿色抛物线 + self.invalid_arc_color = Vec4(0.9, 0.2, 0.2, 0.8) # 红色抛物线 + self.target_color = Vec4(0.2, 0.7, 1.0, 0.9) # 蓝色落点 + + print("✓ VR传送系统初始化完成") + + def initialize(self): + """初始化传送系统""" + try: + print("🔧 正在初始化VR传送系统...") + + # 创建可视化元素 + self._create_teleport_visuals() + + # 设置地面检测 + self._setup_ground_detection() + + print("✅ VR传送系统初始化成功") + return True + + except Exception as e: + print(f"❌ VR传送系统初始化失败: {e}") + import traceback + traceback.print_exc() + return False + + def _create_teleport_visuals(self): + """创建传送可视化元素""" + if not self.world or not hasattr(self.world, 'render'): + print("⚠️ 无法创建传送可视化 - 缺少世界渲染节点") + return + + # 创建抛物线节点 + self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") + self.teleport_arc_node.hide() # 初始隐藏 + + # 创建落点标记 + self._create_target_marker() + + # 创建无效位置标记 + self._create_invalid_marker() + + print("✓ 传送可视化元素已创建") + + def _create_target_marker(self): + """创建传送目标标记""" + try: + # 创建一个圆形平台作为落点标记 + from panda3d.core import CardMaker + + cm = CardMaker("teleport_target") + cm.setFrame(-1, 1, -1, 1) # 2x2的平面 + + self.teleport_target_node = self.world.render.attachNewNode(cm.generate()) + self.teleport_target_node.setScale(1.0) # 2米直径的圆形 + self.teleport_target_node.setP(-90) # 平放在地面 + self.teleport_target_node.setColor(self.target_color) + self.teleport_target_node.setTransparency(TransparencyAttrib.MAlpha) + self.teleport_target_node.hide() + + # 添加PBR材质以兼容RenderPipeline + material = Material() + material.setBaseColor(self.target_color) + material.setRoughness(0.8) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + self.teleport_target_node.setMaterial(material, 1) + + except Exception as e: + print(f"⚠️ 创建传送目标标记失败: {e}") + + def _create_invalid_marker(self): + """创建无效位置标记""" + try: + from panda3d.core import CardMaker + + cm = CardMaker("teleport_invalid") + cm.setFrame(-0.8, 0.8, -0.8, 0.8) # 稍小的红色叉号 + + self.teleport_invalid_node = self.world.render.attachNewNode(cm.generate()) + self.teleport_invalid_node.setScale(1.0) + self.teleport_invalid_node.setP(-90) + self.teleport_invalid_node.setColor(self.invalid_arc_color) + self.teleport_invalid_node.setTransparency(TransparencyAttrib.MAlpha) + self.teleport_invalid_node.hide() + + # 添加PBR材质 + material = Material() + material.setBaseColor(self.invalid_arc_color) + material.setRoughness(0.8) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + self.teleport_invalid_node.setMaterial(material, 1) + + except Exception as e: + print(f"⚠️ 创建无效位置标记失败: {e}") + + def _setup_ground_detection(self): + """设置地面检测""" + try: + # 假设地面在Z=0平面,使用正确的CollisionPlane API + from panda3d.core import Plane + ground_plane = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) + + ground_node = CollisionNode("ground_detection") + ground_node.addSolid(ground_plane) + ground_node.setIntoCollideMask(BitMask32.bit(1)) # 地面碰撞掩码 + + # 附加到渲染节点 + if self.world and hasattr(self.world, 'render'): + ground_node_path = self.world.render.attachNewNode(ground_node) + print("✓ 地面检测设置成功") + + except Exception as e: + print(f"⚠️ 设置地面检测失败: {e}") + # 继续运行,不让这个错误阻止传送系统 + + def start_teleport_preview(self, controller, direction): + """开始传送预览 + + Args: + controller: VR控制器实例 + direction: 传送方向向量(摇杆输入转换的世界方向) + """ + if not controller or not controller.anchor_node: + return False + + self.is_teleport_active = True + self.active_controller = controller + + # 计算抛物线轨迹 + start_pos = controller.get_world_position() + self._calculate_teleport_trajectory(start_pos, direction) + + # 显示可视化 + self._show_teleport_visuals() + + return True + + def _calculate_teleport_trajectory(self, start_pos, direction): + """计算传送抛物线轨迹 + + Args: + start_pos: 起始位置 + direction: 方向向量 + """ + try: + # 确保方向向量已标准化 + direction = direction.normalized() + + # 计算抛物线点 + trajectory_points = [] + hit_ground = False + self.teleport_valid = False + + # 初始速度向量 + velocity = direction * self.initial_velocity + velocity.z += 2.0 # 给一些向上的初始速度 + + dt = 0.05 # 时间步长 + current_pos = Vec3(start_pos) + current_velocity = Vec3(velocity) + + for i in range(self.arc_resolution): + trajectory_points.append(Point3(current_pos)) + + # 更新位置和速度 + current_pos += current_velocity * dt + current_velocity.z += self.gravity * dt # 应用重力 + + # 检查是否碰撞地面 + if current_pos.z <= 0.1: # 假设地面在z=0 + # 精确计算落地点 + ground_pos = self._calculate_ground_intersection( + trajectory_points[-2] if len(trajectory_points) > 1 else start_pos, + current_pos + ) + trajectory_points.append(ground_pos) + + # 检查传送有效性 + distance = (ground_pos - start_pos).length() + if (distance >= self.min_teleport_distance and + distance <= self.teleport_range): + self.teleport_target_pos = ground_pos + self.teleport_valid = True + else: + self.teleport_target_pos = ground_pos + self.teleport_valid = False + + hit_ground = True + break + + # 超出范围 + if (current_pos - start_pos).length() > self.teleport_range: + break + + # 如果没有碰撞地面,传送无效 + if not hit_ground: + if trajectory_points: + self.teleport_target_pos = trajectory_points[-1] + self.teleport_valid = False + + # 创建抛物线几何体 + self._create_arc_geometry(trajectory_points) + + except Exception as e: + print(f"⚠️ 计算传送轨迹失败: {e}") + self.teleport_valid = False + + def _calculate_ground_intersection(self, p1, p2): + """计算与地面的精确交点""" + if p1.z == p2.z: + return Point3(p2) + + # 线性插值找到z=0的点 + t = -p1.z / (p2.z - p1.z) + t = max(0, min(1, t)) # 限制在0-1范围内 + + intersection = p1 + (p2 - p1) * t + intersection.z = 0.1 # 稍微高于地面 + + return Point3(intersection) + + def _create_arc_geometry(self, points): + """创建抛物线几何体""" + if not points or len(points) < 2: + return + + try: + # 创建线段 + line_segs = LineSegs() + line_segs.setThickness(3) + + # 根据有效性设置颜色 + color = self.valid_arc_color if self.teleport_valid else self.invalid_arc_color + line_segs.setColor(color) + + # 添加线段点 + line_segs.moveTo(points[0]) + for point in points[1:]: + line_segs.drawTo(point) + + # 清除旧的几何体 + self.teleport_arc_node.removeNode() + self.teleport_arc_node = self.world.render.attachNewNode("teleport_arc") + + # 创建新的几何体 + geom_node = line_segs.create() + arc_node_path = self.teleport_arc_node.attachNewNode(geom_node) + + # 设置材质 + material = Material() + material.setBaseColor(color) + material.setRoughness(0.1) + material.setMetallic(0.0) + material.setRefractiveIndex(1.5) + arc_node_path.setMaterial(material, 1) + + # 设置透明度 + arc_node_path.setTransparency(TransparencyAttrib.MAlpha) + + except Exception as e: + print(f"⚠️ 创建抛物线几何体失败: {e}") + + def _show_teleport_visuals(self): + """显示传送可视化""" + if self.teleport_arc_node: + self.teleport_arc_node.show() + + if self.teleport_target_pos: + if self.teleport_valid and self.teleport_target_node: + # 显示有效的传送目标 + self.teleport_target_node.setPos(self.teleport_target_pos) + self.teleport_target_node.show() + self.teleport_invalid_node.hide() + elif self.teleport_invalid_node: + # 显示无效的传送位置 + self.teleport_invalid_node.setPos(self.teleport_target_pos) + self.teleport_invalid_node.show() + self.teleport_target_node.hide() + + def stop_teleport_preview(self): + """停止传送预览""" + self.is_teleport_active = False + self.active_controller = None + + # 隐藏可视化 + if self.teleport_arc_node: + self.teleport_arc_node.hide() + if self.teleport_target_node: + self.teleport_target_node.hide() + if self.teleport_invalid_node: + self.teleport_invalid_node.hide() + + def execute_teleport(self): + """执行传送""" + if not self.teleport_valid or not self.teleport_target_pos: + print("⚠️ 传送位置无效,无法执行传送") + return False + + try: + # 计算传送偏移 + if self.vr_manager.tracking_space: + current_pos = self.vr_manager.tracking_space.getPos() + target_offset = self.teleport_target_pos - self.active_controller.get_world_position() + new_pos = current_pos + target_offset + + # 执行传送 + self.vr_manager.tracking_space.setPos(new_pos) + + print(f"✅ 传送成功: {current_pos} → {new_pos}") + + # 停止预览 + self.stop_teleport_preview() + + return True + else: + print("⚠️ 无法获取VR跟踪空间,传送失败") + return False + + except Exception as e: + print(f"❌ 执行传送失败: {e}") + return False + + def update_teleport_preview(self, controller, direction): + """更新传送预览(摇杆移动时调用)""" + if self.is_teleport_active and controller == self.active_controller: + start_pos = controller.get_world_position() + self._calculate_teleport_trajectory(start_pos, direction) + self._show_teleport_visuals() + + def cleanup(self): + """清理传送系统资源""" + try: + self.stop_teleport_preview() + + if self.teleport_arc_node: + self.teleport_arc_node.removeNode() + self.teleport_arc_node = None + + if self.teleport_target_node: + self.teleport_target_node.removeNode() + self.teleport_target_node = None + + if self.teleport_invalid_node: + self.teleport_invalid_node.removeNode() + self.teleport_invalid_node = None + + self.ignoreAll() + + print("🧹 VR传送系统已清理") + + except Exception as e: + print(f"⚠️ 清理传送系统失败: {e}") \ No newline at end of file diff --git a/core/vr_visualization.py b/core/vr_visualization.py index 368cda81..c331a299 100644 --- a/core/vr_visualization.py +++ b/core/vr_visualization.py @@ -178,36 +178,38 @@ class VRControllerVisualizer: return None def _fix_model_material(self, model): - """修复模型材质,解决纯黑色问题同时保持纹理""" - from panda3d.core import Material, MaterialAttrib + """修复模型材质,使用RenderPipeline兼容的新Material API""" + from panda3d.core import Material, Vec4 # 检查模型是否有纹理 has_texture = model.hasTexture() - # 创建新的材质 + # 创建新的材质,使用RenderPipeline兼容的API material = Material() if has_texture: - # 有纹理时,设置材质以增强纹理效果 - material.setDiffuse((1.0, 1.0, 1.0, 1.0)) # 白色漫反射让纹理完全显示 - material.setAmbient((0.4, 0.4, 0.4, 1.0)) # 适度环境光 - material.setSpecular((0.3, 0.3, 0.3, 1.0)) # 轻度高光 - material.setShininess(16.0) # 中等光泽度 - print(f"🎨 {self.controller.name}手柄:已修复材质(保持纹理效果)") + # 有纹理时,设置白色基础颜色让纹理完全显示 + material.setBaseColor(Vec4(1.0, 1.0, 1.0, 1.0)) # 白色让纹理完全显示 + material.setRoughness(0.4) # 中等粗糙度 + material.setMetallic(0.3) # 轻度金属感 + material.setRefractiveIndex(1.5) # 标准折射率 + print(f"🎨 {self.controller.name}手柄:已设置材质(纹理+PBR)") else: - # 无纹理时,设置合适的基础颜色 - material.setDiffuse((0.7, 0.7, 0.8, 1.0)) # 略偏蓝的灰色 - material.setAmbient((0.3, 0.3, 0.3, 1.0)) # 环境光 - material.setSpecular((0.5, 0.5, 0.5, 1.0)) # 高光 - material.setShininess(32.0) # 光泽度 - print(f"🎨 {self.controller.name}手柄:已修复材质(使用颜色)") + # 无纹理时,设置手柄颜色 + material.setBaseColor(Vec4(0.7, 0.7, 0.8, 1.0)) # 略偏蓝的灰色 + material.setRoughness(0.5) # 中等粗糙度 + material.setMetallic(0.2) # 轻度金属感 + material.setRefractiveIndex(1.5) # 标准折射率 + print(f"🎨 {self.controller.name}手柄:已设置材质(颜色+PBR)") - # 应用材质到模型 - model.setMaterial(material) + # 应用材质到模型,使用优先级1确保覆盖默认材质 + model.setMaterial(material, 1) - # 确保模型能正确渲染 + # 确保模型能正确渲染(双面渲染是NodePath的方法) model.setTwoSided(False) + print(f"🔧 {self.controller.name}手柄:已使用RenderPipeline兼容的Material API") + def _apply_controller_identity_marker(self, model): """为控制器添加身份标记,区分左右手""" from panda3d.core import RenderModeAttrib @@ -494,10 +496,33 @@ class VRControllerVisualizer: # 设置透明度 self.ray_node.setTransparency(TransparencyAttrib.MAlpha) + # 使用RenderPipeline兼容的Material API设置射线材质 + from panda3d.core import Material, Vec4 + ray_material = Material() + ray_material.setBaseColor(Vec4(self.ray_color.x, self.ray_color.y, self.ray_color.z, self.ray_color.w)) + ray_material.setRoughness(0.1) # 光滑射线 + ray_material.setMetallic(0.0) # 非金属 + ray_material.setRefractiveIndex(1.5) # 标准折射率 + + # 为射线应用材质 + self.ray_node.setMaterial(ray_material, 1) + self.ray_node.setTwoSided(False) + + # 为端点球设置相同的材质 + end_material = Material() + end_material.setBaseColor(Vec4(self.ray_color.x, self.ray_color.y, self.ray_color.z, self.ray_color.w)) + end_material.setRoughness(0.2) # 略粗糙 + end_material.setMetallic(0.0) # 非金属 + end_material.setRefractiveIndex(1.5) # 标准折射率 + + # 为端点球应用材质 + end_node.setMaterial(end_material, 1) + end_node.setTwoSided(False) + # 默认隐藏射线 self.ray_node.hide() - print(f"✓ {self.controller.name}手柄交互射线已创建") + print(f"✓ {self.controller.name}手柄交互射线已创建(含PBR支持)") def _create_sphere_geometry(self, radius): """创建球体几何体""" @@ -530,6 +555,19 @@ class VRControllerVisualizer: self.trackpad_indicator.setPos(0, -0.02, 0.045) self.trackpad_indicator.setColor(0.2, 0.2, 0.2, 1.0) + # 为所有按钮指示器设置RenderPipeline兼容的材质 + from panda3d.core import Material, Vec4 + indicator_material = Material() + indicator_material.setBaseColor(Vec4(0.2, 0.2, 0.2, 1.0)) # 深灰色 + indicator_material.setRoughness(0.8) # 比较粗糙的表面 + indicator_material.setMetallic(0.1) # 轻微金属感 + indicator_material.setRefractiveIndex(1.5) # 标准折射率 + + # 为所有指示器应用材质 + for indicator in [self.trigger_indicator, self.grip_indicator, self.trackpad_indicator]: + indicator.setMaterial(indicator_material, 1) + indicator.setTwoSided(False) + print(f"✓ {self.controller.name}手柄按钮指示器已创建") def update(self): @@ -619,6 +657,24 @@ class VRControllerVisualizer: if self.ray_node: self.ray_node.setColor(color) + # 更新射线材质的baseColor + from panda3d.core import Material, Vec4 + if isinstance(color, (list, tuple)) and len(color) >= 3: + alpha = color[3] if len(color) > 3 else 0.8 + ray_color = Vec4(color[0], color[1], color[2], alpha) + elif isinstance(color, Vec4): + ray_color = color + else: + ray_color = Vec4(0.9, 0.9, 0.2, 0.8) # 默认黄色 + + # 更新射线材质 + ray_material = Material() + ray_material.setBaseColor(ray_color) + ray_material.setRoughness(0.1) + ray_material.setMetallic(0.0) + ray_material.setRefractiveIndex(1.5) + self.ray_node.setMaterial(ray_material, 1) + def set_ray_length(self, length): """设置射线长度""" self.ray_length = length @@ -652,7 +708,22 @@ class VRControllerVisualizer: def cleanup(self): """清理资源""" + # 清理射线节点 + if hasattr(self, 'ray_node') and self.ray_node: + self.ray_node.removeNode() + self.ray_node = None + print(f"🧹 {self.controller.name}手柄射线已清理") + + # 清理模型节点 + if hasattr(self, 'model_node') and self.model_node: + self.model_node.removeNode() + self.model_node = None + print(f"🧹 {self.controller.name}手柄模型已清理") + + # 清理主可视化节点 if self.visual_node: self.visual_node.removeNode() + self.visual_node = None + print(f"🧹 {self.controller.name}手柄主节点已清理") - print(f"🧹 {self.controller.name}手柄可视化已清理") \ No newline at end of file + print(f"✅ {self.controller.name}手柄可视化已彻底清理") \ No newline at end of file diff --git a/run_vr_test.sh b/run_vr_test.sh new file mode 100755 index 00000000..e6202e9a --- /dev/null +++ b/run_vr_test.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# VR性能测试启动脚本 +# 使用方法: ./run_vr_test.sh + +echo "🚀 VR性能测试启动脚本" +echo "======================" + +# 检查是否在正确的目录 +if [ ! -f "vr_performance_test.py" ]; then + echo "❌ 错误:找不到vr_performance_test.py文件" + echo "请在EG项目根目录运行此脚本" + exit 1 +fi + +# 检查Python是否可用 +if ! command -v python3 &> /dev/null; then + if ! command -v python &> /dev/null; then + echo "❌ 错误:找不到Python解释器" + exit 1 + else + PYTHON_CMD="python" + fi +else + PYTHON_CMD="python3" +fi + +echo "✓ 使用Python解释器: $PYTHON_CMD" + +# 检查核心模块是否存在 +if [ ! -d "core" ]; then + echo "❌ 错误:找不到core目录" + echo "请确保在EG项目根目录中运行" + exit 1 +fi + +if [ ! -f "core/vr_manager.py" ]; then + echo "❌ 错误:找不到core/vr_manager.py文件" + echo "VR管理器模块不存在" + exit 1 +fi + +echo "✓ 核心模块检查通过" + +# 检查VR相关依赖 +echo "🔍 检查VR依赖..." + +$PYTHON_CMD -c "import openvr" 2>/dev/null +if [ $? -eq 0 ]; then + echo "✓ OpenVR Python绑定可用" +else + echo "⚠️ 警告:OpenVR Python绑定不可用" + echo " 如果需要VR功能,请安装: pip install openvr" +fi + +$PYTHON_CMD -c "from panda3d.core import Vec3" 2>/dev/null +if [ $? -eq 0 ]; then + echo "✓ Panda3D可用" +else + echo "❌ 错误:Panda3D不可用" + echo "请安装Panda3D: pip install panda3d" + exit 1 +fi + +# 检查可选的性能监控依赖 +echo "🔍 检查性能监控依赖..." + +$PYTHON_CMD -c "import psutil" 2>/dev/null +if [ $? -eq 0 ]; then + echo "✓ psutil可用(CPU/内存监控)" +else + echo "⚠️ 建议安装psutil以获得完整性能监控: pip install psutil" +fi + +$PYTHON_CMD -c "import GPUtil" 2>/dev/null +if [ $? -eq 0 ]; then + echo "✓ GPUtil可用(GPU监控)" +else + echo "⚠️ 建议安装GPUtil以获得GPU监控: pip install GPUtil" +fi + +echo "" +echo "🎮 启动前检查清单:" +echo "□ VR头显已连接并开机" +echo "□ SteamVR正在运行" +echo "□ VR头显已完成初始设置" +echo "□ VR跟踪系统正常工作" +echo "" + +# 询问用户是否继续 +echo "是否继续启动VR性能测试?" +echo "按Enter继续,或按Ctrl+C取消..." +read -r + +echo "" +echo "🚀 正在启动VR性能测试..." +echo "控制说明:" +echo " ESC - 退出测试" +echo " 1-9 - 设置场景复杂度" +echo " R - 重置性能计数器" +echo " P - 手动输出性能报告" +echo " D - 切换调试模式" +echo "VR分辨率控制:" +echo " Q - 切换质量预设 (性能/平衡/质量)" +echo " [ - 降低分辨率缩放" +echo " ] - 提高分辨率缩放" +echo " I - 显示分辨率信息" +echo "" + +# 启动测试 +$PYTHON_CMD vr_performance_test.py + +# 检查退出状态 +if [ $? -eq 0 ]; then + echo "" + echo "✅ VR性能测试正常结束" +else + echo "" + echo "❌ VR性能测试异常退出(错误代码:$?)" +fi + +echo "📋 检查当前目录中的CSV文件获取详细性能数据" +echo "📖 查看VR_PERFORMANCE_TEST_README.md获取详细使用说明" \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index 33e3f247..541588f6 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -445,6 +445,94 @@ class MainWindow(QMainWindow): self.vrMenu.addSeparator() self.vrStatusAction = self.vrMenu.addAction('VR状态') self.vrSettingsAction = self.vrMenu.addAction('VR设置') + self.vrMenu.addSeparator() + + # VR调试子菜单 + self.vrDebugMenu = self.vrMenu.addMenu('VR调试') + self.vrDebugToggleAction = self.vrDebugMenu.addAction('启用调试输出') + self.vrDebugToggleAction.setCheckable(True) + self.vrDebugToggleAction.setChecked(False) # 默认关闭(节省资源) + + self.vrShowPerformanceAction = self.vrDebugMenu.addAction('立即显示性能报告') + + self.vrDebugMenu.addSeparator() + + # 调试模式切换 + self.vrDebugModeMenu = self.vrDebugMenu.addMenu('输出模式') + self.vrDebugBriefAction = self.vrDebugModeMenu.addAction('简短模式') + self.vrDebugDetailedAction = self.vrDebugModeMenu.addAction('详细模式') + self.vrDebugBriefAction.setCheckable(True) + self.vrDebugDetailedAction.setCheckable(True) + self.vrDebugDetailedAction.setChecked(True) # 默认详细模式 + + # 创建调试模式动作组(单选) + from PyQt5.QtWidgets import QActionGroup + self.vrDebugModeGroup = QActionGroup(self) + self.vrDebugModeGroup.addAction(self.vrDebugBriefAction) + self.vrDebugModeGroup.addAction(self.vrDebugDetailedAction) + + self.vrDebugMenu.addSeparator() + + # 性能监控选项 + self.vrPerformanceMonitorAction = self.vrDebugMenu.addAction('启用性能监控') + self.vrPerformanceMonitorAction.setCheckable(True) + self.vrPerformanceMonitorAction.setChecked(False) # 默认关闭(节省资源) + + self.vrGpuTimingAction = self.vrDebugMenu.addAction('启用GPU时间监控') + self.vrGpuTimingAction.setCheckable(True) + self.vrGpuTimingAction.setChecked(False) # 默认关闭(节省资源) + + # 管线监控选项 + self.vrPipelineMonitorAction = self.vrDebugMenu.addAction('启用管线监控') + self.vrPipelineMonitorAction.setCheckable(True) + self.vrPipelineMonitorAction.setChecked(False) # 默认关闭(节省资源) + + self.vrDebugMenu.addSeparator() + + # 姿态策略选项 + self.vrPoseStrategyMenu = self.vrDebugMenu.addMenu('姿态策略') + self.vrPoseRenderCallbackAction = self.vrPoseStrategyMenu.addAction('渲染回调策略') + self.vrPoseUpdateTaskAction = self.vrPoseStrategyMenu.addAction('更新任务策略') + self.vrPoseRenderCallbackAction.setCheckable(True) + self.vrPoseUpdateTaskAction.setCheckable(True) + self.vrPoseRenderCallbackAction.setChecked(True) # 默认策略 + + # 创建姿态策略动作组(单选) + self.vrPoseStrategyGroup = QActionGroup(self) + self.vrPoseStrategyGroup.addAction(self.vrPoseRenderCallbackAction) + self.vrPoseStrategyGroup.addAction(self.vrPoseUpdateTaskAction) + + self.vrDebugMenu.addSeparator() + + # 测试功能 + self.vrTestPipelineAction = self.vrDebugMenu.addAction('测试管线监控') + + # VR测试模式 + self.vrTestModeAction = self.vrDebugMenu.addAction('VR测试模式') + self.vrTestModeAction.setCheckable(True) + self.vrTestModeAction.setChecked(False) # 默认关闭 + + # VR测试模式调试子菜单 + self.vrTestDebugMenu = self.vrDebugMenu.addMenu('测试模式调试') + + # 渐进式功能开关 + self.vrTestSubmitTextureAction = self.vrTestDebugMenu.addAction('启用纹理提交') + self.vrTestSubmitTextureAction.setCheckable(True) + self.vrTestSubmitTextureAction.setChecked(False) # 默认关闭 + + self.vrTestWaitPosesAction = self.vrTestDebugMenu.addAction('启用姿态等待') + self.vrTestWaitPosesAction.setCheckable(True) + self.vrTestWaitPosesAction.setChecked(False) # 默认关闭 + + self.vrTestDebugMenu.addSeparator() + + # 快捷测试预设 + self.vrTestStep1Action = self.vrTestDebugMenu.addAction('步骤1: 只启用纹理提交') + self.vrTestStep2Action = self.vrTestDebugMenu.addAction('步骤2: 只启用姿态等待') + self.vrTestStep3Action = self.vrTestDebugMenu.addAction('步骤3: 同时启用两者') + self.vrTestResetAction = self.vrTestDebugMenu.addAction('重置: 禁用所有功能') + + self.vrDebugSettingsAction = self.vrDebugMenu.addAction('调试设置...') # 初始状态下禁用退出VR选项 self.exitVRAction.setEnabled(False) @@ -915,6 +1003,29 @@ class MainWindow(QMainWindow): self.vrStatusAction.triggered.connect(self.onShowVRStatus) self.vrSettingsAction.triggered.connect(self.onShowVRSettings) + # 连接VR调试菜单事件 + self.vrDebugToggleAction.triggered.connect(self.onToggleVRDebug) + self.vrShowPerformanceAction.triggered.connect(self.onShowVRPerformance) + self.vrDebugBriefAction.triggered.connect(lambda: self.onSetVRDebugMode('brief')) + self.vrDebugDetailedAction.triggered.connect(lambda: self.onSetVRDebugMode('detailed')) + self.vrPerformanceMonitorAction.triggered.connect(self.onToggleVRPerformanceMonitor) + self.vrGpuTimingAction.triggered.connect(self.onToggleVRGpuTiming) + self.vrPipelineMonitorAction.triggered.connect(self.onToggleVRPipelineMonitor) + self.vrPoseRenderCallbackAction.triggered.connect(lambda: self.onSetVRPoseStrategy('render_callback')) + self.vrPoseUpdateTaskAction.triggered.connect(lambda: self.onSetVRPoseStrategy('update_task')) + self.vrTestPipelineAction.triggered.connect(self.onTestVRPipeline) + self.vrTestModeAction.triggered.connect(self.onToggleVRTestMode) + + # 连接VR测试模式调试菜单事件 + self.vrTestSubmitTextureAction.triggered.connect(self.onToggleVRTestSubmitTexture) + self.vrTestWaitPosesAction.triggered.connect(self.onToggleVRTestWaitPoses) + self.vrTestStep1Action.triggered.connect(lambda: self.onSetVRTestStep(1)) + self.vrTestStep2Action.triggered.connect(lambda: self.onSetVRTestStep(2)) + self.vrTestStep3Action.triggered.connect(lambda: self.onSetVRTestStep(3)) + self.vrTestResetAction.triggered.connect(lambda: self.onSetVRTestStep(0)) + + self.vrDebugSettingsAction.triggered.connect(self.onShowVRDebugSettings) + def onCreateCesiumView(self): if hasattr(self.world,'gui_manager') and self.world.gui_manager: @@ -2190,6 +2301,380 @@ class MainWindow(QMainWindow): except Exception as e: QMessageBox.critical(dialog, "错误", f"应用VR设置时发生错误:\n{str(e)}") + # ==================== VR调试事件处理 ==================== + + def onToggleVRDebug(self): + """切换VR调试输出""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.world.vr_manager.toggle_debug_output() + self.vrDebugToggleAction.setChecked(enabled) + + status = "启用" if enabled else "禁用" + QMessageBox.information(self, "VR调试", f"VR调试输出已{status}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"切换VR调试时发生错误:\n{str(e)}") + + def onShowVRPerformance(self): + """立即显示VR性能报告""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + if self.world.vr_manager.vr_enabled: + self.world.vr_manager.force_performance_report() + QMessageBox.information(self, "VR性能", "性能报告已输出到控制台") + else: + QMessageBox.warning(self, "提示", "请先启用VR模式") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"显示VR性能报告时发生错误:\n{str(e)}") + + def onSetVRDebugMode(self, mode): + """设置VR调试模式""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + self.world.vr_manager.set_debug_mode(mode) + + # 更新菜单状态 + if mode == 'brief': + self.vrDebugBriefAction.setChecked(True) + self.vrDebugDetailedAction.setChecked(False) + else: + self.vrDebugBriefAction.setChecked(False) + self.vrDebugDetailedAction.setChecked(True) + + mode_name = "简短" if mode == 'brief' else "详细" + QMessageBox.information(self, "VR调试", f"调试模式已设置为:{mode_name}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"设置VR调试模式时发生错误:\n{str(e)}") + + def onToggleVRPerformanceMonitor(self): + """切换VR性能监控""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.vrPerformanceMonitorAction.isChecked() + if enabled: + self.world.vr_manager.enable_performance_monitoring() + else: + self.world.vr_manager.disable_performance_monitoring() + + status = "启用" if enabled else "禁用" + QMessageBox.information(self, "VR性能监控", f"VR性能监控已{status}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + self.vrPerformanceMonitorAction.setChecked(False) + except Exception as e: + QMessageBox.critical(self, "错误", f"切换VR性能监控时发生错误:\n{str(e)}") + + def onToggleVRGpuTiming(self): + """切换VR GPU时间监控""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.vrGpuTimingAction.isChecked() + if enabled: + self.world.vr_manager.enable_gpu_timing_monitoring() + else: + self.world.vr_manager.disable_gpu_timing_monitoring() + + status = "启用" if enabled else "禁用" + QMessageBox.information(self, "VR GPU监控", f"VR GPU时间监控已{status}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + self.vrGpuTimingAction.setChecked(False) + except Exception as e: + QMessageBox.critical(self, "错误", f"切换VR GPU时间监控时发生错误:\n{str(e)}") + + def onToggleVRPipelineMonitor(self): + """切换VR管线监控""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.vrPipelineMonitorAction.isChecked() + self.world.vr_manager.enable_pipeline_monitoring = enabled + status = "启用" if enabled else "禁用" + print(f"✓ VR管线监控已{status}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + self.vrPipelineMonitorAction.setChecked(False) + except Exception as e: + QMessageBox.critical(self, "错误", f"切换VR管线监控时发生错误:\n{str(e)}") + + def onSetVRPoseStrategy(self, strategy): + """设置VR姿态策略""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + success = self.world.vr_manager.set_pose_strategy(strategy) + if success: + strategy_names = { + 'render_callback': '渲染回调策略', + 'update_task': '更新任务策略' + } + QMessageBox.information(self, "VR姿态策略", + f"姿态策略已切换为:{strategy_names.get(strategy, strategy)}") + else: + QMessageBox.warning(self, "错误", f"无效的姿态策略:{strategy}") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"设置VR姿态策略时发生错误:\n{str(e)}") + + def onTestVRPipeline(self): + """测试VR管线监控功能""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + self.world.vr_manager.test_pipeline_monitoring() + QMessageBox.information(self, "VR管线测试", "管线监控测试已完成,请查看控制台输出。") + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"测试VR管线监控时发生错误:\n{str(e)}") + + def onShowVRDebugSettings(self): + """显示VR调试设置对话框""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + dialog = self.createVRDebugSettingsDialog() + dialog.exec_() + else: + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + QMessageBox.critical(self, "错误", f"打开VR调试设置时发生错误:\n{str(e)}") + + def createVRDebugSettingsDialog(self): + """创建VR调试设置对话框""" + from PyQt5.QtWidgets import QCheckBox, QSlider + from PyQt5.QtCore import Qt + + dialog = QDialog(self) + dialog.setWindowTitle("VR调试设置") + dialog.setModal(True) + dialog.resize(450, 400) + + layout = QVBoxLayout(dialog) + + # 获取当前设置 + vr_manager = self.world.vr_manager + debug_status = vr_manager.get_debug_status() + perf_config = vr_manager.get_performance_monitoring_config() + + # 调试状态显示 + status_group = QGroupBox("调试状态") + status_layout = QVBoxLayout() + + debug_enabled_label = QLabel(f"调试输出: {'启用' if debug_status['debug_enabled'] else '禁用'}") + debug_enabled_label.setStyleSheet(f"color: {'green' if debug_status['debug_enabled'] else 'red'};") + status_layout.addWidget(debug_enabled_label) + + debug_mode_label = QLabel(f"调试模式: {debug_status['debug_mode']}") + status_layout.addWidget(debug_mode_label) + + performance_label = QLabel(f"性能监控: {'启用' if debug_status['performance_monitoring'] else '禁用'}") + performance_label.setStyleSheet(f"color: {'green' if debug_status['performance_monitoring'] else 'red'};") + status_layout.addWidget(performance_label) + + status_group.setLayout(status_layout) + layout.addWidget(status_group) + + # 报告设置 + report_group = QGroupBox("报告设置") + report_layout = QFormLayout() + + # 报告间隔滑块 (5-120秒) + interval_slider = QSlider(Qt.Horizontal) + interval_slider.setMinimum(5) + interval_slider.setMaximum(120) + interval_slider.setValue(int(debug_status['report_interval_seconds'])) + interval_slider.setTickPosition(QSlider.TicksBelow) + interval_slider.setTickInterval(15) + + interval_label = QLabel(f"{int(debug_status['report_interval_seconds'])}秒") + interval_slider.valueChanged.connect(lambda v: interval_label.setText(f"{v}秒")) + + interval_layout = QHBoxLayout() + interval_layout.addWidget(interval_slider) + interval_layout.addWidget(interval_label) + report_layout.addRow("报告间隔:", interval_layout) + + # 性能检查间隔 + check_interval_combo = QComboBox() + check_interval_combo.addItems(["0.1秒", "0.5秒", "1.0秒", "2.0秒"]) + current_check_interval = perf_config['check_interval'] + if current_check_interval == 0.1: + check_interval_combo.setCurrentIndex(0) + elif current_check_interval == 0.5: + check_interval_combo.setCurrentIndex(1) + elif current_check_interval == 1.0: + check_interval_combo.setCurrentIndex(2) + else: + check_interval_combo.setCurrentIndex(3) + report_layout.addRow("性能检查间隔:", check_interval_combo) + + # 帧历史大小 + frame_history_spin = QSpinBox() + frame_history_spin.setMinimum(10) + frame_history_spin.setMaximum(1000) + frame_history_spin.setValue(perf_config['frame_history_size']) + frame_history_spin.setSuffix(" 帧") + report_layout.addRow("帧时间历史:", frame_history_spin) + + report_group.setLayout(report_layout) + layout.addWidget(report_group) + + # 监控项目 + monitor_group = QGroupBox("监控项目") + monitor_layout = QVBoxLayout() + + cpu_check = QCheckBox("CPU使用率") + cpu_check.setChecked(perf_config['psutil_available']) + cpu_check.setEnabled(perf_config['psutil_available']) + monitor_layout.addWidget(cpu_check) + + memory_check = QCheckBox("内存使用率") + memory_check.setChecked(perf_config['psutil_available']) + memory_check.setEnabled(perf_config['psutil_available']) + monitor_layout.addWidget(memory_check) + + gpu_check = QCheckBox("GPU使用率") + gpu_check.setChecked(perf_config['gputil_available'] or perf_config['nvidia_ml_available']) + gpu_check.setEnabled(perf_config['gputil_available'] or perf_config['nvidia_ml_available']) + monitor_layout.addWidget(gpu_check) + + frame_time_check = QCheckBox("帧时间统计") + frame_time_check.setChecked(True) + monitor_layout.addWidget(frame_time_check) + + monitor_group.setLayout(monitor_layout) + layout.addWidget(monitor_group) + + # 按钮 + button_layout = QHBoxLayout() + + apply_button = QPushButton("应用") + reset_button = QPushButton("重置计数器") + ok_button = QPushButton("确定") + cancel_button = QPushButton("取消") + + button_layout.addWidget(apply_button) + button_layout.addWidget(reset_button) + button_layout.addStretch() + button_layout.addWidget(ok_button) + button_layout.addWidget(cancel_button) + + layout.addLayout(button_layout) + + # 连接信号 + def apply_settings(): + try: + # 应用报告间隔 + new_interval_seconds = interval_slider.value() + new_interval_frames = int(new_interval_seconds * 60) # 假设60fps + vr_manager.set_performance_report_interval(new_interval_frames) + + # 应用性能检查间隔 + check_intervals = [0.1, 0.5, 1.0, 2.0] + new_check_interval = check_intervals[check_interval_combo.currentIndex()] + vr_manager.set_performance_check_interval(new_check_interval) + + # 应用帧历史大小 + vr_manager.set_frame_time_history_size(frame_history_spin.value()) + + QMessageBox.information(dialog, "成功", "VR调试设置已应用!") + except Exception as e: + QMessageBox.critical(dialog, "错误", f"应用设置时发生错误:\n{str(e)}") + + def reset_counters(): + try: + vr_manager.reset_performance_counters() + QMessageBox.information(dialog, "成功", "性能计数器已重置!") + except Exception as e: + QMessageBox.critical(dialog, "错误", f"重置计数器时发生错误:\n{str(e)}") + + apply_button.clicked.connect(apply_settings) + reset_button.clicked.connect(reset_counters) + ok_button.clicked.connect(lambda: (apply_settings(), dialog.accept())) + cancel_button.clicked.connect(dialog.reject) + + return dialog + + # ==================== VR测试模式事件处理 ==================== + + def onToggleVRTestMode(self): + """切换VR测试模式""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + if self.vrTestModeAction.isChecked(): + # 启用VR测试模式 + success = self.world.vr_manager.enable_vr_test_mode(display_mode='stereo') + if success: + QMessageBox.information(self, "VR测试模式", + "VR测试模式已启用!\n\n现在VR渲染内容将直接显示在PC屏幕上,无需VR头显。\n\n特点:\n- 显示VR左右眼视图\n- 实时性能监控HUD\n- 复用完整VR渲染管线\n- 可测量纯渲染性能") + print("✅ VR测试模式已启用") + + # 可选:自动开启性能测试 + self.world.vr_manager.run_vr_performance_test(duration_seconds=10) + else: + self.vrTestModeAction.setChecked(False) + QMessageBox.warning(self, "错误", "启用VR测试模式失败!") + else: + # 禁用VR测试模式 + self.world.vr_manager.disable_vr_test_mode() + QMessageBox.information(self, "VR测试模式", "VR测试模式已禁用") + print("✅ VR测试模式已禁用") + else: + self.vrTestModeAction.setChecked(False) + QMessageBox.warning(self, "错误", "VR管理器不可用!") + except Exception as e: + self.vrTestModeAction.setChecked(False) + QMessageBox.critical(self, "错误", f"切换VR测试模式时发生错误:\n{str(e)}") + + def onToggleVRTestSubmitTexture(self): + """切换VR测试模式纹理提交功能""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.vrTestSubmitTextureAction.isChecked() + self.world.vr_manager.set_test_mode_features(submit_texture=enabled) + except Exception as e: + QMessageBox.critical(self, "错误", f"设置纹理提交功能时发生错误:\n{str(e)}") + + def onToggleVRTestWaitPoses(self): + """切换VR测试模式姿态等待功能""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + enabled = self.vrTestWaitPosesAction.isChecked() + self.world.vr_manager.set_test_mode_features(wait_poses=enabled) + except Exception as e: + QMessageBox.critical(self, "错误", f"设置姿态等待功能时发生错误:\n{str(e)}") + + def onSetVRTestStep(self, step): + """设置VR测试步骤""" + try: + if hasattr(self.world, 'vr_manager') and self.world.vr_manager: + if step == 0: # 重置 + self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=False) + self.vrTestSubmitTextureAction.setChecked(False) + self.vrTestWaitPosesAction.setChecked(False) + QMessageBox.information(self, "VR测试", "已重置为基线状态:两个功能都禁用") + elif step == 1: # 只启用纹理提交 + self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=False) + self.vrTestSubmitTextureAction.setChecked(True) + self.vrTestWaitPosesAction.setChecked(False) + QMessageBox.information(self, "VR测试", "步骤1:只启用纹理提交\n观察FPS变化来判断submit_texture是否影响性能") + elif step == 2: # 只启用姿态等待 + self.world.vr_manager.set_test_mode_features(submit_texture=False, wait_poses=True) + self.vrTestSubmitTextureAction.setChecked(False) + self.vrTestWaitPosesAction.setChecked(True) + QMessageBox.information(self, "VR测试", "步骤2:只启用姿态等待\n观察FPS变化来判断waitGetPoses是否影响性能") + elif step == 3: # 同时启用两者 + self.world.vr_manager.set_test_mode_features(submit_texture=True, wait_poses=True) + self.vrTestSubmitTextureAction.setChecked(True) + self.vrTestWaitPosesAction.setChecked(True) + QMessageBox.information(self, "VR测试", "步骤3:同时启用两者\n这应该完全复现普通VR模式的36FPS问题") + except Exception as e: + QMessageBox.critical(self, "错误", f"设置VR测试步骤时发生错误:\n{str(e)}") + def setup_main_window(world,path = None): """设置主窗口的便利函数""" app = QApplication.instance() diff --git a/vr_actions/bindings_index.json b/vr_actions/bindings_index.json new file mode 100644 index 00000000..f7198f13 --- /dev/null +++ b/vr_actions/bindings_index.json @@ -0,0 +1,133 @@ +{ + "controller_type": "knuckles", + "description": "Valve Index\u63a7\u5236\u5668\u7ed1\u5b9a", + "name": "EG VR Editor - Index", + "bindings": { + "/actions/default": { + "sources": [ + { + "inputs": { + "click": { + "output": "/actions/default/in/Trigger" + } + }, + "mode": "button", + "path": "/user/hand/left/input/trigger" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/Trigger" + } + }, + "mode": "button", + "path": "/user/hand/right/input/trigger" + }, + { + "inputs": { + "value": { + "output": "/actions/default/in/Squeeze" + } + }, + "mode": "trigger", + "path": "/user/hand/left/input/grip" + }, + { + "inputs": { + "value": { + "output": "/actions/default/in/Squeeze" + } + }, + "mode": "trigger", + "path": "/user/hand/right/input/grip" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/AButton" + } + }, + "mode": "button", + "path": "/user/hand/right/input/a" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/BButton" + } + }, + "mode": "button", + "path": "/user/hand/right/input/b" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Trackpad" + }, + "click": { + "output": "/actions/default/in/TrackpadClick" + }, + "touch": { + "output": "/actions/default/in/TrackpadTouch" + } + }, + "mode": "trackpad", + "path": "/user/hand/left/input/trackpad" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Trackpad" + }, + "click": { + "output": "/actions/default/in/TrackpadClick" + }, + "touch": { + "output": "/actions/default/in/TrackpadTouch" + } + }, + "mode": "trackpad", + "path": "/user/hand/right/input/trackpad" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Joystick" + } + }, + "mode": "joystick", + "path": "/user/hand/left/input/thumbstick" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Joystick" + } + }, + "mode": "joystick", + "path": "/user/hand/right/input/thumbstick" + } + ], + "poses": [ + { + "output": "/actions/default/in/Pose", + "path": "/user/hand/left/pose/raw" + }, + { + "output": "/actions/default/in/Pose", + "path": "/user/hand/right/pose/raw" + } + ], + "haptics": [ + { + "output": "/actions/default/out/Haptic", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/default/out/Haptic", + "path": "/user/hand/right/output/haptic" + } + ] + } + } +} \ No newline at end of file diff --git a/vr_actions/bindings_oculus.json b/vr_actions/bindings_oculus.json new file mode 100644 index 00000000..71a697b7 --- /dev/null +++ b/vr_actions/bindings_oculus.json @@ -0,0 +1,118 @@ +{ + "controller_type": "oculus_touch", + "description": "Oculus Touch\u63a7\u5236\u5668\u7ed1\u5b9a", + "name": "EG VR Editor - Oculus Touch", + "bindings": { + "/actions/default": { + "sources": [ + { + "inputs": { + "click": { + "output": "/actions/default/in/Trigger" + } + }, + "mode": "button", + "path": "/user/hand/left/input/trigger" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/Trigger" + } + }, + "mode": "button", + "path": "/user/hand/right/input/trigger" + }, + { + "inputs": { + "value": { + "output": "/actions/default/in/Squeeze" + } + }, + "mode": "trigger", + "path": "/user/hand/left/input/grip" + }, + { + "inputs": { + "value": { + "output": "/actions/default/in/Squeeze" + } + }, + "mode": "trigger", + "path": "/user/hand/right/input/grip" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/AButton" + } + }, + "mode": "button", + "path": "/user/hand/right/input/a" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/BButton" + } + }, + "mode": "button", + "path": "/user/hand/right/input/b" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Joystick" + }, + "click": { + "output": "/actions/default/in/TrackpadClick" + } + }, + "mode": "joystick", + "path": "/user/hand/left/input/thumbstick" + }, + { + "inputs": { + "position": { + "output": "/actions/default/in/Joystick" + }, + "click": { + "output": "/actions/default/in/TrackpadClick" + } + }, + "mode": "joystick", + "path": "/user/hand/right/input/thumbstick" + }, + { + "inputs": { + "click": { + "output": "/actions/default/in/Menu" + } + }, + "mode": "button", + "path": "/user/hand/left/input/menu" + } + ], + "poses": [ + { + "output": "/actions/default/in/Pose", + "path": "/user/hand/left/pose/raw" + }, + { + "output": "/actions/default/in/Pose", + "path": "/user/hand/right/pose/raw" + } + ], + "haptics": [ + { + "output": "/actions/default/out/Haptic", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/default/out/Haptic", + "path": "/user/hand/right/output/haptic" + } + ] + } + } +} \ No newline at end of file