diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index 6f07db6d4..f6fa42315 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -8,7 +8,7 @@ import { useScene, } from '@pascal-app/core' import { Canvas, extend, type ThreeToJSXElements, useFrame, useThree } from '@react-three/fiber' -import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react' +import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import * as THREE from 'three/webgpu' import { hasDrawableGeometry } from '../../lib/drawable-geometry' import { PERF_OVERLAY_ENABLED, pushGpuSample } from '../../lib/gpu-perf' @@ -66,6 +66,38 @@ const DIRTY_BUILD_KINDS = new Set([ const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet() +function canCreateWebGLContext() { + if (typeof document === 'undefined') return false + + const canvas = document.createElement('canvas') + try { + return Boolean(canvas.getContext('webgl2') ?? canvas.getContext('webgl')) + } catch { + return false + } +} + +function canMountGpuViewer() { + if (typeof window === 'undefined') return false + if (!('gpu' in navigator) && !canCreateWebGLContext()) return false + + return true +} + +function UnsupportedGpuViewerFallback() { + return ( +
+
+

3D viewer unavailable

+

+ This browser or environment does not expose WebGPU or WebGL, so Pascal cannot render the + 3D scene here. Try opening the editor in a browser with hardware acceleration enabled. +

+
+
+ ) +} + /** * Renderer-level safety net against the empty-vertex-buffer crash. * @@ -341,6 +373,9 @@ const Viewer = forwardRef(function Viewer( } }, [isolate]) + const [rendererInitFailed, setRendererInitFailed] = useState(false) + const canMountViewer = useMemo(canMountGpuViewer, []) + const isDark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark') const defaultShading = defaultRender?.shading const defaultTextures = defaultRender?.textures @@ -383,6 +418,10 @@ const Viewer = forwardRef(function Viewer( // Desktops (fine pointer) keep the original 1.5 cap. const maxDpr = typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches ? 1.25 : 1.5 + if (!canMountViewer || rendererInitFailed) { + return + } + return ( (function Viewer( // rejection forever. if (canvas) WEBGPU_RENDERER_CACHE.delete(canvas) console.error('[viewer] WebGPURenderer init failed', err) + setRendererInitFailed(true) throw err } })()