EG/core/vr/testing/test_mode.py
2026-03-04 11:57:40 +08:00

622 lines
25 KiB
Python

"""
VR测试调试子系统
负责VR测试模式、HUD显示和性能测试功能
"""
import time
class VRTestMode:
"""VR测试模式系统
功能:
- VR内容直接显示在屏幕上(不提交给OpenVR)
- 实时性能监控HUD
- 支持左眼/右眼/立体三种显示模式
- 渐进式测试功能(纹理提交、姿态等待)
- 性能测试和调试工具
"""
def __init__(self, vr_manager):
"""初始化VR测试模式系统
Args:
vr_manager: VRManager实例的引用
"""
self.vr_manager = vr_manager
# ===== 测试模式状态 =====
self.vr_test_mode = False # 是否启用VR测试模式
self.test_display_mode = 'stereo' # 'left', 'right', 'stereo'
self.test_mode_initialized = False # 测试模式是否已初始化
# ===== 显示组件 =====
self.test_display_quad = None # 测试显示的四边形
self.test_right_quad = None # 右眼显示的四边形(立体模式)
self.stereo_display_created = False # 立体显示是否已创建
# ===== HUD组件 =====
self.test_performance_hud = None # 性能HUD
self.test_performance_text = None # 性能文本节点
self.hud_update_counter = 0 # HUD更新计数器
self.hud_update_interval = 30 # HUD更新间隔(帧数), 30帧约0.5秒@60fps
# ===== 测试模式调试选项 =====
self.test_mode_submit_texture = False # 是否在测试模式提交纹理到OpenVR
self.test_mode_wait_poses = False # 是否在测试模式调用waitGetPoses
# ===== 纹理缓存 =====
self._left_ovr_texture = None # 缓存的左眼OpenVR Texture对象
self._right_ovr_texture = None # 缓存的右眼OpenVR Texture对象
# ========== 测试模式控制方法 ==========
def enable_vr_test_mode(self, display_mode='stereo'):
"""启用VR测试模式 - 将VR渲染直接显示在屏幕上
Args:
display_mode: 显示模式
- 'stereo': 左右眼并排显示
- 'left': 只显示左眼
- 'right': 只显示右眼
"""
if not self.vr_manager.is_vr_available():
print("❌ VR系统不可用,无法启动测试模式")
return False
try:
print(f"🧪 启动VR测试模式 - 显示模式: {display_mode}")
# 初始化VR系统(如果还没初始化)
if not self.vr_manager.vr_initialized:
if not self.vr_manager.initialize_vr():
print("❌ VR初始化失败")
return False
# 🔧 关键修复:确保测试模式的纹理资源已初始化
# 这解决了测试模式纹理提交失败导致的36FPS问题
print("🔧 检查VR测试模式纹理资源...")
if not self._ensure_test_mode_textures():
print("❌ VR测试模式纹理资源初始化失败")
return False
# 设置测试模式
self.vr_test_mode = True
self.test_display_mode = display_mode
# 启用VR渲染但不提交给OpenVR
if not self.vr_manager.vr_enabled:
# 启用VR渲染流程(但会在回调中跳过提交)
self.vr_manager.vr_enabled = True
self.vr_manager._disable_main_cam()
# 设置高帧率用于测试
host_widget = getattr(self.vr_manager.world, "host_widget", None)
if host_widget and hasattr(host_widget, "synchronizer"):
host_widget.synchronizer.setInterval(int(1000 / 144))
print("✓ 测试模式:渲染同步器设置为144Hz")
# 初始化测试显示系统
if not self._initialize_test_display():
print("❌ 测试显示系统初始化失败")
return False
# 创建性能HUD
if not self._initialize_test_performance_hud():
print("❌ 性能HUD初始化失败")
return False
# 恢复主相机以查看测试内容
self.vr_manager._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_manager.vr_enabled:
print(" 选择: [1] 恢复正常VR模式 [2] 完全禁用VR")
# 这里可以让用户选择,现在默认禁用VR
self.vr_manager.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 _ensure_test_mode_textures(self):
"""确保VR测试模式的纹理资源已正确初始化
这解决了测试模式启用纹理提交时的36FPS问题:
- VR测试模式可能跳过了VR渲染缓冲区的初始化
- submit_texture()需要有效的texture ID和OpenVR Texture对象
- 如果未初始化会导致提交失败,造成帧率减半
"""
try:
print(" 检查VR渲染缓冲区...")
# 检查VR渲染缓冲区是否存在
buffers_exist = (
hasattr(self.vr_manager, 'vr_left_eye_buffer') and self.vr_manager.vr_left_eye_buffer and
hasattr(self.vr_manager, 'vr_right_eye_buffer') and self.vr_manager.vr_right_eye_buffer
)
if not buffers_exist:
print(" ⚠️ VR渲染缓冲区不存在,正在创建...")
# 🐛 BUG修复: 原代码调用了不存在的 _setup_vr_render_buffers()
# 应该调用 _create_vr_buffers() 或 _create_vr_buffers_with_pipeline()
if hasattr(self.vr_manager.world, 'render_pipeline') and self.vr_manager.world.render_pipeline:
success = self.vr_manager._create_vr_buffers_with_pipeline()
else:
success = self.vr_manager._create_vr_buffers()
if not success:
print(" ❌ VR渲染缓冲区创建失败")
return False
print(" ✅ VR渲染缓冲区创建成功")
else:
print(" ✅ VR渲染缓冲区已存在")
# 检查纹理ID是否已缓存
print(" 检查纹理ID缓存...")
texture_ids_cached = (
hasattr(self.vr_manager, 'left_texture_id') and self.vr_manager.left_texture_id and self.vr_manager.left_texture_id > 0 and
hasattr(self.vr_manager, 'right_texture_id') and self.vr_manager.right_texture_id and self.vr_manager.right_texture_id > 0
)
if not texture_ids_cached:
print(" ⚠️ 纹理ID未缓存,正在准备纹理...")
if not self.vr_manager._prepare_and_cache_textures():
print(" ❌ 纹理准备和缓存失败")
return False
print(f" ✅ 纹理ID缓存成功 - 左眼:{self.vr_manager.left_texture_id}, 右眼:{self.vr_manager.right_texture_id}")
else:
print(f" ✅ 纹理ID已缓存 - 左眼:{self.vr_manager.left_texture_id}, 右眼:{self.vr_manager.right_texture_id}")
# 检查OpenVR Texture对象是否已创建
print(" 检查OpenVR Texture对象...")
ovr_textures_exist = (
self._left_ovr_texture and self._right_ovr_texture
)
if not ovr_textures_exist:
print(" ⚠️ OpenVR Texture对象未创建,正在创建...")
self._create_cached_ovr_textures()
print(" ✅ OpenVR Texture对象创建成功")
else:
print(" ✅ OpenVR Texture对象已存在")
print(" ✅ VR测试模式纹理资源检查完成,可安全启用纹理提交")
return True
except Exception as e:
print(f" ❌ VR测试模式纹理资源检查失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_cached_ovr_textures(self):
"""创建缓存的OpenVR Texture对象 - 避免每帧创建新对象"""
try:
import openvr
self._left_ovr_texture = openvr.Texture_t()
self._right_ovr_texture = openvr.Texture_t()
# 设置固定属性(这些不变)
self._left_ovr_texture.eType = openvr.TextureType_OpenGL
self._left_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
self._right_ovr_texture.eType = openvr.TextureType_OpenGL
self._right_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
print("✅ OpenVR Texture对象缓存已创建")
except Exception as e:
print(f"⚠️ OpenVR Texture对象创建失败: {e}")
# 不抛出异常,使用备用方案
# 注意: _batch_submit_textures() 方法已移至VRManager
# 因为它是VR渲染的核心功能,不仅用于测试模式
# ========== 显示系统方法 ==========
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.vr_manager.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_manager.vr_left_texture:
self.test_display_quad.setTexture(self.vr_manager.vr_left_texture)
elif self.test_display_mode == 'right':
if not self.test_display_quad:
return
if self.vr_manager.vr_right_texture:
self.test_display_quad.setTexture(self.vr_manager.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_manager.vr_left_texture:
self.test_display_quad.setTexture(self.vr_manager.vr_left_texture)
if self.test_right_quad and self.vr_manager.vr_right_texture:
self.test_right_quad.setTexture(self.vr_manager.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.vr_manager.world.render2d.attachNewNode(left_cm.generate())
if self.vr_manager.vr_left_texture:
left_quad.setTexture(self.vr_manager.vr_left_texture)
# 创建右眼显示
right_cm = CardMaker("vr_test_right")
right_cm.setFrame(0, 1, -1, 1) # 右半屏
right_quad = self.vr_manager.world.render2d.attachNewNode(right_cm.generate())
if self.vr_manager.vr_right_texture:
right_quad.setTexture(self.vr_manager.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}")
# ========== HUD系统方法 ==========
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.vr_manager.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
# 收集性能数据
perf_mon = self.vr_manager.performance_monitor
left_render_time = perf_mon.left_render_time if perf_mon else 0
right_render_time = perf_mon.right_render_time if perf_mon else 0
total_render_time = left_render_time + right_render_time
# 计算FPS
current_fps = perf_mon.vr_fps if perf_mon else 0
# 获取系统性能
cpu_usage = perf_mon.cpu_usage if perf_mon else 0
memory_usage = perf_mon.memory_usage if perf_mon else 0
gpu_usage = perf_mon.gpu_usage if perf_mon else 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: {perf_mon.left_render_count if perf_mon else 0}
Right Eye: {perf_mon.right_render_count if perf_mon else 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
perf_mon = self.vr_manager.performance_monitor
left_render_time = perf_mon.left_render_time if perf_mon else 0
right_render_time = perf_mon.right_render_time if perf_mon else 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': perf_mon.vr_fps if perf_mon else 0,
'left_render_time': left_render_time,
'right_render_time': right_render_time,
'total_render_time': total_render_time,
'left_render_count': perf_mon.left_render_count if perf_mon else 0,
'right_render_count': perf_mon.right_render_count if perf_mon else 0,
'performance_rating': ('excellent' if total_render_time < 10 else
'good' if total_render_time < 16 else 'poor')
}
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 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 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
# 记录测试开始状态
test_start_time = time.time()
perf_mon = self.vr_manager.performance_monitor
start_frame_count = perf_mon.frame_count if perf_mon else 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.test_mode.disable_vr_test_mode()',
'check_status': 'vr_manager.test_mode.get_test_mode_status()',
'switch_mode': 'vr_manager.test_mode.switch_test_display_mode("left/right/stereo")'
}
}