Crashed when removing primitive

I just want one volume in “viewer.scene.primitives”, so when I update the volume, I need to remove the last volume, and then cesium crashed like:

codes are below:

function CesiumViewer({ volume }) {
const viewerRef = useRef(null);

useEffect(() => {
    const viewer = viewerRef.current;

    if (!viewer || !volume) {
        return;
    }

    if(viewer.scene.primitives.get(0)){
        viewer.scene.primitives.remove(viewer.scene.primitives.get(0));
    }
    viewer.scene.primitives.add(volume);
    

}, [volume]);

useEffect(() => {
    const viewer = new Cesium.Viewer("cesiumContainer", {
        baseLayer: Cesium.ImageryLayer.fromProviderAsync(
            Cesium.TileMapServiceImageryProvider.fromUrl(
                Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII")
            )
        ),
        infoBox: false,
        geocoder: false,
        homeButton: false,
        sceneModePicker: false,
        baseLayerPicker: false,
        navigationHelpButton: false,
        animation: false,
        timeline: false,
        fullscreenButton: false,
        vrButton: false,
        selectionIndicator: true,
        shadows: true,
        shouldAnimate: true,
        scene3DOnly: true,
        contextOptions: {
            requestWebgl2: true, //using WEBGL2.0
            webgl: {
                alpha: true
            }
        }
    });
    viewer._cesiumWidget._creditContainer.style.display = "none";
    viewer.scene.logarithmicDepthBuffer = true;
    //viewer.scene.debugShowFramesPerSecond = true;

    viewer.scene.fxaa = true;
    viewer.scene.postProcessStages.fxaa.enabled = true;

    viewer.scene.screenSpaceCameraController.tiltEventTypes = [
        Cesium.CameraEventType.LEFT_DRAG,
    ];

    viewer.scene.screenSpaceCameraController.zoomEventTypes = [
        Cesium.CameraEventType.MIDDLE_DRAG,
        Cesium.CameraEventType.WHEEL,
        Cesium.CameraEventType.PINCH,
    ];

    viewer.scene.screenSpaceCameraController.rotateEventTypes = [
        Cesium.CameraEventType.RIGHT_DRAG,
    ];

    viewerRef.current = viewer;

    
}, []);

return <div id="cesiumContainer" ></div>;

}

Just to have the cross-link: You seem to also have mentioned that in the issue comment at Bug when removing primitve with shared materials · Issue #1567 · CesiumGS/cesium · GitHub

The error message is slightly different, though. Here, it is

primitive.destroy is not a function,

but in the issue it was

This object was destroyed, i.e., destroy() was called.

This does not mean that they are not related (or have the same root cause), but this is something that has to be sorted out by whoever investigates this further.

This is indeed a feature.

There are many subtleties that are related to the React infrastructure that is apparently used there. When using something like React, one might need a deep understanding of what this framework actually does under the hood. Similarly, when using CesiumJS, one might need an understanding of certain aspects of the framework.

Let’s look at the relevant part of the code:

