diff --git a/QPanda3D/Panda3DWorld.py b/QPanda3D/Panda3DWorld.py index bf6a2524..f93c74aa 100644 --- a/QPanda3D/Panda3DWorld.py +++ b/QPanda3D/Panda3DWorld.py @@ -75,6 +75,7 @@ class Panda3DWorld(ShowBase): 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): diff --git a/core/vr_manager.py b/core/vr_manager.py index 522d81b4..a9242f9b 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, @@ -56,6 +57,13 @@ 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 = {} @@ -63,12 +71,46 @@ class VRManager(DirectObject): self.poses = None # OpenVR渲染姿态数组 self.game_poses = None # OpenVR游戏逻辑姿态数组 + # 🚀 对象池和缓存系统 - 修复16-19帧周期性GPU峰值 + self._matrix_pool = [] # Mat4对象池 + self._matrix_pool_size = 8 # 池大小,足够处理多个控制器 + self._cached_matrices = {} # 设备ID到矩阵的缓存 + self._controller_poses_cache = {} # 控制器姿态缓存,避免每帧clear() + + # 🚀 OpenVR Texture对象缓存 - 避免每帧创建openvr.Texture_t() + self._left_ovr_texture = None # 左眼纹理对象缓存 + self._right_ovr_texture = None # 右眼纹理对象缓存 + + # Python垃圾回收控制 + self._gc_control_enabled = True # 是否启用GC控制 + self._gc_disabled = False # GC是否被禁用 + self._manual_gc_interval = 900 # 每900帧手动触发一次GC (15秒@60fps) - 减少GC频率 + self._last_manual_gc_frame = 0 + + # 🚀 立即初始化对象池和GC控制(在其他组件之前) + self._initialize_object_pools() + # VR渲染参数 self.eye_width = 1080 self.eye_height = 1200 self.near_clip = 0.1 self.far_clip = 1000.0 + # VR分辨率缩放优化 + self.resolution_scale = 0.75 # 默认0.75倍分辨率,性能和质量平衡 + self.base_eye_width = 1080 # 原始推荐分辨率 + self.base_eye_height = 1200 + self.scaled_eye_width = 1080 # 实际使用的缩放后分辨率 + self.scaled_eye_height = 1200 + + # VR质量预设 + self.quality_presets = { + 'performance': 0.6, # 性能模式 - 约60%分辨率 + 'balanced': 0.75, # 平衡模式 - 约75%分辨率 + 'quality': 1.0 # 质量模式 - 100%分辨率 + } + self.current_quality_preset = 'balanced' # 默认平衡模式 + # VR任务 self.vr_task = None @@ -105,10 +147,14 @@ class VRManager(DirectObject): # 渲染管线详细监控 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同步等待时间 @@ -142,10 +188,25 @@ class VRManager(DirectObject): 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.007 # 7ms的预测时间 - 优化后减少VSync等待 - self.poses_updated_in_task = 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() @@ -153,8 +214,6 @@ class VRManager(DirectObject): # VR提交策略 - 基于参考实现 self.submit_together = True # 是否在right_cb中同时提交左右眼 - # 帧同步标记 - 修复ATW闪烁 - self._poses_updated_this_frame = False # Running Start标记 - Valve最佳实践 self._waitgetposes_called_this_frame = False @@ -172,14 +231,122 @@ class VRManager(DirectObject): self.controllers = {} # 设备索引到控制器的映射 self.tracked_device_anchors = {} # 跟踪设备锚点 - # VR动作系统 - self.action_manager = VRActionManager(self) + # VR动作系统 - 添加异常保护 + try: + self.action_manager = VRActionManager(self) + print("✓ VR动作管理器初始化完成") + except Exception as e: + print(f"⚠️ VR动作管理器初始化失败: {e}") + self.action_manager = None - # VR交互系统 - self.interaction_manager = VRInteractionManager(self) + # VR交互系统 - 添加异常保护 + try: + self.interaction_manager = VRInteractionManager(self) + print("✓ VR交互管理器初始化完成") + except Exception as e: + print(f"⚠️ VR交互管理器初始化失败: {e}") + self.interaction_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矩阵 - 基于参考实现 @@ -223,6 +390,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: @@ -236,10 +408,25 @@ class VRManager(DirectObject): return False # 获取推荐的渲染目标尺寸 - self.eye_width, self.eye_height = self.vr_system.getRecommendedRenderTargetSize() + base_width, base_height = self.vr_system.getRecommendedRenderTargetSize() + self.base_eye_width = base_width + self.base_eye_height = base_height + + # 应用分辨率缩放 + self.scaled_eye_width = int(base_width * self.resolution_scale) + self.scaled_eye_height = int(base_height * self.resolution_scale) + + # 使用缩放后的分辨率作为实际渲染分辨率 + self.eye_width = self.scaled_eye_width + self.eye_height = self.scaled_eye_height + self.current_eye_resolution = (self.eye_width, self.eye_height) - self.recommended_eye_resolution = (self.eye_width, self.eye_height) - print(f"✓ VR渲染目标尺寸: {self.eye_width}x{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: @@ -324,16 +511,31 @@ 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 self.action_manager: + try: + if not self.action_manager.initialize(): + print("⚠️ VR动作系统初始化失败,但VR系统将继续运行") + except Exception as e: + print(f"⚠️ VR动作系统初始化异常: {e}") + 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交互管理器未创建,跳过交互系统初始化") # 可选:禁用异步重投影(备选方案) if self.disable_async_reprojection: @@ -353,8 +555,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( @@ -391,8 +599,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}") @@ -409,6 +623,61 @@ 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渲染缓冲区 - 基于参考实现 + 性能诊断""" # 设置帧缓冲属性 @@ -426,7 +695,9 @@ class VRManager(DirectObject): # 清除默认渲染纹理 buffer.clearRenderTextures() - # 添加我们的纹理 - 使用RTMBindOrCopy避免不必要的内存拷贝 + # 🚀 使用RTMBindOrCopy模式 - 已经是最优选择 + # RTMBindOrCopy会尝试直接绑定到纹理,只有硬件不支持时才回退到复制 + # 这已经是Panda3D中最高效的渲染到纹理模式 buffer.addRenderTexture(texture, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor) else: @@ -505,16 +776,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 @@ -555,6 +828,71 @@ 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: @@ -572,6 +910,15 @@ class VRManager(DirectObject): # 性能监控 self.frame_count += 1 + # 🚀 手动垃圾回收控制 - 避免16-19帧周期性峰值 + self._manual_gc_control() + + # 🚀 自动启用性能模式 - 减少计时对象创建 + if not self.performance_mode_enabled and self.frame_count >= self.performance_mode_trigger_frame: + self.performance_mode_enabled = True + print(f"🎯 性能模式已启用 (帧#{self.frame_count}) - 禁用详细监控以提升性能") + print(" 现在将减少每帧对象创建,显著提升VR性能稳定性") + # 记录帧时间 self._track_frame_time() @@ -592,22 +939,17 @@ class VRManager(DirectObject): if self.enable_gpu_timing: self._get_gpu_frame_timing() - # VR更新策略选择 - if self.pose_strategy == 'update_task': - # 🚀 新的Running Start策略:WaitGetPoses在Submit后调用,此处只更新相机 - # 不在此处调用WaitGetPoses,避免错过VSync窗口 - self.poses_updated_in_task = True + # 🚀 Running Start策略:WaitGetPoses在Submit后调用,此处只更新相机 + # 不在此处调用WaitGetPoses,避免错过VSync窗口 + self.poses_updated_in_task = True - # 使用上一帧获取的姿态更新相机 - self._update_camera_poses() + # 使用上一帧获取的姿态更新相机 + self._update_camera_poses() - # 输出策略信息(仅第一次) - if not hasattr(self, '_running_start_logged'): - print("✓ Running Start模式已启用 - WaitGetPoses在Submit后调用") - self._running_start_logged = True - else: - # 策略2:在渲染回调中调用waitGetPoses(传统策略) - self.poses_updated_in_task = False + # 输出策略信息(仅第一次) + if not hasattr(self, '_running_start_logged'): + print("✓ Running Start模式已启用 - WaitGetPoses在Submit后调用") + self._running_start_logged = True # 1. 更新手柄和其他跟踪设备 self.update_tracked_devices() @@ -618,7 +960,8 @@ class VRManager(DirectObject): # 3. 更新VR交互系统 self.interaction_manager.update() - # 注意:纹理提交现在通过渲染回调自动处理 + # 🚀 阶段2&3:渲染同步和纹理提交(替代DrawCallback) + self._handle_vr_rendering_and_submit() # 定期输出性能报告 - 默认10秒间隔 report_interval = getattr(self, 'performance_report_interval', 600) @@ -632,6 +975,134 @@ class VRManager(DirectObject): return task.cont + def _handle_vr_rendering_and_submit(self): + """VR帧管理 - DrawCallback现在处理实际渲染和提交""" + try: + import time + + # 记录帧处理开始时间 + frame_start_time = time.perf_counter() + + # DrawCallback会处理实际的渲染和纹理提交 + # 这里主要处理帧管理和诊断 + + # 同步等待GPU完成(如果需要) + gsg = self.world.win.getGsg() + if gsg: + gsg.getEngine().syncFrame() + + # 计算帧处理时间 + frame_time = (time.perf_counter() - frame_start_time) * 1000 + + # Running Start的备用逻辑(DrawCallback可能已处理) + # 确保每帧都有姿态获取 + if not hasattr(self, '_waitgetposes_called_this_frame') or not self._waitgetposes_called_this_frame: + pose_start = time.perf_counter() + self._wait_get_poses_immediate() + self.wait_poses_time = (time.perf_counter() - pose_start) * 1000 + self._waitgetposes_called_this_frame = True + + # 重置标记,准备下一帧 + self.world.taskMgr.doMethodLater(0.001, self._reset_waitgetposes_flag, "reset_waitgetposes_flag") + + # 调试信息(仅在启用时) + if self.debug_output_enabled and self.frame_count % 300 == 1: + print(f"🎯 帧#{self.frame_count} DrawCallback模式:") + print(f" 帧管理时间: {frame_time:.2f}ms") + print(f" 渲染由回调处理: 左眼={self.left_render_count}, 右眼={self.right_render_count}") + print(f" 姿态获取: {self.wait_poses_time:.2f}ms") + + except Exception as e: + print(f"❌ VR帧管理异常: {e}") + import traceback + traceback.print_exc() + + 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 + + # 检查避免重复提交 + current_frame = getattr(self, 'frame_count', 0) + if not hasattr(self, '_left_submitted_frame'): + self._left_submitted_frame = -1 + + if self._left_submitted_frame != current_frame: + self._left_submitted_frame = current_frame + + # 🔧 渐进式VR功能测试:根据调试标志决定是否提交纹理 + should_submit = not self.vr_test_mode or self.test_mode_submit_texture + + 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 + + # 检查避免重复提交 + current_frame = getattr(self, 'frame_count', 0) + if not hasattr(self, '_right_submitted_frame'): + self._right_submitted_frame = -1 + + if self._right_submitted_frame != current_frame: + self._right_submitted_frame = current_frame + + # 🔧 渐进式VR功能测试:根据调试标志决定启用哪些功能 + should_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 should_submit: + # 渲染后立即提交纹理(普通模式或测试模式启用提交时) + if self.vr_compositor and self.vr_right_texture: + self.submit_texture(openvr.Eye_Right, self.vr_right_texture) + + if should_wait_poses: + # Running Start:右眼提交后立即获取姿态(普通模式或测试模式启用时) + if not hasattr(self, '_waitgetposes_called_this_frame') or not self._waitgetposes_called_this_frame: + self._wait_get_poses_immediate() + self._waitgetposes_called_this_frame = True + self.world.taskMgr.doMethodLater(0.001, self._reset_waitgetposes_flag, "reset_waitgetposes_flag") + + 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: @@ -709,15 +1180,29 @@ class VRManager(DirectObject): print("⚠️ HMD姿态数据无效(立即模式)") self._hmd_invalid_warning_shown = True - # 更新控制器姿态 - self.controller_poses.clear() + # 🚀 优化控制器姿态更新:使用缓存,避免每帧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) + + # 检查是否已有此设备的缓存矩阵 + 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] # 调试信息 - 仅在第一次成功时输出 if not hasattr(self, '_dual_pose_mode_logged') and valid_poses > 0: @@ -813,10 +1298,6 @@ class VRManager(DirectObject): except Exception as e: print(f"⚠️ 姿态缓存失败: {e}") - def _reset_frame_flag(self, task): - """重置帧标记 - 确保下一帧可以更新姿态""" - self._poses_updated_this_frame = False - return task.done def _reset_waitgetposes_flag(self, task): """重置WaitGetPoses标记 - 确保下一帧可以调用WaitGetPoses""" @@ -848,7 +1329,7 @@ class VRManager(DirectObject): print(f"更新追踪数据失败: {e}") def _convert_openvr_matrix_to_panda(self, ovr_matrix): - """将OpenVR矩阵转换为Panda3D矩阵 + """将OpenVR矩阵转换为Panda3D矩阵 - 使用对象池优化 坐标系转换: OpenVR: X右, Y上, -Z前(右手坐标系) @@ -859,7 +1340,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上 @@ -890,7 +1372,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 @@ -898,36 +1380,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 @@ -1003,125 +1482,6 @@ class VRManager(DirectObject): import traceback traceback.print_exc() - def _submit_frames_to_vr(self): - """将渲染帧提交给VR系统""" - try: - if not self.vr_compositor: - return - - # 使用我们创建的纹理对象 - left_texture = self.vr_left_texture - right_texture = self.vr_right_texture - - if left_texture and right_texture: - # 确保纹理已准备并获取OpenGL纹理ID - gsg = self.world.win.getGsg() - prepared_objects = gsg.getPreparedObjects() - - # 准备纹理 - 使用更简单的方法 - 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 - else: - # 只在第一次失败时输出警告,避免太多日志 - if not hasattr(self, '_texture_id_warning_shown'): - print("⚠️ 无法获取有效的纹理OpenGL ID,跳过VR帧提交") - print(" 这可能是因为纹理尚未正确准备到GPU") - self._texture_id_warning_shown = True - - except Exception as e: - print(f"提交VR帧失败: {e}") - 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模式""" @@ -1138,11 +1498,17 @@ class VRManager(DirectObject): # 禁用主相机避免干扰VR渲染 self._disable_main_cam() - # 优化VR性能:切换到update_task姿态策略 - print("🚀 正在优化VR性能...") - self.set_pose_strategy('update_task') - self.set_prediction_time(7.0) # 7ms预测时间 - 减少VSync等待时间 - print("✓ VR性能优化完成 - 姿态策略已切换至update_task模式") + # VR性能优化:使用Running Start模式(Valve最佳实践) + print("🚀 VR性能优化:Running Start模式已启用") + print(" 优势:Submit后立即获取姿态,避免错过VSync窗口") + 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 @@ -1154,6 +1520,12 @@ class VRManager(DirectObject): # 恢复主相机 self._enable_main_cam() + # 恢复Qt Timer到60FPS + if hasattr(self.world, 'qtWidget') and self.world.qtWidget: + if hasattr(self.world.qtWidget, 'synchronizer'): + self.world.qtWidget.synchronizer.setInterval(int(1000/60)) + print("✓ Qt Timer恢复为60Hz") + print("✅ VR模式已禁用") def cleanup(self): @@ -1166,6 +1538,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() @@ -1356,35 +1758,54 @@ class VRManager(DirectObject): 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窗口,建议降低预测时间") + print(f" 可能原因: 错过VSync窗口,建议调整预测时间") elif wait_poses_time > 5: print(f" ⚠️ waitGetPoses时间偏高: {wait_poses_time:.1f}ms") else: print(f" ✓ waitGetPoses时间正常: {wait_poses_time:.1f}ms") - # CPU-GPU并行度分析 + # CPU-GPU并行度分析 - 增强诊断 gpu_total = current.get('gpu_total_render', 0) - cpu_render = current.get('total_render', 0) - if gpu_total > 0 and cpu_render > 0: - if gpu_total > cpu_render * 3: - print(f" ⚠️ GPU等待CPU: GPU{gpu_total:.1f}ms vs CPU{cpu_render:.1f}ms") + 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命令提交是否及时") - elif cpu_render > gpu_total * 2: - print(f" ⚠️ CPU瓶颈: CPU{cpu_render:.1f}ms vs GPU{gpu_total:.1f}ms") else: - print(f" ✓ CPU-GPU平衡: CPU{cpu_render:.1f}ms GPU{gpu_total:.1f}ms") + print(f" ✓ CPU-GPU时间匹配: 比例{ratio:.1f}:1") # 预测时间诊断 current_prediction = self.use_prediction_time * 1000 print(f" 预测时间设置: {current_prediction:.1f}ms") - if current_prediction > 10: + if current_prediction > 15: print(f" 建议: 预测时间较高,可能增加waitGetPoses延迟") - elif current_prediction < 5: + elif current_prediction < 8: print(f" 注意: 预测时间较低,可能影响姿态准确性") + else: + print(f" ✓ 预测时间在合理范围内") # 优化状态总结 optimization_score = 0 @@ -1412,13 +1833,11 @@ class VRManager(DirectObject): 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") + # 显示Running Start模式信息 + print(f"🎯 Running Start模式:") + print(f" 模式: Valve Running Start - Submit后立即获取姿态") + print(f" 优势: 避免VSync延迟,提升VR性能") + print(f" 预测时间: {self.use_prediction_time * 1000:.1f}ms") # 分析最大瓶颈 current = pipeline_stats['current'] @@ -1565,180 +1984,103 @@ class VRManager(DirectObject): print(summary) - def left_cb(self, cbdata): - """左眼渲染回调 - 修复ATW闪烁问题""" - # 开始计时左眼渲染 - render_timing = self._start_timing('left_render') + # 注意:原来的left_cb和right_cb函数已被删除 + # 它们的功能已集成到_handle_vr_rendering_and_submit方法中 - try: - # 根据策略决定是否在渲染回调中获取姿态 - 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: - # 根据策略决定是否在渲染回调中获取姿态 - 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) - self.submit_texture(openvr.Eye_Right, self.vr_right_texture) - else: - # 分别提交模式:只提交右眼 - self.submit_texture(openvr.Eye_Right, self.vr_right_texture) - self._end_timing(submit_timing) - - # 🚀 Valve的"Running Start"技术:Submit后立即调用WaitGetPoses - # 这是获取下一帧姿态的最佳时机,避免错过VSync窗口 - if not hasattr(self, '_waitgetposes_called_this_frame') or not self._waitgetposes_called_this_frame: - pose_timing = self._start_timing('wait_poses') - self._wait_get_poses_immediate() - self._end_timing(pose_timing) - self._waitgetposes_called_this_frame = True - # print("✓ Running Start: 在Submit后立即获取下一帧姿态") - - # 重置标记,准备下一帧 - self.world.taskMgr.doMethodLater(0.001, self._reset_waitgetposes_flag, "reset_waitgetposes_flag") - - except Exception as e: - self._end_timing(render_timing) - print(f"右眼渲染回调错误: {e}") + # 注意:_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 - # 🚀 安全的OpenGL命令刷新:在纹理提交前确保渲染命令已发送到GPU - try: - if hasattr(gsg, 'flush'): - gsg.flush() - elif hasattr(gsg, 'clear_before_callback'): - # 另一种可能的刷新方法 - pass - except Exception as flush_error: - if not hasattr(self, '_gsg_flush_error_logged'): - print(f"⚠️ GSG刷新失败: {flush_error}") - self._gsg_flush_error_logged = True + # ❌ 移除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 - # 准备纹理并获取更详细的错误信息 - if not texture: - print("❌ 纹理对象为空") - self.submit_failures += 1 - return + # 🚀 关键优化:使用缓存的OpenVR Texture对象,避免每帧创建 + if eye == openvr.Eye_Left: + ovr_texture = self._left_ovr_texture + else: + ovr_texture = self._right_ovr_texture - # 纹理准备调试信息已注释掉以减少输出 - # print(f"🔍 准备纹理: {texture.getName()}, 大小: {texture.getXSize()}x{texture.getYSize()}") - - 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}") @@ -1746,6 +2088,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: @@ -2122,19 +2517,20 @@ class VRManager(DirectObject): print("📝 异步重投影禁用选项已关闭,将使用默认ATW行为") def _start_timing(self, operation_name): - """开始计时操作""" - if not self.enable_pipeline_monitoring: + """开始计时操作 - 性能优化版本""" + 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 not timing_data: + """结束计时并记录结果 - 性能优化版本""" + if not self.enable_pipeline_monitoring or self.performance_mode_enabled or not timing_data: return 0.0 import time @@ -2232,34 +2628,6 @@ class VRManager(DirectObject): } } - 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(" 🚀 Valve Running Start模式:Submit后立即获取姿态") - print(" 优势:避免错过VSync窗口,显著降低WaitGetPoses延迟") - else: - print(" 使用传统渲染回调策略") - print(" 缺点:可能在渲染前调用WaitGetPoses,容易错过VSync") - - # 重置相关标记 - 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策略) @@ -2275,16 +2643,6 @@ class VRManager(DirectObject): 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': '在渲染回调中获取姿态 - 传统模式,可能错过VSync', - 'update_task': 'Valve Running Start模式 - Submit后立即获取姿态,避免VSync延迟' - }.get(self.pose_strategy, '未知策略') - } def test_pipeline_monitoring(self): """测试管线监控功能 - 用于调试和验证""" @@ -2297,11 +2655,10 @@ class VRManager(DirectObject): 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") + # Running Start模式信息 + print("🎯 Running Start模式:") + print(f" 预测时间: {self.use_prediction_time * 1000:.1f}ms") + print(f" 模式: Valve Running Start - Submit后立即获取姿态") # 测试管线统计 if self.enable_pipeline_monitoring: @@ -2496,19 +2853,21 @@ class VRManager(DirectObject): 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 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) + # 限制历史长度 + if len(self.frame_times) > self.max_frame_time_history: + self.frame_times.pop(0) self._last_frame_time = current_time @@ -2655,6 +3014,127 @@ class VRManager(DirectObject): 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 @@ -2692,4 +3172,703 @@ class VRManager(DirectObject): '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 + + # 设置测试模式 + 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 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/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 aa5208ad..b7dfc0f2 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -498,6 +498,11 @@ class MainWindow(QMainWindow): # 测试功能 self.vrTestPipelineAction = self.vrDebugMenu.addAction('测试管线监控') + # VR测试模式 + self.vrTestModeAction = self.vrDebugMenu.addAction('VR测试模式') + self.vrTestModeAction.setCheckable(True) + self.vrTestModeAction.setChecked(False) # 默认关闭 + self.vrDebugSettingsAction = self.vrDebugMenu.addAction('调试设置...') # 初始状态下禁用退出VR选项 @@ -978,6 +983,7 @@ class MainWindow(QMainWindow): 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) self.vrDebugSettingsAction.triggered.connect(self.onShowVRDebugSettings) @@ -2517,6 +2523,37 @@ class MainWindow(QMainWindow): 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 setup_main_window(world,path = None): """设置主窗口的便利函数""" app = QApplication.instance()