elegant-webgl-stability-solution.md 5.0 KB

优雅的 WebGL 上下文稳定性检测方案

问题:固定延迟的缺陷

  • 100ms可能不够,某些设备可能需要更长时间
  • 浪费时间,某些设备可能很快就稳定了
  • 不够精确,无法确保真正的稳定性

优雅解决方案

方案1:WebGL 上下文状态检测 + 轮询

const waitForWebGLStable = async (canvas: HTMLCanvasElement, timeout = 5000): Promise<boolean> => {
  const startTime = Date.now();
  
  return new Promise((resolve) => {
    const checkStability = () => {
      const gl = canvas.getContext('webgl') || canvas.getContext('webgl2');
      
      if (!gl) {
        if (Date.now() - startTime > timeout) {
          resolve(false);
          return;
        }
        setTimeout(checkStability, 16); // ~60fps
        return;
      }
      
      // 检查 WebGL 状态
      const isStable = 
        gl.getError() === gl.NO_ERROR && 
        gl.getParameter(gl.RENDERER) && 
        gl.getParameter(gl.VERSION);
      
      if (isStable) {
        resolve(true);
      } else if (Date.now() - startTime > timeout) {
        resolve(false);
      } else {
        setTimeout(checkStability, 16);
      }
    };
    
    checkStability();
  });
};

方案2:Cornerstone 渲染引擎就绪检测

const waitForViewportReady = async (viewport: any, timeout = 5000): Promise<boolean> => {
  const startTime = Date.now();
  
  return new Promise((resolve) => {
    const checkReady = () => {
      try {
        // 检查 viewport 是否完全初始化
        const canvas = viewport.canvas;
        const element = viewport.element;
        
        if (!canvas || !element) {
          if (Date.now() - startTime > timeout) {
            resolve(false);
            return;
          }
          setTimeout(checkReady, 16);
          return;
        }
        
        // 检查渲染状态
        const renderingState = viewport.getRenderingEngine?.()?.hasBeenDestroyed;
        if (renderingState === false) {
          resolve(true);
        } else if (Date.now() - startTime > timeout) {
          resolve(false);
        } else {
          setTimeout(checkReady, 16);
        }
      } catch (error) {
        if (Date.now() - startTime > timeout) {
          resolve(false);
        } else {
          setTimeout(checkReady, 16);
        }
      }
    };
    
    checkReady();
  });
};

方案3:组合方案 - 事件驱动 + 状态检测

const [isViewportReady, setIsViewportReady] = useState(false);

// 在创建 viewport 后检测就绪状态
useEffect(() => {
  if (!stackViewport) return;
  
  const checkViewportReady = async () => {
    try {
      // 方法1:等待canvas元素存在
      const canvas = viewportRef.current?.querySelector('canvas');
      if (!canvas) {
        setTimeout(checkViewportReady, 16);
        return;
      }
      
      // 方法2:等待WebGL上下文稳定
      const isWebGLStable = await waitForWebGLStable(canvas);
      if (!isWebGLStable) {
        console.warn('[DcmCell] WebGL上下文未稳定');
        return;
      }
      
      // 方法3:等待viewport就绪
      const isViewportStable = await waitForViewportReady(stackViewport);
      if (!isViewportStable) {
        console.warn('[DcmCell] Viewport未就绪');
        return;
      }
      
      console.log('[DcmCell] Viewport完全就绪');
      setIsViewportReady(true);
      
    } catch (error) {
      console.error('[DcmCell] 检测viewport就绪状态失败:', error);
    }
  };
  
  checkViewportReady();
}, [stackViewport]);

// 只有当viewport就绪后才加载图像
useEffect(() => {
  if (!isViewportReady || !stackViewport) return;
  
  const loadImageFromProps = async () => {
    // 避免重复加载相同图像
    if (imageId === currentImageId) return;
    
    if (imageId && imageId.trim() !== '') {
      console.log(`[DcmCell] Viewport就绪,开始加载图像: ${imageId}`);
      const success = await loadImage(imageId, 'props');
      if (success) {
        setCurrentImageId(imageId);
      }
    } else {
      // 清空逻辑...
    }
  };

  loadImageFromProps();
}, [imageId, isViewportReady, stackViewport]);

方案4:简化版 - requestAnimationFrame

const waitForStableFrame = (): Promise<void> => {
  return new Promise(resolve => {
    // 等待2-3帧确保渲染稳定
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        requestAnimationFrame(resolve);
      });
    });
  });
};

useEffect(() => {
  const loadImageFromProps = async () => {
    if (!stackViewport) return;
    
    // 等待几个渲染帧确保稳定
    await waitForStableFrame();
    
    // 避免重复加载相同图像
    if (imageId === currentImageId) return;
    
    // 继续正常的加载逻辑...
  };

  loadImageFromProps();
}, [imageId, stackViewport]);

推荐实施顺序

  1. 首先尝试方案4(最简单,通常有效)
  2. 如果仍有问题,升级到方案3(完整的状态检测)
  3. 方案1和2作为底层工具函数支持