Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: [Scene] frame should not be invalid, it may have been released. #18139

Closed
dumganhar opened this issue Jan 8, 2025 · 3 comments
Closed
Assignees
Labels
Milestone

Comments

@dumganhar
Copy link
Contributor

dumganhar commented Jan 8, 2025

Reported at https://forum.cocos.org/t/topic/164672

This issue is fired in v3.8.5.

How to Reproduce?

  • Create a prefab
  • Add a label node to prefab
  • Set the label's cache mode to CHAR
  • Save the prefab
  • Close the prefab
  • Open the prefab again
  • Close the prefab again

Get the error log:

企业微信截图_ffa63752-2083-4b40-93f9-0b5bea53e26d

Reason

The behavior of opening or closing a prefab will trigger scene switch, there is a global variable called _sharedAtlas in letter-font.ts whose texture will be cleared and re-constructed.

// letter-font.ts

let _shareAtlas: LetterAtlas | null  = null;

export const letterFont = js.mixin(bmfontUtils, {
    getAssemblerData () {
        if (!_shareAtlas) {
            _shareAtlas = new LetterAtlas(_atlasWidth, _atlasHeight);
        }

        return _shareAtlas.getTexture() as LetterRenderTexture | null;
    },
// font-utils.ts

export class LetterAtlas {
        ......

    constructor (width: number, height: number) {
            ......
        // Subscribe the event of before loading scene.
        director.on(DirectorEvent.BEFORE_SCENE_LAUNCH, this.beforeSceneLoad, this);
    }

    public beforeSceneLoad (): void {
        this.clearAllCache();
    }

    public clearAllCache (): void {
        this.destroy(); // Will destroy the internal texture instance

        const texture = new LetterRenderTexture(); // Re-create the internal texture instance
        texture.initWithSize(this._width, this._height);

        this.fontDefDictionary.texture = texture;
    }

    public destroy (): void {
        this.reset();
        const dict = this.fontDefDictionary;
        if (dict && dict.texture) {
            dict.texture.destroy();
            dict.texture = null;
        }
    }

There is a new functionality that could preview a prefab in inspector window in v3.8.5.
企业微信截图_11f7a0b5-7424-4b32-b207-dfc5a8fa2cb9

It uses an individual scene which will never be destroyed and never be run.
Some code snippet:

// preview/Interactive-preview.ts

class InteractivePreview extends PreviewBase {
    protected scene!: Scene;
    protected cameraComp!: Camera;
    protected camera: renderer.scene.Camera | null = null;
    protected worldAxisNode!: Node;
    protected axisCameraOffset = new Vec3(0, 0, 40);

    protected isMouseLeft = false;
    protected isMouseMiddle = false;

    protected enableAxis = true;
    protected worldAxis: PreviewWorldAxis | null = null;

    protected enableGrid = true;
    protected grid: Grid |null = null;

    protected enableSkybox = true;
    protected skybox: SkyboxInfo | null = null;

    public createNodes(scene: Scene) {

    }

    public init(registerName: string, queryName: string) {
        this.scene = new Scene(registerName);

        if (this.enableSkybox) {
            this.skybox = this.scene.globals.skybox;
            this.scene.globals.skybox.enabled = true;
        }

        this.cameraComp = new Node(registerName + 'camera').addComponent(Camera);

        this.cameraComp.node.setParent(this.scene);
        this.cameraComp.node.setPosition(0, 1, 2.5);
        this.cameraComp.node.lookAt(Vec3.ZERO);
        this.cameraComp.near = 0.01;
        this.cameraComp.enabled = false;

        this.createNodes(this.scene);

       // Load the scene manually instead of running it.
        this.scene._load();
        this.scene._activate();

        this.cameraComp.clearColor = new Color(71, 71, 71, 255);
        this.camera = this.cameraComp.camera;
        ......
        // Create the Preview buffer which will create an offscreen RenderWindow. See the code bellow.
        this.previewBuffer = new PreviewBuffer(registerName, queryName, this.scene);
        // Change the render window for camera which created in preview buffer.
        this.camera.changeTargetWindow(this.previewBuffer.window);
      ........
    }
// preview/buffer.ts

class PreviewBuffer extends EventEmitter {
    ......
    constructor(registerName: string, name: string, scene: any = null) {
        super();
        ......
        this.createWindow(); // Create render window
        this.queue = [];
    }

    createWindow(uuid: string | null = null) {
        if (uuid && this.windows[uuid]) {
            this.window = this.windows[uuid];
            return;
        }
        const root = cc.director.root;
        const renderPassInfo = new gfx.RenderPassInfo(
            [new gfx.ColorAttachment(root.mainWindow.swapchain.colorTexture.format)],
            new gfx.DepthStencilAttachment(root.mainWindow.swapchain.depthStencilTexture.format),
        );
        renderPassInfo.colorAttachments[0].barrier = root.device.getGeneralBarrier(new gfx.GeneralBarrierInfo(0, gfx.AccessFlagBit.FRAGMENT_SHADER_READ_TEXTURE));
        const window = root.createWindow({
            title: this._name,
            width: this.width,
            height: this.height,
            renderPassInfo,
            isOffscreen: true, // Mark it as an offscreen render window.
        });
        this.window = window;
        uuid && (this.windows[uuid] = window);
    }

So we get an independent preview-prefab-scene who contains an offscreen render window and all its children will be walked and rendered to offscreen texture even the preview-prefab-scene is not the current active scene.

When the global variable _sharedAtlas re-creates the altas texture, the label with char mode will not know that the texture is destroyed and changed. It will use the destroyed texture to render the label in preview-prefab-scene. Then, we get the error message in Label._render.

// label.ts
protected _render (render: IBatcher): void {
       // BOMB,  this._texture was destroyed.
        render.commitComp(this, this.renderData, this._texture, this._assembler!, null);
    }

Probable Solutions

Solution 1

The global variable _sharedAtlas should be associated with scene, which means each scene should have an individual _sharedAtlas instance. So when editor switchs scenes, the label cached in preview-prefab-scene will hold a valid texture.

And add a public interface to re-create the texture in _sharedAtlas, this is for editor to release the memory manually.

I don't find a good place the store all _sharedAtlas instances ( one per scene).

Solution 2

Fire an event when the texture re-created, Label component subscribes that event and update the texture. This solution will make the label cached in preview-prefab-scene always change the texture and fill new letter textures to altas texture which will cause some redundant operations.

Solution 3

Don't re-create the letter altas texture while scene switch in editor mode, add a reset method for editor to decide when to re-create it.

Solution 4

Use Asset.refCount to decide whether could destroy the altas texture.

@dumganhar dumganhar added the Bug label Jan 8, 2025
@dumganhar dumganhar added this to the 3.8.6 milestone Jan 8, 2025
@dumganhar dumganhar self-assigned this Jan 8, 2025
@minggo
Copy link
Contributor

minggo commented Jan 8, 2025

Can i know when _shareAtlas is destroyed as it is a global variable.

@dumganhar
Copy link
Contributor Author

dumganhar commented Jan 8, 2025

_shareAtlas will not be destroyed, it's its property fontDefDictionary.texture will be destroyed when switching scenes.

Look at this:

https://github.com/cocos/cocos-engine/blob/3.8.5/cocos/2d/assembler/label/font-utils.ts#L281
https://github.com/cocos/cocos-engine/blob/3.8.5/cocos/2d/assembler/label/font-utils.ts#L369

@dumganhar
Copy link
Contributor Author

dumganhar commented Jan 9, 2025

We selected the solution 4 ( Asset Reference Count ) to resolve the issue, the PR:

#18144

@minggo minggo closed this as completed in 13888fc Jan 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants