Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion packages/viewer/src/components/viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -66,6 +66,38 @@ const DIRTY_BUILD_KINDS = new Set([

const warnedEmptyDraw = process.env.NODE_ENV === 'production' ? null : new WeakSet<object>()

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 (
<div className="flex h-full min-h-64 w-full items-center justify-center bg-[#fafafa] p-6 text-center text-neutral-900">
<div className="max-w-md rounded-2xl border border-neutral-200 bg-white p-6 shadow-sm">
<h2 className="font-semibold text-lg">3D viewer unavailable</h2>
<p className="mt-2 text-neutral-600 text-sm">
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.
</p>
</div>
</div>
)
}

/**
* Renderer-level safety net against the empty-vertex-buffer crash.
*
Expand Down Expand Up @@ -341,6 +373,9 @@ const Viewer = forwardRef<ViewerHandle, ViewerProps>(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
Expand Down Expand Up @@ -383,6 +418,10 @@ const Viewer = forwardRef<ViewerHandle, ViewerProps>(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 <UnsupportedGpuViewerFallback />
}

return (
<Canvas
camera={{ position: [50, 50, 50], fov: 50 }}
Expand Down Expand Up @@ -410,6 +449,7 @@ const Viewer = forwardRef<ViewerHandle, ViewerProps>(function Viewer(
// rejection forever.
if (canvas) WEBGPU_RENDERER_CACHE.delete(canvas)
console.error('[viewer] WebGPURenderer init failed', err)
setRendererInitFailed(true)
throw err
}
})()
Expand Down
Loading