diff --git a/core/vr_controller.py b/core/vr_controller.py index 3b352207..f9e5fd64 100644 --- a/core/vr_controller.py +++ b/core/vr_controller.py @@ -153,26 +153,38 @@ class VRController(DirectObject): 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 # 更新轴状态(扳机、握把、触摸板) - if len(state.rAxis) > 0: + # OpenVR Python绑定中使用vAxis而不是rAxis + if hasattr(state, 'vAxis') and len(state.vAxis) > 0: # 扳机轴通常在axis[1].x - if len(state.rAxis) > 1: - self.trigger_value = state.rAxis[1].x + if len(state.vAxis) > 1: + self.trigger_value = state.vAxis[1].x # 触摸板轴通常在axis[0] - if len(state.rAxis) > 0: - self.touchpad_pos = Vec3(state.rAxis[0].x, state.rAxis[0].y, 0) + if len(state.vAxis) > 0: + self.touchpad_pos = Vec3(state.vAxis[0].x, state.vAxis[0].y, 0) - # 触摸板触摸状态 - self.touchpad_touched = (state.rButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 0 + # 触摸板触摸状态 - 使用正确的OpenVR属性名 + self.touchpad_touched = (state.ulButtonTouched & (1 << openvr.k_EButton_SteamVR_Touchpad)) != 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): """检查按钮是否被按下""" diff --git a/core/vr_manager.py b/core/vr_manager.py index 7a28e5f8..37c289ab 100644 --- a/core/vr_manager.py +++ b/core/vr_manager.py @@ -90,6 +90,54 @@ class VRManager(DirectObject): self.submit_failures = 0 self.pose_failures = 0 + # 高级性能监控 + self.performance_monitoring = True # 是否启用性能监控 + self.debug_output_enabled = True # 是否启用调试输出 + 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.wait_poses_time = 0.0 # waitGetPoses耗时 + self.left_render_time = 0.0 # 左眼渲染耗时 + self.right_render_time = 0.0 # 右眼渲染耗时 + self.submit_time = 0.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 + + # 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 # 运动平滑状态 + + # waitGetPoses调用策略配置 + self.pose_strategy = 'render_callback' # 'render_callback' 或 'update_task' + self.use_prediction_time = 0.011 # 11ms的预测时间(用于update_task策略) + self.poses_updated_in_task = False # 标记是否在更新任务中已获取姿态 + + # 尝试导入性能监控库 + self._init_performance_monitoring() + # VR提交策略 - 基于参考实现 self.submit_together = True # 是否在right_cb中同时提交左右眼 @@ -174,8 +222,77 @@ class VRManager(DirectObject): # 获取推荐的渲染目标尺寸 self.eye_width, self.eye_height = self.vr_system.getRecommendedRenderTargetSize() + self.current_eye_resolution = (self.eye_width, self.eye_height) + self.recommended_eye_resolution = (self.eye_width, self.eye_height) print(f"✓ VR渲染目标尺寸: {self.eye_width}x{self.eye_height}") + # 获取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() # 渲染姿态(预测的) @@ -394,6 +511,9 @@ class VRManager(DirectObject): # 性能监控 self.frame_count += 1 + # 记录帧时间 + self._track_frame_time() + # 计算VR FPS import time current_time = time.time() @@ -404,9 +524,22 @@ class VRManager(DirectObject): self.last_fps_check = self.frame_count self.last_fps_time = current_time - # 优化的VR更新顺序: - # 注意:WaitGetPoses现在在渲染回调中调用,避免ATW双重预测 - # 这里仅更新非关键的跟踪设备和交互系统 + # 更新系统性能指标 + self._update_performance_metrics() + + # VR更新策略选择 + if self.pose_strategy == 'update_task': + # 策略1:在更新任务中调用waitGetPoses(推荐用于高性能) + timing = self._start_timing('wait_poses') + self._wait_get_poses_with_prediction() + self._end_timing(timing) + self.poses_updated_in_task = True + + # 更新相机姿态 + self._update_camera_poses() + else: + # 策略2:在渲染回调中调用waitGetPoses(默认策略) + self.poses_updated_in_task = False # 1. 更新手柄和其他跟踪设备 self.update_tracked_devices() @@ -419,8 +552,9 @@ class VRManager(DirectObject): # 注意:纹理提交现在通过渲染回调自动处理 - # 定期输出性能报告 - if self.frame_count % 1800 == 1: # 每30秒@60fps输出一次性能报告 + # 定期输出性能报告 - 默认10秒间隔 + report_interval = getattr(self, 'performance_report_interval', 600) + if self.frame_count % report_interval == 1: self._print_performance_report() except Exception as e: @@ -480,14 +614,21 @@ class VRManager(DirectObject): 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) + # 结束计时 + wait_time = self._end_timing(timing) + # 检查姿态数据的有效性 valid_poses = 0 @@ -527,6 +668,56 @@ class VRManager(DirectObject): 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: @@ -830,7 +1021,7 @@ class VRManager(DirectObject): 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}") + # print(f"✓ 成功获取纹理 {texture.getName()} 的OpenGL ID: {native_id}") return native_id # 方法2: 备选方案 - 通过prepared_objects获取texture context @@ -839,14 +1030,14 @@ class VRManager(DirectObject): 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}") + # 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}") + # print(f"✓ 直接获取纹理 {texture.getName()} 的OpenGL ID: {native_id}") return native_id # 如果所有方法都失败 @@ -948,65 +1139,295 @@ 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") + + # 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") + + # VSync和时序信息 + if pipeline_stats['vr_info']['target_frame_time_ms'] > 0: + print(f"🎯 VSync时序:") + print(f" 目标帧时间: {pipeline_stats['vr_info']['target_frame_time_ms']:.2f}ms") + print(f" VSync到光子: {pipeline_stats['vr_info']['vsync_to_photons_ms']:.2f}ms") + print(f" VSync窗口: ±{pipeline_stats['vr_info']['vsync_window_ms']:.2f}ms") + print(f" 异步重投影: {'启用' if pipeline_stats['vr_info']['async_reprojection'] else '禁用'}") + print(f" 运动平滑: {'启用' if pipeline_stats['vr_info']['motion_smoothing'] else '禁用'}") + + # 分析帧时间是否在目标范围内 + current_frame_time = stats['frame_time_avg'] + target_frame_time = pipeline_stats['vr_info']['target_frame_time_ms'] + if current_frame_time > 0 and target_frame_time > 0: + frame_time_ratio = current_frame_time / target_frame_time + if frame_time_ratio > 1.1: + print(f" ⚠️ 帧时间超标: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)") + elif frame_time_ratio < 0.9: + print(f" ✅ 帧时间充裕: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)") + else: + print(f" ✓ 帧时间正常: {current_frame_time:.1f}ms (目标:{target_frame_time:.1f}ms)") + + print(f"🕐 各阶段耗时 (最近{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") + + # 显示姿态策略信息 + strategy_info = self.get_pose_strategy_info() + print(f"🎯 姿态策略:") + print(f" 当前策略: {strategy_info['strategy']}") + print(f" 策略描述: {strategy_info['description']}") + if strategy_info['strategy'] == 'update_task': + print(f" 预测时间: {strategy_info['prediction_time_ms']:.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'] < 72: + recommendations.append(" ⚠️ VR帧率过低,可能影响体验") + + # 帧时间相关建议 + if stats['frame_time_avg'] > 13.9: # 72fps = 13.9ms + 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(" 🔴 显存使用率过高,可能需要降低纹理质量") + + # 失败率建议 + 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" + + # 显示目标帧时间对比 + 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) def left_cb(self, cbdata): """左眼渲染回调 - 修复ATW闪烁问题""" + # 开始计时左眼渲染 + render_timing = self._start_timing('left_render') + try: - # 在渲染开始前立即获取最新姿态,避免ATW双重预测 - if not hasattr(self, '_poses_updated_this_frame') or not self._poses_updated_this_frame: - self._wait_get_poses_immediate() - # 缓存当前姿态供下一帧使用,符合OpenVR时序假设 - self._cache_poses_for_next_frame() - # 使用缓存的姿态更新相机(上一帧的姿态) - self._update_camera_poses_with_cache() - self._poses_updated_this_frame = True - # 在帧结束时重置标记 - self.world.taskMgr.doMethodLater(0.001, self._reset_frame_flag, "reset_frame_flag") + # 根据策略决定是否在渲染回调中获取姿态 + if self.pose_strategy == 'render_callback': + # 传统策略:在渲染开始前立即获取最新姿态 + if not hasattr(self, '_poses_updated_this_frame') or not self._poses_updated_this_frame: + self._wait_get_poses_immediate() + # 缓存当前姿态供下一帧使用,符合OpenVR时序假设 + self._cache_poses_for_next_frame() + # 使用缓存的姿态更新相机(上一帧的姿态) + self._update_camera_poses_with_cache() + self._poses_updated_this_frame = True + # 在帧结束时重置标记 + self.world.taskMgr.doMethodLater(0.001, self._reset_frame_flag, "reset_frame_flag") + elif self.poses_updated_in_task: + # 新策略:使用在更新任务中获取的姿态,无需重新获取 + pass # 执行实际的渲染工作 cbdata.upcall() + # 结束渲染计时 + self._end_timing(render_timing) + # 根据提交策略决定是否立即提交 if not self.submit_together: # 分别提交模式:左眼渲染完成后立即提交 + submit_timing = self._start_timing('submit') self.submit_texture(openvr.Eye_Left, self.vr_left_texture) + self._end_timing(submit_timing) except Exception as e: + self._end_timing(render_timing) print(f"左眼渲染回调错误: {e}") def right_cb(self, cbdata): """右眼渲染回调 - 修复ATW闪烁问题""" + # 开始计时右眼渲染 + render_timing = self._start_timing('right_render') + try: - # 在渲染开始前立即获取最新姿态,避免ATW双重预测 - # 由于左右眼都会调用回调,确保每帧只调用一次WaitGetPoses - if not hasattr(self, '_poses_updated_this_frame') or not self._poses_updated_this_frame: - self._wait_get_poses_immediate() - # 缓存当前姿态供下一帧使用,符合OpenVR时序假设 - self._cache_poses_for_next_frame() - # 使用缓存的姿态更新相机(上一帧的姿态) - self._update_camera_poses_with_cache() - self._poses_updated_this_frame = True - # 在帧结束时重置标记 - self.world.taskMgr.doMethodLater(0.001, self._reset_frame_flag, "reset_frame_flag") + # 根据策略决定是否在渲染回调中获取姿态 + if self.pose_strategy == 'render_callback': + # 传统策略:在渲染开始前立即获取最新姿态 + # 由于左右眼都会调用回调,确保每帧只调用一次WaitGetPoses + if not hasattr(self, '_poses_updated_this_frame') or not self._poses_updated_this_frame: + self._wait_get_poses_immediate() + # 缓存当前姿态供下一帧使用,符合OpenVR时序假设 + self._cache_poses_for_next_frame() + # 使用缓存的姿态更新相机(上一帧的姿态) + self._update_camera_poses_with_cache() + self._poses_updated_this_frame = True + # 在帧结束时重置标记 + self.world.taskMgr.doMethodLater(0.001, self._reset_frame_flag, "reset_frame_flag") + elif self.poses_updated_in_task: + # 新策略:使用在更新任务中获取的姿态,无需重新获取 + pass # 执行实际的渲染工作 cbdata.upcall() + # 结束渲染计时 + self._end_timing(render_timing) + # 根据提交策略决定提交方式 + submit_timing = self._start_timing('submit') if self.submit_together: # 同时提交模式:右眼渲染完成后同时提交左右眼 self.submit_texture(openvr.Eye_Left, self.vr_left_texture) @@ -1014,8 +1435,10 @@ class VRManager(DirectObject): else: # 分别提交模式:只提交右眼 self.submit_texture(openvr.Eye_Right, self.vr_right_texture) + self._end_timing(submit_timing) except Exception as e: + self._end_timing(render_timing) print(f"右眼渲染回调错误: {e}") def submit_texture(self, eye, texture): @@ -1045,7 +1468,8 @@ class VRManager(DirectObject): self.submit_failures += 1 return - print(f"🔍 准备纹理: {texture.getName()}, 大小: {texture.getXSize()}x{texture.getYSize()}") + # 纹理准备调试信息已注释掉以减少输出 + # print(f"🔍 准备纹理: {texture.getName()}, 大小: {texture.getXSize()}x{texture.getYSize()}") texture_context = texture.prepareNow(0, prepared_objects, gsg) if not texture_context: @@ -1054,7 +1478,7 @@ class VRManager(DirectObject): return handle = texture_context.getNativeId() - print(f"🔍 获取OpenGL纹理句柄: {handle}") + # print(f"🔍 获取OpenGL纹理句柄: {handle}") if handle != 0: ovr_texture = openvr.Texture_t() @@ -1063,12 +1487,12 @@ class VRManager(DirectObject): ovr_texture.eColorSpace = openvr.ColorSpace_Gamma eye_name = "左眼" if eye == openvr.Eye_Left else "右眼" - print(f"🔍 提交{eye_name}纹理到VR, 句柄: {handle}") + # print(f"🔍 提交{eye_name}纹理到VR, 句柄: {handle}") # 提交到VR系统 error = self.vr_compositor.submit(eye, ovr_texture) - print(f"🔍 VR提交结果: {error}") + # print(f"🔍 VR提交结果: {error}") # 检查错误 if error and error != openvr.VRCompositorError_None: @@ -1464,4 +1888,474 @@ class VRManager(DirectObject): def disable_async_reprojection_disable(self): """禁用异步重投影禁用选项 - 恢复默认行为""" self.disable_async_reprojection = False - print("📝 异步重投影禁用选项已关闭,将使用默认ATW行为") \ No newline at end of file + print("📝 异步重投影禁用选项已关闭,将使用默认ATW行为") + + def _start_timing(self, operation_name): + """开始计时操作""" + if not self.enable_pipeline_monitoring: + 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 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) + } + + 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), + '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 + }, + '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 + } + } + + def set_pose_strategy(self, strategy): + """设置waitGetPoses调用策略 + + Args: + strategy: 'render_callback' 或 'update_task' + """ + if strategy not in ['render_callback', 'update_task']: + print(f"⚠️ 无效的姿态策略: {strategy},支持的策略: render_callback, update_task") + return False + + old_strategy = self.pose_strategy + self.pose_strategy = strategy + + if old_strategy != strategy: + print(f"✓ VR姿态策略已切换: {old_strategy} → {strategy}") + if strategy == 'update_task': + print(f" 使用预测时间: {self.use_prediction_time*1000:.1f}ms") + print(" 优势:降低VR回调延迟,提高性能") + else: + print(" 使用传统渲染回调策略") + print(" 优势:更低延迟,更精确的姿态") + + # 重置相关标记 + if hasattr(self, '_poses_updated_this_frame'): + delattr(self, '_poses_updated_this_frame') + self.poses_updated_in_task = False + + return True + + 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 get_pose_strategy_info(self): + """获取当前姿态策略信息""" + return { + 'strategy': self.pose_strategy, + 'prediction_time_ms': self.use_prediction_time * 1000, + 'description': { + 'render_callback': '在渲染回调中获取姿态 - 低延迟,精确', + 'update_task': '在更新任务中获取姿态 - 高性能,预测' + }.get(self.pose_strategy, '未知策略') + } + + 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") + + # 测试策略信息 + strategy_info = self.get_pose_strategy_info() + print("🎯 姿态策略:") + print(f" 当前策略: {strategy_info['strategy']}") + print(f" 预测时间: {strategy_info['prediction_time_ms']:.1f}ms") + + # 测试管线统计 + 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 _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 # 转换为毫秒 + + # 添加到帧时间历史 + 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 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 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 + } \ No newline at end of file diff --git a/ui/main_window.py b/ui/main_window.py index 33e3f247..aa5208ad 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -445,6 +445,60 @@ 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(True) # 默认启用 + + 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.vrPipelineMonitorAction = self.vrDebugMenu.addAction('启用管线监控') + self.vrPipelineMonitorAction.setCheckable(True) + self.vrPipelineMonitorAction.setChecked(True) # 默认启用 + + 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('测试管线监控') + + self.vrDebugSettingsAction = self.vrDebugMenu.addAction('调试设置...') # 初始状态下禁用退出VR选项 self.exitVRAction.setEnabled(False) @@ -915,6 +969,17 @@ 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.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.vrDebugSettingsAction.triggered.connect(self.onShowVRDebugSettings) + def onCreateCesiumView(self): if hasattr(self.world,'gui_manager') and self.world.gui_manager: @@ -2190,6 +2255,268 @@ 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 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 + def setup_main_window(world,path = None): """设置主窗口的便利函数""" app = QApplication.instance()