21 Feb 2023

Sharing resources among multiple viewers

Displaying the same model in multiple viewer instances may not be a very common use case but if you ever find yourself implementing such feature (for example, to provide different camera angles of the same design), here's a little optimization to consider.

Naïve approach

Of course, we could just create separate instances of Autodesk.Viewing.Viewer3D or Autodesk.Viewing.GuiViewer3D, and load the same model URN in each one of them. This approach would certainly work but it is not very efficient, and it could start causing issues when it comes to very large and complex designs.

The problem is, each instance of the viewer allocates its own set of browser and GPU resources for each model (e.g., textures, geometry buffers, or property databases). Loading the same model into multiple viewers would therefore result in duplicate memory allocation. Moreover, loading the same model URN in different viewers would trigger the same HTTP requests to the Model Derivative service, unnecessarily wasting the bandwidth.

Optimized approach

In the Viewer SDK there's a small, inconspicuous class called Autodesk.Viewing.MultiViewerFactory that can create "special instances" of the viewer. These instances share many of their resources, including the resources of models that you load in them. Here's how you can make use of it:

The MultiViewerFactory class implements a createViewer method with the following signature:

/**
 * Creates a special instance of the viewer that can share resources with other instances.
 * @param container HTML container to host the viewer.
 * @param config Viewer config options.
 * @param viewerClass Specific viewer class to instantiate (`Autodesk.Viewing.Viewer3D` by default).
 * @returns New viewer instance.
 */
createViewer(container, config, viewerClass)

Viewers created with this method can be used as usual, and under-the-hood they will try and share as much of their resources as possible.

Here's a simple webpage with 3 instances of the viewer all displaying the same model:

If you want to try it yourself, you can provide your own ACCESS_TOKEN and MODEL_URN values, and serve this HTML page using any static server.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.css">
    <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
    <title>Autodesk Platform Services: MultiViewerFactory Sample</title>
    <style>
        body { margin: 0; padding: 0; width: 100vw; height: 100vh; }
        #viewer1, #viewer2, #viewer3 { position: absolute; }
        #viewer1 { left: 0; top: 0; width: 50%; height: 100%; }
        #viewer2 { right: 0; top: 0; width: 50%; height: 50%; }
        #viewer3 { right: 0; bottom: 0; width: 50%; height: 50%; }
    </style>
</head>
<body>
    <div id="viewer1"></div>
    <div id="viewer2"></div>
    <div id="viewer3"></div>
    <script>
        const ACCESS_TOKEN = '<enter your access token>';
        const MODEL_URN = '<enter your model urn>';

        function initViewer(factory, container, config = {}) {
            const viewer = factory.createViewer(container, config, Autodesk.Viewing.GuiViewer3D);
            viewer.start();
            return viewer;
        }

        Autodesk.Viewing.Initializer({ accessToken: ACCESS_TOKEN, env: 'AutodeskProduction2', api: 'derivativeV2' }, function () {
            window.factory = new Autodesk.Viewing.MultiViewerFactory();
            window.viewer1 = initViewer(window.factory, document.getElementById('viewer1'));
            window.viewer2 = initViewer(window.factory, document.getElementById('viewer2'));
            window.viewer3 = initViewer(window.factory, document.getElementById('viewer3'));
            Autodesk.Viewing.Document.load(
                'urn:' + MODEL_URN,
                async doc => {
                    const viewable = doc.getRoot().getDefaultGeometry();
                    await window.viewer1.loadDocumentNode(doc, viewable);
                    await window.viewer2.loadDocumentNode(doc, viewable);
                    await window.viewer3.loadDocumentNode(doc, viewable);
                },
                err => console.error(err)
            );
        });
    </script>
</body>
</html>

And here's the amount of resources downloaded by the app when we display only one, two, or all three of the viewers:

One viewerTwo viewersThree viewers

Note that each viewer keeps its own state (camera position, selection, isolation, explode, etc.). If you want to synchronize any of this state information among all viewers, you can do so using the standard Viewer APIs, for example, the getState and restoreState methods.

Related Article