function CesiumViewer({ volume }) {
...
useEffect(() => {
    ...
    if(viewer.scene.primitives.get(0)){
        viewer.scene.primitives.remove(viewer.scene.primitives.get(0));
    }
    viewer.scene.primitives.add(volume);

}, [volume]);

What this does is that it removes the first primitive from the primitive collection of the scene (regardless of what this primitive is!).

And then adds the volume as a new primitive.

It is not clear what volume actually is, because you did not provide this information. Neither did you provide a Sandcastle so that people could try it out. But if this is called more than once (and your description sounds like it is), then it will remove the volume and add it back again.

Now, the documentation of the PrimitiveCollection.remove function points to PrimitiveCollection.destroyPrimitives, which says

Determines if primitives in the collection are destroyed when they are removed

So when the primitive is removed from the collection, then its rendering resources are destroyed (by default). And when trying to add this same primitive again, you might have to expect some form of error message. Maybe an error message like

This object was destroyed, i.e., destroy() was called.

So this behavior indeed is a feature. Namely, that CesiumJS takes care of cleaning up unused resources.


You could switch this off, by setting
viewer.scene.primitives.destroyPrimitives = false;
But… you should be aware of the implications. When doing that, you will have to take care of all the resource management!

The other option is just to not remove-and-add the same primitive twice. And here, for your convenience, a Sandcastle, to quickly try this out:

Wow, thank you!

I’m sorry, it turns out that it was my fault. I wrote a primitive-like class and forgot to include the destroy() function. After I added this function, the remove() method worked!!!

Guess I still got a lot to learn.

class volumePrimitive {

constructor(options) {
    this.drawCommand = undefined;

    if (Cesium.defined(options)) {
        this.modelMatrix = options.modelMatrix;
        this.geometry = options.geometry;
        this.data = options.data;
        this.width = options.width;
        this.height = options.height;
        this.depth = options.depth;
        this.dim = options.dim;
        this.threshold = options.threshold;
        this.lon = options.lon;
        this.lat = options.lat;
        this.alt = options.alt;
        this.isActive = options.isActive;
    }
}
createCommand(context) {
    if (!Cesium.defined(this.geometry)) {
        console.log("error")
        return;
    }
    const geometry = Cesium.BoxGeometry.createGeometry(this.geometry);
    const attributelocations = Cesium.GeometryPipeline.createAttributeLocations(geometry);
    this.vertexarray = Cesium.VertexArray.fromGeometry({
        context: context,
        geometry: geometry,
        attributes: attributelocations
    });
    const renderstate = Cesium.RenderState.fromCache({
        depthTest: {
            enabled: true,
        },
        cull: {
            enabled: false,
        },
        blending: Cesium.BlendingState.ALPHA_BLEND
    })
    const shaderProgram = Cesium.ShaderProgram.fromCache({
        context: context,
        vertexShaderSource: vs,
        fragmentShaderSource: fs,
        attributeLocations: attributelocations
    });
    const that = this;
    const uniformmap = {
        map() {
            return that.getTexture3D(context);
        },
        dim() {
            return that.dim;
        },
        threshold() {
            return that.threshold;
        },
        lon() {
            return that.lon;
        },
        lat() {
            return that.lat;
        },
        alt() {
            return that.alt;
        },
        isActive() {
            return that.isActive;
        }
    };

    this.drawCommand = new Cesium.DrawCommand({
        boundingVolume: this.geometry.boundingSphere,
        modelMatrix: this.modelMatrix,
        pass: Cesium.Pass.VOXELS,
        shaderProgram: shaderProgram,
        renderState: renderstate,
        vertexArray: this.vertexarray,
        uniformMap: uniformmap
    });
}

getTexture3D(context) {
    if (!this.texture) {
        this.texture = new Cesium.Texture3D({
            width: this.width,
            height: this.height,
            depth: this.depth,
            context: context,
            pixelFormat: Cesium.PixelFormat.ALPHA,
            pixelDataType: Cesium.ComponentDatatype.fromTypedArray(
                this.data
            ),
            source: {
                width: this.width,
                height: this.height,
                depth: this.depth,
                arrayBufferView: this.data,
            },
            sampler: new Cesium.Sampler({
                minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
                magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR
            }),
        })
    }
    return this.texture;
}

update(frameState) {
    if (!this.drawCommand) {
        this.createCommand(frameState.context);
    }
    frameState.commandList.push(this.drawCommand);
}

destroy() {
    return Cesium.destroyObject(this);
}

}

Good to hear that this is resolved now.

Initially, you said

when I update the volume, I need to remove the last volume, and then cesium crashed

and one could probably dive deeper into what ‘updating’ means here. The approach that you have done now looks like you created a custom class that “looks like” a Primitive, and where objects (instances of that class) show a certain, special behavior. I cannot say whether this is “good” or “bad”. But … it might have subtle, unexpected side-effects (and the destroy() behavior is only one of them). You might consider isloating the data that actually changes during such an “update”, and create a new primitive with the updated data. But of course, these are engineering/implementation decisions that are eventually up to you.

1 Like