优雅的 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]);
推荐实施顺序
- 首先尝试方案4(最简单,通常有效)
- 如果仍有问题,升级到方案3(完整的状态检测)
- 方案1和2作为底层工具函数支持