What is SVF2 and how it works.

Your app already translates to SVF2 and opens it with the streaming api, so this is the pipeline you are running.

SVF2 is the public name. Inside the viewer it is called OTG, and the loader classes carry that name. The key idea is that a model is no longer a package you download. It is a small index plus a content-addressed store of geometry, and the viewer pulls pieces of that store on demand.

What the translation produces. When your server posts the job with output type svf2, the derivative service converts the design into three kinds of things:

  • A fragment list per model. One record per drawable fragment: a transform, a bounding box, a material reference, a geometry reference and the dbId it belongs to. Compact binary, no geometry inside.
  • A shared geometry and material store. Every unique mesh is stored once, in its own local coordinates, under a hash of its contents. Same for materials and textures. Ten identical valves in one model, or the same door family across twenty models of the account, are one entry. The fragment list points at those hashes.
  • The property database and the object tree. Same structure SVF had. Properties are per model, not shared.

Deduplication works because the geometry is stored without placement. Two instances differ only by the matrix in the fragment list, so their hashes match.

How the viewer boots a model. Roughly this sequence:

  1. Document.load fetches the derivative manifest for the urn. For SVF2 that manifest points at an OTG-specific manifest, which lists the fragment list, the hash tables and the root of the shared storage.
  2. The fragment list is downloaded and parsed in a worker. This alone lets the viewer know every bounding box, so it builds the BVH client-side before a single triangle exists. That is why the model structure and the camera fit appear before anything is drawn.
  3. The loader walks the BVH from the current camera and requests geometry by hash, biggest and nearest first. Requests are batched. The viewer opens a WebSocket to the derivative CDN and streams many hashes per message, falling back to plain HTTP if the socket fails.
  4. Each mesh arrives as a compact binary: interleaved vertices, quantized positions and packed normals. A worker decodes it and the main thread uploads it to the GPU. Every fragment pointing at that hash becomes drawable in the next frame, which is the progressive fill you see.
  5. A resource cache in memory is shared across every model in the viewer session. A second model of the same project loads faster because most of its hashes are already resident.

What changes for you compared to SVF. SVF grouped meshes into pack files and downloaded whole packs, so the first pixel waited for a large fetch, and every model carried its own copy of every repeated mesh. SVF2 trades that for many small, cacheable, deduplicated fetches. The cost is that there is no self-contained package to copy offline. Geometry lives behind the CDN and every request carries the access token your server issues, which is why the viewer needs the streamingV2 api rather than derivativeV2.

The parts I am confident about are the shared hashed store, the fragment list, the manifest chain and the client-side BVH. The exact vertex encoding and the WebSocket batching are from reading viewer internals some time ago, so treat those two as likely rather than guaranteed.

Why Autodesk Forge Viewer is so fast.

The short answer: the Forge Viewer (internally called LMV, Large Model Viewer) barely uses Three.js as a rendering engine. It uses a forked r71 for the math library, the WebGL state wrapper and the material/shader plumbing, and replaced everything above that with its own machinery built for one job. The Three.js version is obsolete because nothing they depend on lives in the parts that changed since 2015, and upgrading would mean re-porting a decade of divergence for no gain.

What actually makes the difference:

  • No scene graph. Three.js gives every mesh an Object3D with a matrix, a parent, children, and a per-frame updateMatrixWorld walk. LMV stores a model as a flat FragmentList: packed typed arrays for transforms, bounds, material ids and geometry ids. A million fragments is a few contiguous buffers, not a million JS objects. That is cache-friendly, garbage-free and trivially iterable.
  • BVH-driven, screen-size-aware traversal. The SVF/OTG loader ships a precomputed bounding volume hierarchy. Each frame the iterator walks it front to back, frustum-culls whole subtrees, and skips anything whose projected size is below a pixel threshold. Modern Three.js frustum-culls per object and draws everything that passes, including thousands of bolts that cover half a pixel.
  • Progressive, time-budgeted rendering. LMV does not try to draw the whole model each frame. It draws the largest, nearest fragments within a frame budget, presents that, and keeps filling in over subsequent frames while the camera is still. When you orbit, it drops the tail and stays at frame rate. Three.js has no notion of “good enough for this frame”; the frame takes as long as the draw list takes.
  • Consolidation and instancing at load time. Fragments sharing a material are merged into large buffers, and repeated geometry is drawn instanced. Draw calls drop by one or two orders of magnitude. Three.js has BatchedMesh and InstancedMesh now, but you have to build that yourself, and most loaders produce one mesh per element.
  • Compact geometry format. Interleaved vertex buffers, quantized positions and packed normals, deduplicated geometry hashes across models in OTG. Less GPU memory, less upload, more of the model fits.
  • GPU-side picking and overlays. An id buffer is rendered alongside the colour buffer, so selection and hover are a pixel read, not a CPU raycast against millions of triangles. Selection highlight, ghosting and section planes are done through render targets and a single uber-shader rather than by swapping materials and redrawing.
  • Streaming and paging. Geometry loads on demand in BVH priority order, so the big visible stuff appears first, and it can unload geometry under memory pressure. The viewer is usable long before the model is fully resident.

So, the frame rate is not a property of the Three.js version. It comes from the file format, the flat data model, the BVH and the progressive renderer, which is why Autodesk had no incentive to chase upstream.

One correction to the premise: other engines do reach this class. xeokit uses data-texture geometry with the same flat philosophy, That Open Engine builds fragment models on modern Three.js, and Speckle takes a similar path. What they share with LMV is that they abandoned the per-object scene graph. A stock Three.js app with one mesh per element cannot get there, and that is the comparison most people are making.