Repository: cocos/cocos-engine
Pull request #18614: Fix most UIOpacity issues on native platforms, optimize the logic of synchronizing localOpacity to native.
Author: login: dumganhar
Created: 2025-04-14T13:53:58Z | Merged: 2025-04-20T07:49:44Z

Avoid to iterate children recursively while UIOpacity value changes.

## C++ Class Size Changes ( Affect Cache Miss Rate)

### 3.8.7

21:10:16 [INFO]: Node size: 344
21:10:16 [INFO]: RenderEntity size: 632
21:10:16 [INFO]: RenderDrawInfo size: 144

### 3.8.6

21:14:58 [INFO]: Node size: 344
21:14:58 [INFO]: RenderEntity size: 640
21:14:58 [INFO]: RenderDrawInfo size: 144

Re: #

### Changelog

*

-------

### Continuous Integration

This pull request:

* [ ] needs automatic test cases check.
  > Manual trigger with `@cocos-robot run test cases` afterward.
* [ ] does not change any runtime related code or build configuration
  > If any reviewer thinks the CI checks are needed, please uncheck this option, then close and reopen the issue.

-------

### Compatibility Check

This pull request:

* [ ] changes public API, and have ensured backward compatibility with [deprecated features](https://github.com/cocos/cocos-engine/blob/v3.5.0/docs/contribution/deprecated-features.md).
* [ ] affects platform compatibility, e.g. system version, browser version, platform sdk version, platform toolchain, language version, hardware compatibility etc.
* [ ] affects file structure of the build package or build configuration which requires user project upgrade.
* [ ] **introduces breaking changes**, please list all changes, affected features and the scope of violation.

<!-- Note: Makes sure these boxes are checked before submitting your PR - thank you!
- [ ] Your pull request title is using English, it's precise and appropriate.
- [ ] If your pull request has gone "stale", you should **rebase** your work on top of the latest version of the upstream branch.
- [ ] If your commit history is full of small, unimportant commits (such as "fix pep8" or "update tests"), **squash** your commits down to a few, or one, discreet changesets before submitting a pull request.
- [ ] Document new code with comments in source code based on API docs
- [ ] Make sure any runtime log information in `log` , `error` or `new Error('')` has been moved into `EngineErrorMap.md` with an ID, and use `logID(id)` or `new Error(getError(id))` instead.
- To official teams:
  - [ ] Check that your PR is following our [guides](https://github.com/cocos/3d-tasks/blob/master/workflows/readme.md)
-->



Diff:
diff --git a/cocos/2d/assembler/sprite/radial-filled.ts b/cocos/2d/assembler/sprite/radial-filled.ts
index 57f6523c58f..f8c252f11c5 100644
--- a/cocos/2d/assembler/sprite/radial-filled.ts
+++ b/cocos/2d/assembler/sprite/radial-filled.ts
@@ -389,6 +389,7 @@ class RadialFilled implements IAssembler {
     }
 
     private createQuadIndices (indexCount: number): void {
+        if (!JSB) return;
         QUAD_INDICES = null;
         QUAD_INDICES = new Uint16Array(indexCount);
         let offset = 0;
@@ -424,6 +425,7 @@ class RadialFilled implements IAssembler {
     }
 
     private updateWorldUVData (sprite: Sprite): void {
+        if (!JSB) return;
         const renderData = sprite.renderData;
         if (!renderData) return;
         const stride = renderData.floatStride;
diff --git a/cocos/2d/assembler/sprite/tiled.ts b/cocos/2d/assembler/sprite/tiled.ts
index 807bc95ecc1..cd6295037bc 100644
--- a/cocos/2d/assembler/sprite/tiled.ts
+++ b/cocos/2d/assembler/sprite/tiled.ts
@@ -104,7 +104,7 @@ class Tiled implements IAssembler {
 
         this.updateVerts(sprite, sizableWidth, sizableHeight, row, col);
 
-        if (renderData.vertexCount !== row * col * 4) {
+        if (JSB && renderData.vertexCount !== row * col * 4) {
             sprite.renderEntity.colorDirty = true;
         }
         // update data property
@@ -124,6 +124,7 @@ class Tiled implements IAssembler {
     }
 
     private createQuadIndices (indexCount: number): void {
+        if (!JSB) return;
         if (indexCount % 6 !== 0) {
             errorID(16308);
             return;
diff --git a/cocos/2d/assembler/utils.ts b/cocos/2d/assembler/utils.ts
index 71d08e9bf9a..94d402fce55 100644
--- a/cocos/2d/assembler/utils.ts
+++ b/cocos/2d/assembler/utils.ts
@@ -26,7 +26,7 @@ import { Color, Vec4, clamp } from '../../core';
 import { RenderData } from '../renderer/render-data';
 import { IBatcher } from '../renderer/i-batcher';
 import { Node } from '../../scene-graph/node';
-import { FormatInfos } from '../../gfx';
+import { FormatInfos, Attribute, FormatInfo } from '../../gfx';
 
 const _col = new Vec4();
 
@@ -81,7 +81,10 @@ export function fillMeshVertices3D (node: Node, renderer: IBatcher, renderData:
 export function updateOpacity (renderData: RenderData, opacity: number): void {
     const vfmt = renderData.vertexFormat;
     const vb = renderData.chunk.vb;
-    let attr; let format; let stride;
+    let vbUint32View: Uint32Array | undefined;
+    let attr: Attribute;
+    let format: FormatInfo;
+    let stride: number;
     // Color component offset
     let offset = 0;
     for (let i = 0; i < vfmt.length; ++i) {
@@ -90,10 +93,13 @@ export function updateOpacity (renderData: RenderData, opacity: number): void {
         if (format.hasAlpha) {
             stride = renderData.floatStride;
             if (format.size / format.count === 1) {
+                if (!vbUint32View) {
+                    vbUint32View = new Uint32Array(vb.buffer, vb.byteOffset, vb.length);
+                }
                 const alpha = ~~clamp(Math.round(opacity * 255), 0, 255);
                 // Uint color RGBA8
-                for (let color = offset; color < vb.length; color += stride) {
-                    vb[color] = ((vb[color] & 0xffffff00) | alpha) >>> 0;
+                for (let color = offset; color < vbUint32View.length; color += stride) {
+                    vbUint32View[color] = ((vbUint32View[color] & 0x00ffffff) | (alpha << 24)) >>> 0;
                 }
             } else if (format.size / format.count === 4) {
                 // RGBA32 color, alpha at position 3
diff --git a/cocos/2d/components/ui-opacity.ts b/cocos/2d/components/ui-opacity.ts
index 5aaca605507..c5b052811c2 100644
--- a/cocos/2d/components/ui-opacity.ts
+++ b/cocos/2d/components/ui-opacity.ts
@@ -26,83 +26,6 @@ import { ccclass, disallowMultiple, editable, executeInEditMode, executionOrder,
 import { EDITOR_NOT_IN_PREVIEW, JSB } from 'internal:constants';
 import { Component } from '../../scene-graph/component';
 import { clamp } from '../../core';
-import { UIRenderer } from '../framework/ui-renderer';
-import { Node } from '../../scene-graph';
-import { NodeEventType } from '../../scene-graph/node-event';
-
-/**
- * @en
- * Recursively sets localopacity.
- *
- * @zh
- * 递归设置localopacity。
- *
- * @param node @en recursive node.
- *             @zh 递归的节点。
- * @param dirty @en Is the color dirty.
- *              @zh color是否dirty。
- * @param parentOpacity @en The parent node's opacity.
- *                      @zh 父节点的opacity。
- * @param stopRecursiveIfHasOpacity @en Stop recursion if UiOpacity component exists.
- *                                  @zh 如果存在UiOpacity组件则停止递归。
- */
-function setEntityLocalOpacityDirtyRecursively (
-    node: Node,
-    dirty: boolean,
-    parentOpacity: number,
-    stopRecursiveIfHasOpacity: boolean,
-): void {
-    if (!node.isValid) {
-        // Since children might be destroyed before the parent,
-        // we should add protecting condition when executing recursion downwards.
-        return;
-    }
-
-    const uiOp = node.getComponent(UIOpacity);
-    if (uiOp && stopRecursiveIfHasOpacity) {
-        // Because it's possible that UiOpacity components are handled by themselves (at onEnable or onDisable)
-        uiOp._setParentOpacity(parentOpacity);
-        return;
-    }
-
-    // If the node has never been activated, then node._uiProps.uiComp won't be set.
-    // We need to check if the UIRenderer exists or not.
-    let render = node._uiProps.uiComp as UIRenderer | null;
-    if (!render) {
-        render = node.getComponent(UIRenderer);
-    }
-
-    if (render && render.color) { // exclude UIMeshRenderer which has not color
-        render.renderEntity.colorDirty = dirty;
-        if (uiOp) {
-            uiOp._setParentOpacity(parentOpacity);
-            render.renderEntity.localOpacity = parentOpacity * uiOp.opacity / 255;
-        } else {
-            // there is a just UIRenderer but no UIOpacity on the node, we should just transport the parentOpacity to the node.
-            render.renderEntity.localOpacity = parentOpacity;
-        }
-        render.node._uiProps.localOpacity = render.renderEntity.localOpacity;
-        //No need for recursion here. Because it doesn't affect the capacity of the child nodes.
-        return;
-    }
-
-    if (uiOp) {
-        // there is a just UIOpacity but no UIRenderer on the node.
-        // we should transport the interrupt opacity downward
-        uiOp._setParentOpacity(parentOpacity);
-        parentOpacity = parentOpacity * uiOp.opacity / 255;
-    }
-
-    const children = node.children;
-    for (let i = 0, len = children.length; i < len; ++i) {
-        setEntityLocalOpacityDirtyRecursively(
-            children[i],
-            dirty || (parentOpacity < 1),
-            parentOpacity,
-            stopRecursiveIfHasOpacity,
-        );
-    }
-}
 
 /**
  * @en
@@ -124,26 +47,6 @@ export class UIOpacity extends Component {
         super();
     }
 
-    /**
-     * @en
-     * The parent node's opacity.
-     *
-     * @zh
-     * 父节点的opacity。
-     *
-     */
-    private _parentOpacity: number = 1.0;
-
-    /**
-     * @engineInternal
-     * @mangle
-     */
-    _setParentOpacity (v: number): void {
-        this._parentOpacity = v;
-    }
-
-    private _parentOpacityResetFlag: boolean = true;
-
     /**
      * @en
      * The transparency value of the impact.
@@ -163,11 +66,7 @@ export class UIOpacity extends Component {
         }
         value = clamp(value, 0, 255);
         this._opacity = value;
-        this.node._uiProps.localOpacity = value / 255;
-
-        if (JSB) {
-            setEntityLocalOpacityDirtyRecursively(this.node, true, this._parentOpacity, false);
-        }
+        this._syncLocalOpacity(value / 255);
 
         if (EDITOR_NOT_IN_PREVIEW) {
             setTimeout(() => {
@@ -179,68 +78,15 @@ export class UIOpacity extends Component {
     @serializable
     protected _opacity = 255;
 
-    protected _getParentOpacity (node: Node): number {
-        if (node == null || !node.isValid) {
-            return 1;
-        }
-        const render = node._uiProps.uiComp as UIRenderer;
-        const uiOp = node.getComponent(UIOpacity);
-        if (render && render.color) {
-            return 1;
-        }
-        if (uiOp) {
-            return uiOp._parentOpacity * (uiOp._opacity / 255);
-        }
-        return this._getParentOpacity(node.getParent()!);
-    }
-
-    protected _parentChanged (): void {
-        if (!JSB) {
-            return;
-        }
-        const parent = this.node.getParent();
-        let opacity = 1;
-        if (parent) {
-            this._parentOpacity = this._getParentOpacity(parent);
-            opacity = this._parentOpacity;
-        } else {
-            this._parentOpacityResetFlag = true;
-        }
-        setEntityLocalOpacityDirtyRecursively(this.node, true, opacity, false);
-    }
-
-    protected _setEntityLocalOpacityRecursively (opacity: number): void {
-        // Because JSB's localOpacity value is present in the renderEntity, but non-JSB's are not.
-        if (!JSB) {
-            return;
-        }
-        const render = this.node._uiProps.uiComp as UIRenderer;
-        if (render && render.color) { // exclude UIMeshRenderer which has not color
-            render.renderEntity.colorDirty = true;
-            render.renderEntity.localOpacity = opacity;
-            render.node._uiProps.localOpacity = opacity;
-            return;
-        }
-        // The current node is not recursive, only the child nodes are recursive.
-        for (const child of this.node.children) {
-            setEntityLocalOpacityDirtyRecursively(child, true, opacity, true);
-        }
-    }
-
     public onEnable (): void {
-        this.node.on(NodeEventType.PARENT_CHANGED, this._parentChanged, this);
-        this.node._uiProps.localOpacity = this._parentOpacity * this._opacity / 255;
-        if (this._parentOpacityResetFlag) {
-            this._parentChanged();
-            this._parentOpacityResetFlag = false;
-        } else {
-            this._setEntityLocalOpacityRecursively(this.node._uiProps.localOpacity);
-        }
+        this._syncLocalOpacity(this._opacity / 255);
     }
 
     public onDisable (): void {
-        this.node.off(NodeEventType.PARENT_CHANGED, this._parentChanged, this);
-        this.node._uiProps.localOpacity = 1;
-        this._setEntityLocalOpacityRecursively(this.node._uiProps.localOpacity);
+        this._syncLocalOpacity(1);
+    }
+
+    private _syncLocalOpacity (localOpacity: number): void {
+        this.node._uiProps.localOpacity = localOpacity;
     }
 }
diff --git a/cocos/2d/framework/ui-renderer.ts b/cocos/2d/framework/ui-renderer.ts
index c82138ba85a..5215b170b78 100644
--- a/cocos/2d/framework/ui-renderer.ts
+++ b/cocos/2d/framework/ui-renderer.ts
@@ -40,7 +40,7 @@ import { UITransform } from './ui-transform';
 import { Stage } from '../renderer/stencil-manager';
 import { NodeEventType } from '../../scene-graph/node-event';
 import { Renderer } from '../../misc/renderer';
-import { RenderEntity, RenderEntityType } from '../renderer/render-entity';
+import { RenderEntity, RenderEntityType, RenderEntityFillColorType } from '../renderer/render-entity';
 import { uiRendererManager } from './ui-renderer-manager';
 import { RenderDrawInfoType } from '../renderer/render-draw-info';
 import { director } from '../../game';
@@ -214,13 +214,6 @@ export class UIRenderer extends Renderer {
         this._renderData = renderData;
     }
 
-    /**
-     * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
-     */
-    get useVertexOpacity (): boolean {
-        return this._useVertexOpacity;
-    }
-
     /**
      * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
      * @en The component stencil stage (please do not any modification directly on this object)
@@ -300,10 +293,48 @@ export class UIRenderer extends Renderer {
     }
 
     /**
-     * @en Marks for calculating opacity per vertex
-     * @zh 标记组件是否逐顶点计算透明度
+     * @en UI rendering component fill color type, COLOR means using color property value to fill, VERTEX means using vertex color value to fill.
+     * @zh UI 渲染组件填充颜色类型，COLOR 表示使用 color 属性值填充，VERTEX 表示使用顶点颜色值填充。
      */
-    protected _useVertexOpacity = false;
+    private _fillColorType = RenderEntityFillColorType.COLOR;
+
+    /**
+     * @engineInternal
+     */
+    public getFillColorType (): RenderEntityFillColorType {
+        return this._fillColorType;
+    }
+
+    /**
+     * @engineInternal
+     */
+    protected setFillColorType (val: RenderEntityFillColorType): void {
+        this._fillColorType = val;
+        if (JSB) {
+            this._renderEntity.setFillColorType(val);
+        }
+    }
+
+    /**
+     * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
+     */
+    protected set _useVertexOpacity (val: boolean) {
+        this.setFillColorType(RenderEntityFillColorType.VERTEX);
+    }
+
+    /**
+     * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
+     */
+    protected get _useVertexOpacity (): boolean {
+        return this._fillColorType === RenderEntityFillColorType.VERTEX;
+    }
+
+    /**
+     * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
+     */
+    get useVertexOpacity (): boolean {
+        return this._fillColorType === RenderEntityFillColorType.VERTEX;
+    }
 
     protected _lastParent: Node | null = null;
 
@@ -498,7 +529,6 @@ export class UIRenderer extends Renderer {
         this.node._uiProps.colorDirty = true;
         this.setEntityColorDirty(true);
         this.setEntityColor(this._color);
-        this.setEntityOpacity(this.node._uiProps.localOpacity);
 
         const assembler = this._assembler;
         if (assembler) {
@@ -518,23 +548,9 @@ export class UIRenderer extends Renderer {
         }
     }
 
-    /**
-     * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
-     */
-    // for common
-    public static setEntityColorDirtyRecursively (node: Node, dirty: boolean): void {
-        const render = node._uiProps.uiComp as UIRenderer;
-        if (render && render.color) { // exclude UIMeshRenderer which has not color
-            render._renderEntity.colorDirty = dirty;
-        }
-        for (let i = 0; i < node.children.length; i++) {
-            UIRenderer.setEntityColorDirtyRecursively(node.children[i], dirty);
-        }
-    }
-
     private setEntityColorDirty (dirty: boolean): void {
         if (JSB) {
-            UIRenderer.setEntityColorDirtyRecursively(this.node, dirty);
+            this._renderEntity.colorDirty = dirty;
         }
     }
 
@@ -552,7 +568,7 @@ export class UIRenderer extends Renderer {
      */
     public setEntityOpacity (opacity: number): void {
         if (JSB) {
-            this._renderEntity.localOpacity = opacity;
+            (this.node as any)._setLocalOpacity(opacity);
         }
     }
 
diff --git a/cocos/2d/renderer/batcher-2d.ts b/cocos/2d/renderer/batcher-2d.ts
index 3b4a08ceeb9..fbce9049460 100644
--- a/cocos/2d/renderer/batcher-2d.ts
+++ b/cocos/2d/renderer/batcher-2d.ts
@@ -50,6 +50,7 @@ import { scene } from '../../render-scene';
 import { builtinResMgr } from '../../asset/asset-manager';
 import { RenderingSubMesh } from '../../asset/assets';
 import { IAssembler } from './base';
+import { RenderEntityFillColorType } from './render-entity';
 import type { Director } from '../../game/director';
 
 const _dsInfo = new DescriptorSetInfo(null!);
@@ -808,6 +809,8 @@ export class Batcher2D implements IBatcher {
         // TODO Set opacity to ui property's opacity before remove it
         uiProps.setOpacity(opacity);
         if (!approx(opacity, 0, EPSILON)) {
+            const renderData = render ? render.renderData : null;
+            const vertexCount = renderData ? renderData.vertexCount : 0;
             if (uiProps.colorDirty) {
             // Cascade color dirty state
                 this._opacityDirty++;
@@ -819,10 +822,22 @@ export class Batcher2D implements IBatcher {
             }
 
             // Update cascaded opacity to vertex buffer
-            if (this._opacityDirty && render && !render.useVertexOpacity && render.renderData && render.renderData.vertexCount > 0) {
-            // HARD COUPLING
-                updateOpacity(render.renderData, opacity);
-                const buffer = render.renderData.getMeshBuffer();
+            if (this._opacityDirty && vertexCount > 0) {
+                // HARD COUPLING
+                switch (render.getFillColorType()) {
+                case RenderEntityFillColorType.COLOR: {
+                    updateOpacity(renderData!, opacity);
+                    break;
+                }
+                case RenderEntityFillColorType.VERTEX: {
+                    // Use vertex color directly, so do nothing here.
+                    break;
+                }
+                default:
+                    break;
+                }
+
+                const buffer = renderData!.getMeshBuffer();
                 if (buffer) {
                     buffer.setDirty();
                 }
@@ -836,7 +851,7 @@ export class Batcher2D implements IBatcher {
             }
 
             if (uiProps.colorDirty) {
-            // Reduce cascaded color dirty state
+                // Reduce cascaded color dirty state
                 this._opacityDirty--;
                 // Reset color dirty
                 uiProps.colorDirty = false;
diff --git a/cocos/2d/renderer/mesh-buffer.ts b/cocos/2d/renderer/mesh-buffer.ts
index c1cf22a0bd4..79f953d11d2 100644
--- a/cocos/2d/renderer/mesh-buffer.ts
+++ b/cocos/2d/renderer/mesh-buffer.ts
@@ -230,9 +230,9 @@ export class MeshBuffer {
     constructor () {
         if (JSB) {
             this._nativeObj = new NativeUIMeshBuffer();
+            this.initSharedBuffer();
+            this.syncSharedBufferToNative();
         }
-        this.initSharedBuffer();
-        this.syncSharedBufferToNative();
     }
 
     /**
diff --git a/cocos/2d/renderer/render-draw-info.ts b/cocos/2d/renderer/render-draw-info.ts
index 5e7a2f1b7ac..9cff0495ad2 100644
--- a/cocos/2d/renderer/render-draw-info.ts
+++ b/cocos/2d/renderer/render-draw-info.ts
@@ -105,14 +105,16 @@ export class RenderDrawInfo {
     protected _render2dBuffer: Float32Array | null = null;
 
     constructor (nativeDrawInfo?: NativeRenderDrawInfo) {
-        this.init(nativeDrawInfo);
-        const attrSharedBuffer = this._nativeObj.getAttrSharedBufferForJS();
-        let offset = 0;
-        this._uint8SharedBuffer = new Uint8Array(attrSharedBuffer, offset, AttrUInt8ArrayView.Count);
-        offset += AttrUInt8ArrayView.Count * Uint8Array.BYTES_PER_ELEMENT;
-        this._uint16SharedBuffer = new Uint16Array(attrSharedBuffer, offset, AttrUInt16ArrayView.Count);
-        offset += AttrUInt16ArrayView.Count * Uint16Array.BYTES_PER_ELEMENT;
-        this._uint32SharedBuffer = new Uint32Array(attrSharedBuffer, offset, AttrUInt32ArrayView.Count);
+        if (JSB) {
+            this.init(nativeDrawInfo);
+            const attrSharedBuffer = this._nativeObj.getAttrSharedBufferForJS();
+            let offset = 0;
+            this._uint8SharedBuffer = new Uint8Array(attrSharedBuffer, offset, AttrUInt8ArrayView.Count);
+            offset += AttrUInt8ArrayView.Count * Uint8Array.BYTES_PER_ELEMENT;
+            this._uint16SharedBuffer = new Uint16Array(attrSharedBuffer, offset, AttrUInt16ArrayView.Count);
+            offset += AttrUInt16ArrayView.Count * Uint16Array.BYTES_PER_ELEMENT;
+            this._uint32SharedBuffer = new Uint32Array(attrSharedBuffer, offset, AttrUInt32ArrayView.Count);
+        }
     }
 
     get nativeObj (): NativeRenderDrawInfo {
@@ -135,6 +137,7 @@ export class RenderDrawInfo {
     }
 
     public clear (): void {
+        if (!JSB) return;
         this._bufferId = 0;
         this._vertexOffset = 0;
         this._indexOffset = 0;
diff --git a/cocos/2d/renderer/render-entity.ts b/cocos/2d/renderer/render-entity.ts
index f29bc05e99a..df233ca2d2e 100644
--- a/cocos/2d/renderer/render-entity.ts
+++ b/cocos/2d/renderer/render-entity.ts
@@ -29,28 +29,28 @@ import { Color } from '../../core';
 import { Stage } from './stencil-manager';
 import { Node } from '../../scene-graph';
 
+export enum RenderEntityFillColorType {
+    COLOR = 0,
+    VERTEX
+}
+
 export enum RenderEntityType {
     STATIC,
     DYNAMIC,
     CROSSED,
 }
 
-export enum RenderEntityFloatSharedBufferView {
-    localOpacity,
-    count,
-}
-
-export enum RenderEntityUInt8SharedBufferView {
+enum RenderEntityUInt8SharedBufferView {
     colorR,
     colorG,
     colorB,
     colorA,
     maskMode,
+    fillColorType,
     count,
 }
 
-export enum RenderEntityBoolSharedBufferView {
-    colorDirty,
+enum RenderEntityBoolSharedBufferViewBitIndex {
     enabled,
     useLocal,
     count,
@@ -73,6 +73,9 @@ export class RenderEntity {
     protected _node: Node | null = null;
     protected _renderTransform: Node | null = null;
     protected _stencilStage: Stage = Stage.DISABLED;
+
+    protected _colorDirty = true;
+    protected _enabled = false;
     protected _useLocal = false;
     protected _maskMode = MaskMode.NONE;
 
@@ -110,40 +113,43 @@ export class RenderEntity {
         }
     }
 
-    protected _localOpacity = 255;
-    get localOpacity (): number {
-        return this._localOpacity;
-    }
-    set localOpacity (val: number) {
-        this._localOpacity = val;
-        if (JSB) {
-            this._floatSharedBuffer[RenderEntityFloatSharedBufferView.localOpacity] = val;
-        }
-    }
-
-    protected _colorDirty = true;
     get colorDirty (): boolean {
-        if (JSB) {
-            // Synchronize values set from native to JS
-            this._colorDirty = !!this._boolSharedBuffer[RenderEntityBoolSharedBufferView.colorDirty];
+        if (JSB && this._node) {
+            this._colorDirty = (this._node as any)._colorDirty;
         }
         return this._colorDirty;
     }
+
     set colorDirty (val: boolean) {
         this._colorDirty = val;
-        if (JSB) {
-            this._boolSharedBuffer[RenderEntityBoolSharedBufferView.colorDirty] = val ? 1 : 0;
+        if (JSB && this._node) {
+            (this._node as any)._colorDirty = val;
         }
     }
 
-    protected _enabled = false;
     get enabled (): boolean {
         return this._enabled;
     }
+
     set enabled (val: boolean) {
         this._enabled = val;
         if (JSB) {
-            this._boolSharedBuffer[RenderEntityBoolSharedBufferView.enabled] = val ? 1 : 0;
+            if (val) {
+                this._boolSharedBuffer[0] |= (1 << RenderEntityBoolSharedBufferViewBitIndex.enabled);
+            } else {
+                this._boolSharedBuffer[0] &= ~(1 << RenderEntityBoolSharedBufferViewBitIndex.enabled);
+            }
+        }
+    }
+
+    setUseLocal (useLocal: boolean): void {
+        this._useLocal = useLocal;
+        if (JSB) {
+            if (useLocal) {
+                this._boolSharedBuffer[0] |= (1 << RenderEntityBoolSharedBufferViewBitIndex.useLocal);
+            } else {
+                this._boolSharedBuffer[0] &= ~(1 << RenderEntityBoolSharedBufferViewBitIndex.useLocal);
+            }
         }
     }
 
@@ -217,6 +223,12 @@ export class RenderEntity {
         this._maskMode = mode;
     }
 
+    public setFillColorType (fillColorType: RenderEntityFillColorType): void {
+        if (JSB) {
+            this._uint8SharedBuffer[RenderEntityUInt8SharedBufferView.fillColorType] = fillColorType;
+        }
+    }
+
     public getStaticRenderDrawInfo (): RenderDrawInfo | null {
         if (JSB) {
             const nativeDrawInfo = this._nativeObj.getStaticRenderDrawInfo(this._nativeObj.staticDrawInfoSize++);
@@ -253,23 +265,14 @@ export class RenderEntity {
         this._stencilStage = stage;
     }
 
-    setUseLocal (useLocal: boolean): void {
-        if (JSB) {
-            this._boolSharedBuffer[RenderEntityBoolSharedBufferView.useLocal] = useLocal ? 1 : 0;
-        }
-        this._useLocal = useLocal;
-    }
-
     private initSharedBuffer (): void {
         if (JSB) {
             //this._sharedBuffer = new Float32Array(RenderEntitySharedBufferView.count);
             const buffer = this._nativeObj.getEntitySharedBufferForJS();
             let offset = 0;
-            this._floatSharedBuffer = new Float32Array(buffer, offset, RenderEntityFloatSharedBufferView.count);
-            offset += RenderEntityFloatSharedBufferView.count * 4;
             this._uint8SharedBuffer = new Uint8Array(buffer, offset, RenderEntityUInt8SharedBufferView.count);
             offset += RenderEntityUInt8SharedBufferView.count * 1;
-            this._boolSharedBuffer = new Uint8Array(buffer, offset, RenderEntityBoolSharedBufferView.count);
+            this._boolSharedBuffer = new Uint8Array(buffer, offset, 1); // Only use 1 bytes for at most 8 booleans
         }
     }
 }
diff --git a/cocos/2d/renderer/static-vb-accessor.ts b/cocos/2d/renderer/static-vb-accessor.ts
index 32b2716a002..0d810517181 100644
--- a/cocos/2d/renderer/static-vb-accessor.ts
+++ b/cocos/2d/renderer/static-vb-accessor.ts
@@ -57,7 +57,9 @@ export class StaticVBChunk {
         public vb: Float32Array,
         public indexCount: number,
     ) {
-        this._ib = new Uint16Array(indexCount); // JSB
+        if (JSB) {
+            this._ib = new Uint16Array(indexCount); // JSB
+        }
         assertIsTrue(meshBuffer === vertexAccessor.getMeshBuffer(bufferId));
     }
 
@@ -163,8 +165,11 @@ export class StaticVBAccessor extends BufferAccessor {
             errorID(9004, byteLength);
             return null;
         }
-        let buf: MeshBuffer = null!; let freeList: IFreeEntry[];
-        let bid = 0; let eid = -1; let entry: IFreeEntry | null = null;
+        let buf: MeshBuffer = null!;
+        let freeList: IFreeEntry[];
+        let bid = 0;
+        let eid = -1;
+        let entry: IFreeEntry | null = null;
         // Loop buffers
         for (let i = 0; i < this._buffers.length; ++i) {
             buf = this._buffers[i];
diff --git a/cocos/particle-2d/motion-streak-2d-assembler.ts b/cocos/particle-2d/motion-streak-2d-assembler.ts
index ddf9150cb6f..42facd0a9ec 100644
--- a/cocos/particle-2d/motion-streak-2d-assembler.ts
+++ b/cocos/particle-2d/motion-streak-2d-assembler.ts
@@ -89,7 +89,8 @@ class MotionStreakAssembler implements IAssembler {
         const cr = color.r;
         const cg = color.g;
         const cb = color.b;
-        const ca = color.a;
+
+        const ca = node._uiProps.opacity * color.a;
 
         const prev = points[1];
         prev.distance = Vec2.subtract(_vec2, cur.point, prev.point).length();
@@ -153,6 +154,7 @@ class MotionStreakAssembler implements IAssembler {
         indexCount = vertexCount <= 2 ? 0 : (vertexCount - 2) * 3;
 
         renderData.resize(vertexCount, indexCount); // resize
+
         if (JSB) {
             const indexCount = renderData.indexCount;
             this.createQuadIndices(comp, indexCount);
@@ -167,12 +169,14 @@ class MotionStreakAssembler implements IAssembler {
     }
 
     private updateWorldVertexAllData (comp: MotionStreak): void {
+        if (!JSB) return;
         const renderData = comp.renderData;
         if (!renderData) return;
         const stride = renderData.floatStride;
         const dataList = renderData.data;
         const vData = renderData.chunk.vb;
-        for (let i  = 0; i < dataList.length; i++) {
+        const vertexCount = renderData.vertexCount;
+        for (let i  = 0; i < vertexCount; i++) {
             const offset = i * stride;
             vData[offset + 0] = dataList[i].x;
             vData[offset + 1] = dataList[i].y;
@@ -184,6 +188,7 @@ class MotionStreakAssembler implements IAssembler {
     }
 
     private createQuadIndices (comp: MotionStreak, indexCount: number): void {
+        if (!JSB) return;
         const renderData = comp.renderData;
         if (!renderData) return;
         const chunk = renderData.chunk;
diff --git a/cocos/particle-2d/motion-streak-2d.ts b/cocos/particle-2d/motion-streak-2d.ts
index 1d0004661cd..5052ee8b9d0 100644
--- a/cocos/particle-2d/motion-streak-2d.ts
+++ b/cocos/particle-2d/motion-streak-2d.ts
@@ -30,6 +30,7 @@ import { Texture2D } from '../asset/assets/texture-2d';
 import type { IBatcher } from '../2d/renderer/i-batcher';
 import { Vec2 } from '../core';
 import type { RenderData } from '../2d/renderer/render-data';
+import { RenderEntityFillColorType } from '../2d/renderer/render-entity';
 
 export class Point {
     public point = new Vec2();
@@ -74,6 +75,7 @@ export class MotionStreak extends UIRenderer {
 
     constructor () {
         super();
+        this.setFillColorType(RenderEntityFillColorType.VERTEX);
     }
 
     /**
diff --git a/cocos/scene-graph/node-ui-properties.ts b/cocos/scene-graph/node-ui-properties.ts
index b23abc1a0ac..320d8d551dc 100644
--- a/cocos/scene-graph/node-ui-properties.ts
+++ b/cocos/scene-graph/node-ui-properties.ts
@@ -22,6 +22,7 @@
  THE SOFTWARE.
 */
 
+import { JSB } from 'internal:constants';
 import { UIRenderer } from '../2d/framework/ui-renderer';
 import { warnID } from '../core/platform/debug';
 import { UIMeshRenderer } from '../2d';
@@ -77,7 +78,12 @@ export class NodeUIProperties {
      * @mangle
      */
     public setOpacity (v: number): void { this._opacity = v; }
-    public get opacity (): number { return this._opacity; }
+    public get opacity (): number {
+        if (JSB) {
+            this._opacity = (this._node as any)._getFinalOpacity();
+        }
+        return this._opacity;
+    }
 
     /**
      * @en The opacity of the UI node itself
@@ -88,6 +94,11 @@ export class NodeUIProperties {
     set localOpacity (val) {
         this._localOpacity = val;
         this.colorDirty = true;
+        if (JSB) {
+            const node = this._node as any;
+            node._colorDirty = true;
+            node._setLocalOpacity(val);
+        }
     }
 
     public colorDirty = true;
diff --git a/cocos/scene-graph/node.jsb.ts b/cocos/scene-graph/node.jsb.ts
index 68c737e1987..b41bc782c00 100644
--- a/cocos/scene-graph/node.jsb.ts
+++ b/cocos/scene-graph/node.jsb.ts
@@ -1053,10 +1053,10 @@ Object.defineProperty(nodeProto, 'activeInHierarchy', {
     configurable: true,
     enumerable: true,
     get(): Readonly<Boolean> {
-        return this._sharedUint8Arr[0] != 0; // Uint8, 0: activeInHierarchy
+        return (this._sharedUint8Arr[0] & 0x01) !== 0; // Uint8, 0:0, activeInHierarchy
     },
     set(v) {
-        this._sharedUint8Arr[0] = (v ? 1 : 0); // Uint8, 0: activeInHierarchy
+        v ? this._sharedUint8Arr[0] |= 0x01 : this._sharedUint8Arr[0] &= ~0x01; // Uint8, 0:0, activeInHierarchy
     },
 });
 
@@ -1064,10 +1064,10 @@ Object.defineProperty(nodeProto, '_activeInHierarchy', {
     configurable: true,
     enumerable: true,
     get(): Readonly<Boolean> {
-        return this._sharedUint8Arr[0] != 0; // Uint8, 0: activeInHierarchy
+        return (this._sharedUint8Arr[0] & 0x01) !== 0; // Uint8, 0:0, activeInHierarchy
     },
     set(v) {
-        this._sharedUint8Arr[0] = (v ? 1 : 0); // Uint8, 0: activeInHierarchy
+        v ? this._sharedUint8Arr[0] |= 0x01 : this._sharedUint8Arr[0] &= ~0x01; // Uint8, 0:0, activeInHierarchy
     },
 });
 
@@ -1161,10 +1161,10 @@ Object.defineProperty(nodeProto, '_active', {
     configurable: true,
     enumerable: true,
     get(): Readonly<Boolean> {
-        return this._sharedUint8Arr[1] != 0; // Uint8, 1: active
+        return (this._sharedUint8Arr[0] & 0x02) !== 0; // Uint8, 0:1, active
     },
     set(v) {
-        this._sharedUint8Arr[1] = (v ? 1 : 0); // Uint8, 1: active
+        v ? this._sharedUint8Arr[0] |= 0x02 : this._sharedUint8Arr[0] &= ~0x02; // Uint8, 0:1, active
     },
 });
 
@@ -1172,7 +1172,7 @@ Object.defineProperty(nodeProto, 'active', {
     configurable: true,
     enumerable: true,
     get(): Readonly<Boolean> {
-        return this._sharedUint8Arr[1] != 0; // Uint8, 1: active
+        return (this._sharedUint8Arr[0] & 0x02) !== 0; // Uint8, 0:1, active
     },
     set(v) {
         this.setActive(!!v);
@@ -1183,10 +1183,21 @@ Object.defineProperty(nodeProto, '_static', {
     configurable: true,
     enumerable: true,
     get(): Readonly<Boolean> {
-        return this._sharedUint8Arr[2] != 0;
+        return (this._sharedUint8Arr[0] & 0x04) !== 0; // Uint8, 0:2, static
     },
     set(v) {
-        this._sharedUint8Arr[2] = (v ? 1 : 0);
+        v ? this._sharedUint8Arr[0] |= 0x04 : this._sharedUint8Arr[0] &= ~0x04; // Uint8, 0:2, static
+    },
+});
+
+Object.defineProperty(nodeProto, '_colorDirty', {
+    configurable: true,
+    enumerable: true,
+    get(): Readonly<Boolean> {
+        return (this._sharedUint8Arr[0] & 0x08) !== 0; // Uint8, 0:3, _colorDirty
+    },
+    set(v) {
+        v ? this._sharedUint8Arr[0] |= 0x08 : this._sharedUint8Arr[0] &= ~0x08; // Uint8, 0:3, _colorDirty
     },
 });
 
@@ -1194,10 +1205,10 @@ Object.defineProperty(nodeProto, '_skewType', {
     configurable: true,
     enumerable: true,
     get(): number {
-        return this._sharedUint8Arr[3];
+        return this._sharedUint8Arr[1];
     },
     set(v) {
-        this._sharedUint8Arr[3] = v;
+        this._sharedUint8Arr[1] = v;
     },
 });
 
@@ -1511,6 +1522,22 @@ nodeProto._setSkewY = function (v: number) {
     this._sharedFloat32Arr[1] = v;
 }
 
+nodeProto._getLocalOpacity = function (): number {
+    return this._sharedFloat32Arr[2];
+};
+
+nodeProto._setLocalOpacity = function (v: number): void {
+    this._sharedFloat32Arr[2] = v;
+}
+
+nodeProto._getFinalOpacity = function (): number {
+    return this._sharedFloat32Arr[3];
+};
+
+nodeProto._setFinalOpacity = function (v: number): void {
+    this._sharedFloat32Arr[3] = v;
+}
+
 //
 nodeProto._ctor = function (name?: string) {
     this.__nativeRefs = {};
@@ -1528,10 +1555,10 @@ nodeProto._ctor = function (name?: string) {
     this._sharedUint32Arr = new Uint32Array(sharedArrayBuffer, 0, 3);
     // Int32Array with 1 element: siblingIndex
     this._sharedInt32Arr = new Int32Array(sharedArrayBuffer, 12, 1);
-    // Uint8Array with 4 elements: activeInHierarchy, active, static, _hasSkewComp
+    // Uint8Array with 4 elements: (activeInHierarchy, active, static, localOpacityDirty) uses 1 bytes for booleans, skewType, 2 bytes padding
     this._sharedUint8Arr = new Uint8Array(sharedArrayBuffer, 16, 4);
-    // Float32Array with 2 elements: skewX, skewY
-    this._sharedFloat32Arr = new Float32Array(sharedArrayBuffer, 20, 2);
+    // Float32Array with 4 elements: skewX, skewY, localOpacity, finalOpacity
+    this._sharedFloat32Arr = new Float32Array(sharedArrayBuffer, 20, 4);
 
     this._sharedUint32Arr[1] = Layers.Enum.DEFAULT; // this._sharedUint32Arr[1] is layer
     this._scene = null;
diff --git a/native/cocos/2d/renderer/Batcher2d.cpp b/native/cocos/2d/renderer/Batcher2d.cpp
index 122e6ec2679..4cc38f67e6d 100644
--- a/native/cocos/2d/renderer/Batcher2d.cpp
+++ b/native/cocos/2d/renderer/Batcher2d.cpp
@@ -33,6 +33,69 @@
 
 namespace cc {
 
+namespace {
+
+CC_FORCE_INLINE void fillIndexBuffers(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
+    uint16_t* ib = drawInfo->getIDataBuffer();
+    
+    UIMeshBuffer* buffer = drawInfo->getMeshBuffer();
+    uint32_t indexOffset = buffer->getIndexOffset();
+    
+    uint16_t* indexb = drawInfo->getIbBuffer();
+    uint32_t indexCount = drawInfo->getIbCount();
+    
+    memcpy(&ib[indexOffset], indexb, indexCount * sizeof(uint16_t));
+    indexOffset += indexCount;
+    
+    buffer->setIndexOffset(indexOffset);
+}
+
+CC_FORCE_INLINE void fillVertexBuffers(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
+    Node* node = entity->getNode();
+    const Mat4& matrix = node->getWorldMatrix();
+    uint8_t stride = drawInfo->getStride();
+    uint32_t size = drawInfo->getVbCount() * stride;
+    float* vbBuffer = drawInfo->getVbBuffer();
+    for (int i = 0; i < size; i += stride) {
+        Render2dLayout* curLayout = drawInfo->getRender2dLayout(i);
+        // make sure that the layout of Vec3 is three consecutive floats
+        static_assert(sizeof(Vec3) == 3 * sizeof(float));
+        // cast to reduce value copy instructions
+        reinterpret_cast<Vec3*>(vbBuffer + i)->transformMat4(curLayout->position, matrix);
+    }
+}
+
+CC_FORCE_INLINE void setIndexRange(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
+    UIMeshBuffer* buffer = drawInfo->getMeshBuffer();
+    uint32_t indexOffset = drawInfo->getIndexOffset();
+    uint32_t indexCount = drawInfo->getIbCount();
+    indexOffset += indexCount;
+    if (buffer->getIndexOffset() < indexOffset) {
+        buffer->setIndexOffset(indexOffset);
+    }
+}
+
+CC_FORCE_INLINE void fillColor(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
+    uint8_t stride = drawInfo->getStride();
+    uint32_t size = drawInfo->getVbCount() * stride;
+    float* vbBuffer = drawInfo->getVbBuffer();
+    Color temp = entity->getColor();
+    
+    uint32_t offset = 0;
+    for (int i = 0; i < size; i += stride) {
+        offset = i + 5;
+        // NOTE: Only support RGBA32F (4 floats) color fomat now.
+        // Spine set 'UIRenderer._useVertexOpacity = true', it uses RGBA32 (4 bytes) color and fills color in Skeleton._updateColor and spine/simple.ts assembler.
+        // So for Spine rendering, it will never go here to fill color.
+        vbBuffer[offset] = static_cast<float>(temp.r) / 255.0F;
+        vbBuffer[offset+1] = static_cast<float>(temp.g) / 255.0F;
+        vbBuffer[offset+2] = static_cast<float>(temp.b) / 255.0F;
+        vbBuffer[offset+3] = entity->getOpacity();
+    }
+}
+
+} // namespace
+
 Batcher2d::Batcher2d() : Batcher2d(nullptr) {
 }
 
@@ -109,25 +172,28 @@ void Batcher2d::fillBuffersAndMergeBatches() {
     }
 }
 
-void Batcher2d::walk(Node* node, float parentOpacity, bool parentOpacityDirty) { // NOLINT(misc-no-recursion)
+void Batcher2d::walk(Node* node, float parentOpacity, bool parentColorDirty) { // NOLINT(misc-no-recursion)
     if (!node->isActiveInHierarchy()) {
         return;
     }
     bool breakWalk = false;
     auto* entity = static_cast<RenderEntity*>(node->getUserData());
-    bool opacityDirty = false;
+    
+    const bool isCurrentColorDirty = node->_isColorDirty() || parentColorDirty;
+    const float localOpacity = node->_getLocalOpacity();
+    // Keep the same logic as which in batcher-2d.ts
+    const float finalOpacity = parentOpacity * localOpacity * (entity ? entity->getColorAlpha() : 1.F);
+    node->_setFinalOpacity(finalOpacity);
+
     if (entity) {
-        if (entity->getColorDirty() || parentOpacityDirty) {
-            float localOpacity = entity->getLocalOpacity();
-            float localColorAlpha = entity->getColorAlpha();
-            entity->setOpacity(parentOpacity * localOpacity * localColorAlpha);
-            entity->setColorDirty(false);
-            entity->setVBColorDirty(true);
-            opacityDirty = true;
-        }
-        if (math::isEqualF(entity->getOpacity(), 0)) {
+        if (math::isEqualF(finalOpacity, 0)) {
             breakWalk = true;
         } else if (entity->isEnabled()) {
+            if (isCurrentColorDirty) {
+                entity->setOpacity(finalOpacity);
+                entity->setVBColorDirty(true);
+            }
+            
             uint32_t size = entity->getRenderDrawInfosSize();
             for (uint32_t i = 0; i < size; i++) {
                 auto* drawInfo = entity->getRenderDrawInfoAt(i);
@@ -135,6 +201,7 @@ void Batcher2d::walk(Node* node, float parentOpacity, bool parentOpacityDirty) {
             }
             entity->setVBColorDirty(false);
         }
+        
         if (entity->getRenderEntityType() == RenderEntityType::CROSSED) {
             breakWalk = true;
         }
@@ -142,12 +209,16 @@ void Batcher2d::walk(Node* node, float parentOpacity, bool parentOpacityDirty) {
 
     if (!breakWalk) {
         const auto& children = node->getChildren();
-        float thisOpacity = entity ? entity->getOpacity() : parentOpacity;
+        float thisOpacity = (entity && entity->isEnabled()) ? entity->getOpacity() : finalOpacity;
         for (const auto& child : children) {
             // we should find parent opacity recursively upwards if it doesn't have an entity.
-            walk(child, thisOpacity, opacityDirty || parentOpacityDirty);
+            walk(child, thisOpacity, isCurrentColorDirty);
         }
     }
+    
+    if (isCurrentColorDirty) {
+        node->_setColorDirty(false);
+    }
 
     // post assembler
     if (_stencilManager->getMaskStackSize() > 0 && entity && entity->isEnabled()) {
@@ -216,7 +287,18 @@ CC_FORCE_INLINE void Batcher2d::handleComponentDraw(RenderEntity* entity, Render
         }
 
         if (entity->getVBColorDirty()) {
-            fillColors(entity, drawInfo);
+            switch (entity->getFillColorType()) {
+                case FillColorType::COLOR: {
+                    fillColor(entity, drawInfo);
+                    break;
+                }
+                case FillColorType::VERTEX: {
+                    // Use vertex color directly, so do nothing here.
+                    break;
+                }
+                default:
+                    break;
+            }
         }
 
         fillIndexBuffers(drawInfo);
diff --git a/native/cocos/2d/renderer/Batcher2d.h b/native/cocos/2d/renderer/Batcher2d.h
index 36a16644a45..72be563bfaa 100644
--- a/native/cocos/2d/renderer/Batcher2d.h
+++ b/native/cocos/2d/renderer/Batcher2d.h
@@ -63,7 +63,7 @@ class Batcher2d final {
     void updateDescriptorSet();
 
     void fillBuffersAndMergeBatches();
-    void walk(Node* node, float parentOpacity, bool parentOpacityDirty);
+    void walk(Node* node, float parentOpacity, bool parentColorDirty);
     void handlePostRender(RenderEntity* entity);
     void handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node);
     void handleComponentDraw(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node);
@@ -77,63 +77,6 @@ class Batcher2d final {
 private:
     bool _isInit = false;
 
-    inline void fillIndexBuffers(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
-        uint16_t* ib = drawInfo->getIDataBuffer();
-
-        UIMeshBuffer* buffer = drawInfo->getMeshBuffer();
-        uint32_t indexOffset = buffer->getIndexOffset();
-
-        uint16_t* indexb = drawInfo->getIbBuffer();
-        uint32_t indexCount = drawInfo->getIbCount();
-
-        memcpy(&ib[indexOffset], indexb, indexCount * sizeof(uint16_t));
-        indexOffset += indexCount;
-
-        buffer->setIndexOffset(indexOffset);
-    }
-
-    inline void fillVertexBuffers(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
-        Node* node = entity->getNode();
-        const Mat4& matrix = node->getWorldMatrix();
-        uint8_t stride = drawInfo->getStride();
-        uint32_t size = drawInfo->getVbCount() * stride;
-        float* vbBuffer = drawInfo->getVbBuffer();
-        for (int i = 0; i < size; i += stride) {
-            Render2dLayout* curLayout = drawInfo->getRender2dLayout(i);
-            // make sure that the layout of Vec3 is three consecutive floats
-            static_assert(sizeof(Vec3) == 3 * sizeof(float));
-            // cast to reduce value copy instructions
-            reinterpret_cast<Vec3*>(vbBuffer + i)->transformMat4(curLayout->position, matrix);
-        }
-    }
-
-    inline void setIndexRange(RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
-        UIMeshBuffer* buffer = drawInfo->getMeshBuffer();
-        uint32_t indexOffset = drawInfo->getIndexOffset();
-        uint32_t indexCount = drawInfo->getIbCount();
-        indexOffset += indexCount;
-        if (buffer->getIndexOffset() < indexOffset) {
-            buffer->setIndexOffset(indexOffset);
-        }
-    }
-
-    inline void fillColors(RenderEntity* entity, RenderDrawInfo* drawInfo) { // NOLINT(readability-convert-member-functions-to-static)
-        Color temp = entity->getColor();
-
-        uint8_t stride = drawInfo->getStride();
-        uint32_t size = drawInfo->getVbCount() * stride;
-        float* vbBuffer = drawInfo->getVbBuffer();
-
-        uint32_t offset = 0;
-        for (int i = 0; i < size; i += stride) {
-            offset = i + 5;
-            vbBuffer[offset++] = static_cast<float>(temp.r) / 255.0F;
-            vbBuffer[offset++] = static_cast<float>(temp.g) / 255.0F;
-            vbBuffer[offset++] = static_cast<float>(temp.b) / 255.0F;
-            vbBuffer[offset++] = entity->getOpacity();
-        }
-    }
-
     void insertMaskBatch(RenderEntity* entity);
     void createClearModel();
 
diff --git a/native/cocos/2d/renderer/RenderDrawInfo.h b/native/cocos/2d/renderer/RenderDrawInfo.h
index f872b4c92d2..a0ba0e70f54 100644
--- a/native/cocos/2d/renderer/RenderDrawInfo.h
+++ b/native/cocos/2d/renderer/RenderDrawInfo.h
@@ -305,4 +305,9 @@ class RenderDrawInfo final {
     IntrusivePtr<gfx::Buffer> _vb;
     IntrusivePtr<gfx::Buffer> _ib;
 };
+
+#if defined(__x86_64__) || defined(__amd64__) || defined(__aarch64__)
+static_assert(sizeof(RenderDrawInfo) == 144, "Be carefull to add property to RenderDrawInfo which may cause the potential cache miss");
+#endif
+
 } // namespace cc
diff --git a/native/cocos/2d/renderer/RenderEntity.cpp b/native/cocos/2d/renderer/RenderEntity.cpp
index 3010e7e975a..1b61930703d 100644
--- a/native/cocos/2d/renderer/RenderEntity.cpp
+++ b/native/cocos/2d/renderer/RenderEntity.cpp
@@ -33,6 +33,11 @@ RenderEntity::RenderEntity(RenderEntityType type) : _renderEntityType(type) {
     } else {
         ccnew_placement(&_dynamicDrawInfos) ccstd::vector<RenderDrawInfo*>();
     }
+    
+    _entityAttrLayout.enabledIndex = 0;
+    _entityAttrLayout.useLocal = 0;
+    _entityAttrLayout.paddings = 0;
+    
     _entitySharedBufferActor.initialize(&_entityAttrLayout, sizeof(EntityAttrLayout));
 }
 
diff --git a/native/cocos/2d/renderer/RenderEntity.h b/native/cocos/2d/renderer/RenderEntity.h
index c5e4700edba..0a3e4d751ee 100644
--- a/native/cocos/2d/renderer/RenderEntity.h
+++ b/native/cocos/2d/renderer/RenderEntity.h
@@ -49,18 +49,25 @@ enum class MaskMode : uint8_t {
     MASK_NODE_INVERTED
 };
 
+enum class FillColorType: uint8_t {
+    COLOR = 0,
+    VERTEX,
+};
+
 struct EntityAttrLayout {
-    float localOpacity{1.0F};
     uint8_t colorR{255};
     uint8_t colorG{255};
     uint8_t colorB{255};
     uint8_t colorA{255};
     uint8_t maskMode{0};
-    uint8_t colorDirtyBit{1};
-    uint8_t enabledIndex{0};
-    uint8_t useLocal{0};
+    FillColorType fillColorType{FillColorType::COLOR};
+    uint8_t enabledIndex: 1;
+    uint8_t useLocal: 1;
+    uint8_t paddings: 6;
 };
 
+static_assert(sizeof(EntityAttrLayout) == 7, "Be carefull to add property to EntityAttrLayout which may cause the potential cache miss");
+
 class RenderEntity final : public Node::UserData {
 public:
     static constexpr uint32_t STATIC_DRAW_INFO_CAPACITY = 4;
@@ -90,6 +97,8 @@ class RenderEntity final : public Node::UserData {
     inline void setUseLocal(bool useLocal) {
         _entityAttrLayout.useLocal = useLocal;
     }
+    
+    inline FillColorType getFillColorType() const { return _entityAttrLayout.fillColorType; }
 
     inline Node* getNode() const { return _node; }
     void setNode(Node* node);
@@ -117,13 +126,11 @@ class RenderEntity final : public Node::UserData {
     ccstd::vector<RenderDrawInfo*>& getDynamicRenderDrawInfos();
 
     inline se::Object* getEntitySharedBufferForJS() const { return _entitySharedBufferActor.getSharedArrayBufferObject(); }
-    inline bool getColorDirty() const { return _entityAttrLayout.colorDirtyBit != 0; }
-    inline void setColorDirty(bool dirty) { _entityAttrLayout.colorDirtyBit = dirty ? 1 : 0; }
+
     inline bool getVBColorDirty() const { return _vbColorDirty; }
     inline void setVBColorDirty(bool vbColorDirty) { _vbColorDirty = vbColorDirty; }
     inline Color getColor() const { return Color(_entityAttrLayout.colorR, _entityAttrLayout.colorG, _entityAttrLayout.colorB, _entityAttrLayout.colorA); }
     inline float getColorAlpha() const { return static_cast<float>(_entityAttrLayout.colorA) / 255.F; }
-    inline float getLocalOpacity() const { return _entityAttrLayout.localOpacity; }
     inline float getOpacity() const { return _opacity; }
     inline void setOpacity(float opacity) { _opacity = opacity; }
     inline bool isEnabled() const { return _entityAttrLayout.enabledIndex != 0; }
@@ -141,18 +148,26 @@ class RenderEntity final : public Node::UserData {
 
     // weak reference
     Node* _renderTransform{nullptr};
-
-    EntityAttrLayout _entityAttrLayout;
-    float _opacity{1.0F};
-
+    
     bindings::NativeMemorySharedToScriptActor _entitySharedBufferActor;
+
     union {
         std::array<RenderDrawInfo, RenderEntity::STATIC_DRAW_INFO_CAPACITY> _staticDrawInfos;
         ccstd::vector<RenderDrawInfo*> _dynamicDrawInfos;
     };
+    EntityAttrLayout _entityAttrLayout;
+    
     StencilStage _stencilStage{StencilStage::DISABLED};
     RenderEntityType _renderEntityType{RenderEntityType::STATIC};
     uint8_t _staticDrawInfoSize{0};
     bool _vbColorDirty{true};
+    uint8_t _paddings[1];
+    
+    float _opacity{1.0F};
 };
+
+#if defined(__x86_64__) || defined(__amd64__) || defined(__aarch64__)
+static_assert(sizeof(RenderEntity) == 632, "Be carefull to add property to RenderEntity which may cause the potential cache miss");
+#endif
+
 } // namespace cc
diff --git a/native/cocos/core/data/Object.h b/native/cocos/core/data/Object.h
index fae477cfc7c..04b52b52312 100644
--- a/native/cocos/core/data/Object.h
+++ b/native/cocos/core/data/Object.h
@@ -119,12 +119,7 @@ class CCObject : public RefCounted /*cjh implements EditorExtendableObject*/ {
     };
 
     static void deferredDestroy();
-
-    // cjh    public declare [editorExtrasTag]: unknown;
-
-    Flags _objFlags{Flags::ZERO};
-    ccstd::string _name;
-
+    
     explicit CCObject(ccstd::string name = "");
     ~CCObject() override;
 
@@ -232,6 +227,10 @@ class CCObject : public RefCounted /*cjh implements EditorExtendableObject*/ {
         return true;
     }
 
+public:
+    Flags _objFlags{Flags::ZERO};
+    ccstd::string _name;
+protected:
     se::Object* _scriptObject{nullptr}; // weak reference
 };
 
diff --git a/native/cocos/core/scene-graph/Node.cpp b/native/cocos/core/scene-graph/Node.cpp
index 990f3d046b9..086d52fef19 100644
--- a/native/cocos/core/scene-graph/Node.cpp
+++ b/native/cocos/core/scene-graph/Node.cpp
@@ -51,8 +51,13 @@ Node::Node() : Node(EMPTY_NODE_NAME) {
 }
 
 Node::Node(const ccstd::string &name) {
-#define NODE_SHARED_MEMORY_BYTE_LENGTH (28)
-    static_assert(offsetof(Node, _skewY) + sizeof(_skewY) - offsetof(Node, _eventMask) == NODE_SHARED_MEMORY_BYTE_LENGTH, "Wrong shared memory size");
+    _activeInHierarchy = 0;
+    _active = 1;
+    _isStatic = 0;
+    _colorDirty = 1;
+    
+#define NODE_SHARED_MEMORY_BYTE_LENGTH (36)
+    static_assert(offsetof(Node, _finalOpacity) + sizeof(_finalOpacity) - offsetof(Node, _eventMask) == NODE_SHARED_MEMORY_BYTE_LENGTH, "Wrong shared memory size");
     _sharedMemoryActor.initialize(&_eventMask, NODE_SHARED_MEMORY_BYTE_LENGTH);
 #undef NODE_SHARED_MEMORY_BYTE_LENGTH
 
diff --git a/native/cocos/core/scene-graph/Node.h b/native/cocos/core/scene-graph/Node.h
index 50251bf9af1..f595761d62f 100644
--- a/native/cocos/core/scene-graph/Node.h
+++ b/native/cocos/core/scene-graph/Node.h
@@ -601,15 +601,18 @@ class Node : public CCObject {
     void _setChildren(ccstd::vector<IntrusivePtr<Node>> &&children); // NOLINT
 
     inline se::Object *_getSharedArrayBufferObject() const { return _sharedMemoryActor.getSharedArrayBufferObject(); } // NOLINT
+    
+    inline float _getLocalOpacity() const { return _localOpacity; } // NOLINT
+    
+    inline float _getFinalOpacity() const { return _finalOpacity; } // NOLINT
+    inline void _setFinalOpacity(float v) { _finalOpacity = v; } // NOLINT
+    
+    inline bool _isColorDirty() const { return _colorDirty == 0 ? false : true; } // NOLINT
+    inline void _setColorDirty(bool v) { _colorDirty = v ? 1 : 0; } // NOLINT
 
     bool onPreDestroy() override;
     bool onPreDestroyBase();
 
-    // For deserialization
-    ccstd::string _id;
-    Node *_parent{nullptr};
-    MobilityMode _mobility = MobilityMode::Static;
-
 private:
     static index_t getIdxOfChild(const ccstd::vector<IntrusivePtr<Node>> &, Node *);
 
@@ -662,6 +665,12 @@ class Node : public CCObject {
     static uint32_t clearFrame;
     static uint32_t clearRound;
 
+public:
+    // For deserialization
+    ccstd::string _id;
+    Node *_parent{nullptr};
+    
+private:
     Scene *_scene{nullptr};
     IntrusivePtr<UserData> _userData;
 
@@ -685,19 +694,28 @@ class Node : public CCObject {
     uint32_t _layer{static_cast<uint32_t>(Layers::LayerList::DEFAULT)}; // Uint32: 1
     uint32_t _transformFlags{static_cast<uint32_t>(TransformBit::TRS | TransformBit::SKEW)}; // Uint32: 2
     index_t _siblingIndex{0};                                           // Int32: 0
-    uint8_t _activeInHierarchy{0};                                      // Uint8: 0
-    uint8_t _active{1};                                                 // Uint8: 1
-    uint8_t _isStatic{0};                                               // Uint8: 2
-    uint8_t _skewType{static_cast<uint8_t>(SkewType::NONE)};            // Uint8: 3
+    uint8_t _activeInHierarchy: 1;                                      // Uint8: 0:0
+    uint8_t _active: 1;                                                 // Uint8: 0:1
+    uint8_t _isStatic: 1;                                               // Uint8: 0:2
+    uint8_t _colorDirty: 1;                                             // Uint8: 0:3
+    uint8_t _boolBitPaddings: 4;                                        // Uint8: 0:4~7
+    uint8_t _skewType{static_cast<uint8_t>(SkewType::NONE)};            // Uint8: 1
+    uint8_t _u8Paddings[2];                                             // Uint8: 2, 3
     float _skewX{.0F};                                                  // Float32: 0
     float _skewY{.0F};                                                  // Float32: 1
+    float _localOpacity{1.F};                                           // Float32: 2
+    float _finalOpacity{1.F};                                           // Float32: 3
 
     /* set _hasChangedFlagsVersion to globalFlagChangeVersion when `_hasChangedFlags` updated.
      * `globalFlagChangeVersion == _hasChangedFlagsVersion` means that "_hasChangedFlags is dirty in current frametime".
      */
     uint32_t _hasChangedFlagsVersion{0};
     uint32_t _hasChangedFlags{0};
+    
+public:
+    MobilityMode _mobility = MobilityMode::Static;
 
+private:
     bool _eulerDirty{false};
 
     friend class NodeActivator;
diff --git a/native/cocos/core/scene-graph/NodeEnum.h b/native/cocos/core/scene-graph/NodeEnum.h
index b2b0d4dffaa..f7bb6f1bcc6 100644
--- a/native/cocos/core/scene-graph/NodeEnum.h
+++ b/native/cocos/core/scene-graph/NodeEnum.h
@@ -48,7 +48,7 @@ CC_ENUM_BITWISE_OPERATORS(TransformBit);
  * @en Node's mobility
  * @zh 节点的移动性
  */
-enum class MobilityMode {
+enum class MobilityMode : uint8_t {
     /**
      * @en Static node
      * @zh 静态节点
diff --git a/native/tools/swig-config/2d.i b/native/tools/swig-config/2d.i
index 81bb62dec2f..eed9cd3e7dc 100644
--- a/native/tools/swig-config/2d.i
+++ b/native/tools/swig-config/2d.i
@@ -44,6 +44,8 @@
 %ignore UserData;
 %ignore cc::RefCounted;
 
+%ignore cc::EntityAttrLayout;
+
 %ignore cc::UIMeshBuffer::requireFreeIA;
 %ignore cc::UIMeshBuffer::createNewIA;
 %ignore cc::UIMeshBuffer::recycleIA;
diff --git a/platforms/native/engine/jsb-spine-skeleton.js b/platforms/native/engine/jsb-spine-skeleton.js
index 4404610023b..2b758440333 100644
--- a/platforms/native/engine/jsb-spine-skeleton.js
+++ b/platforms/native/engine/jsb-spine-skeleton.js
@@ -371,7 +371,6 @@ const cacheManager = require('./jsb-cache-manager');
             const compColor = this._color;
             this.setEntityColorDirty(true);
             this.setEntityColor(compColor);
-            this.setEntityOpacity(this.node._uiProps.localOpacity);
             this._nativeSkeleton.setColor(compColor.r, compColor.g, compColor.b, compColor.a);
             this.markForUpdateRenderData();
         }


Reviews:
user: login: dumganhar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T09:44:52Z
user: login: dumganhar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T09:45:37Z
user: login: qiuguohua
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T06:00:06Z
user: login: dumganhar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T13:03:08Z
user: login: dumganhar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-19T13:06:36Z

Review comments:
diff_hunk: @@ -89,7 +89,8 @@ class MotionStreakAssembler implements IAssembler {
         const cr = color.r;
         const cg = color.g;
         const cb = color.b;
-        const ca = color.a;
+
+        const ca = node._uiProps.opacity * color.a;
path: cocos/particle-2d/motion-streak-2d-assembler.ts
user: login: dumganhar
body: Fix UIOpacity doesn't work for MotionStreak component.
created_at: 2025-04-17T09:44:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -167,12 +169,14 @@ class MotionStreakAssembler implements IAssembler {
     }
 
     private updateWorldVertexAllData (comp: MotionStreak): void {
+        if (!JSB) return;
         const renderData = comp.renderData;
         if (!renderData) return;
         const stride = renderData.floatStride;
         const dataList = renderData.data;
         const vData = renderData.chunk.vb;
-        for (let i  = 0; i < dataList.length; i++) {
+        const vertexCount = renderData.vertexCount;
+        for (let i  = 0; i < vertexCount; i++) {
path: cocos/particle-2d/motion-streak-2d-assembler.ts
user: login: dumganhar
body: vertexCount may be smaller than dataList.length.
created_at: 2025-04-17T09:45:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,10 +93,13 @@ export function updateOpacity (renderData: RenderData, opacity: number): void {
         if (format.hasAlpha) {
             stride = renderData.floatStride;
             if (format.size / format.count === 1) {
+                if (!vbUint32View) {
+                    vbUint32View = new Uint32Array(vb.buffer, vb.byteOffset, vb.length);
+                }
                 const alpha = ~~clamp(Math.round(opacity * 255), 0, 255);
                 // Uint color RGBA8
-                for (let color = offset; color < vb.length; color += stride) {
-                    vb[color] = ((vb[color] & 0xffffff00) | alpha) >>> 0;
+                for (let color = offset; color < vbUint32View.length; color += stride) {
+                    vbUint32View[color] = ((vbUint32View[color] & 0x00ffffff) | (alpha << 24)) >>> 0;
path: cocos/2d/assembler/utils.ts
user: login: dumganhar
body: Alpha is at high bits in Uint32 and needs to use Uint32Array view to visit it.
created_at: 2025-04-19T13:03:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -498,7 +529,6 @@ export class UIRenderer extends Renderer {
         this.node._uiProps.colorDirty = true;
         this.setEntityColorDirty(true);
         this.setEntityColor(this._color);
-        this.setEntityOpacity(this.node._uiProps.localOpacity);
path: cocos/2d/framework/ui-renderer.ts
user: login: dumganhar
body: `this.node._uiProps.localOpacity` has already been synchronized to native, doesn't need to invoke `setEntityOpacity` here.
created_at: 2025-04-19T13:06:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: dumganhar
created_at: 2025-04-14T13:54:06Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-14T14:30:29Z
body: ## Code Size Check Report
| Wechat (WASM) | Before | After | Diff |
|:-:|:-:|:-:|:-:|
| 2D Empty (legacy pipeline) | 1006245 bytes | 1004779 bytes | 👍 -1466 bytes |
| 2D All (legacy pipeline) | 2670088 bytes | 2668245 bytes | 👍 -1843 bytes |
| 2D All (new pipeline) | 2758296 bytes | 2756456 bytes | 👍 -1840 bytes |
| (2D + 3D) All | 10012236 bytes | 10010384 bytes | 👍 -1852 bytes |


| Web (WASM + ASMJS) | Before | After | Diff |
|:-:|:-:|:-:|:-:|
| (2D + 3D) All | 16928360 bytes | 16926508 bytes | 👍 -1852 bytes |
## Interface Check Report
```diff
! WARNING this pull request has changed these public interfaces:

@@ -1365,12 +1365,8 @@
          */
         get renderData(): RenderData | null;
         /**
          * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
-         */
-        get useVertexOpacity(): boolean;
-        /**
-         * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
          * @en The component stencil stage (please do not any modification directly on this object)
          * @zh 组件模板缓冲状态 (注意：请不要直接修改它的值)
          */
         get stencilStage(): __private._cocos_2d_renderer_stencil_manager__Stage;
@@ -1405,12 +1401,19 @@
          * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
          */
         get renderEntity(): __private._cocos_2d_renderer_render_entity__RenderEntity;
         /**
-         * @en Marks for calculating opacity per vertex
-         * @zh 标记组件是否逐顶点计算透明度
+         * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
          */
-        protected _useVertexOpacity: boolean;
+        protected set _useVertexOpacity(val: boolean);
+        /**
+         * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
+         */
+        protected get _useVertexOpacity(): boolean;
+        /**
+         * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
+         */
+        get useVertexOpacity(): boolean;
         protected _lastParent: Node | null;
         onLoad(): void;
         __preload(): void;
         onEnable(): void;
@@ -1458,12 +1461,8 @@
         protected _updateColor(): void;
         /**
          * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
          */
-        static setEntityColorDirtyRecursively(node: Node, dirty: boolean): void;
-        /**
-         * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
-         */
         setEntityColor(color: math.Color): void;
         /**
          * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future.
          */
@@ -2229,11 +2228,8 @@
          */
         get opacity(): number;
         set opacity(value: number);
         protected _opacity: number;
-        protected _getParentOpacity(node: Node): number;
-        protected _parentChanged(): void;
-        protected _setEntityLocalOpacityRecursively(opacity: number): void;
         onEnable(): void;
         onDisable(): void;
     }
     /**
@@ -61785,13 +61781,19 @@
             STATIC = 0,
             DYNAMIC = 1,
             CROSSED = 2
         }
+        export enum _cocos_2d_renderer_render_entity__RenderEntityFillColorType {
+            COLOR = 0,
+            VERTEX = 1
+        }
         /** @mangle */
         export class _cocos_2d_renderer_render_entity__RenderEntity {
             protected _node: Node | null;
             protected _renderTransform: Node | null;
             protected _stencilStage: _cocos_2d_renderer_stencil_manager__Stage;
+            protected _colorDirty: boolean;
+            protected _enabled: boolean;
             protected _useLocal: boolean;
             protected _maskMode: _cocos_2d_renderer_render_entity__MaskMode;
             protected _floatSharedBuffer: Float32Array;
             protected _uint8SharedBuffer: Uint8Array;
@@ -61801,30 +61803,26 @@
             get renderEntityType(): _cocos_2d_renderer_render_entity__RenderEntityType;
             protected _color: math.Color;
             get color(): math.Color;
             set color(val: math.Color);
-            protected _localOpacity: number;
-            get localOpacity(): number;
-            set localOpacity(val: number);
-            protected _colorDirty: boolean;
             get colorDirty(): boolean;
             set colorDirty(val: boolean);
-            protected _enabled: boolean;
             get enabled(): boolean;
             set enabled(val: boolean);
+            setUseLocal(useLocal: boolean): void;
             constructor(entityType: _cocos_2d_renderer_render_entity__RenderEntityType);
             addDynamicRenderDrawInfo(renderDrawInfo: _cocos_2d_renderer_render_draw_info__RenderDrawInfo | null): void;
             removeDynamicRenderDrawInfo(): void;
             clearDynamicRenderDrawInfos(): void;
             clearStaticRenderDrawInfos(): void;
             clearRenderDrawInfos(): void;
             setDynamicRenderDrawInfo(renderDrawInfo: _cocos_2d_renderer_render_draw_info__RenderDrawInfo | null, index: number): void;
             setMaskMode(mode: _cocos_2d_renderer_render_entity__MaskMode): void;
+            setFillColorType(fillColorType: _cocos_2d_renderer_render_entity__RenderEntityFillColorType): void;
             getStaticRenderDrawInfo(): _cocos_2d_renderer_render_draw_info__RenderDrawInfo | null;
             setNode(node: Node | null): void;
             setRenderTransform(renderTransform: Node | null): void;
             setStencilStage(stage: _cocos_2d_renderer_stencil_manager__Stage): void;
-            setUseLocal(useLocal: boolean): void;
         }
         export enum _cocos_2d_framework_sprite_renderer__SpriteMode {
             SIMPLE = 0,
             SLICED = 1,

```

author_association: NONE
user: login: dumganhar
created_at: 2025-04-17T06:53:29Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: dumganhar
created_at: 2025-04-17T07:51:06Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-17T09:40:55Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/e5a13f43-58d2-4b25-a2a1-7cfa3bda9aec/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14510801969
## Task Details

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-17T09:40:56Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/ea4ce91b-dcdf-4004-9d3a-6694c8c68153/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14510801969
## Task Details

author_association: NONE
user: login: dumganhar
created_at: 2025-04-18T08:42:48Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-18T10:06:51Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/cfb9332e-1a66-4715-a9b7-939953348323/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14532459754
## Task Details

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-18T10:06:52Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/1f569f84-46e4-4692-b1e2-a31af9a955c0/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14532459754
## Task Details

author_association: NONE
user: login: dumganhar
created_at: 2025-04-18T14:23:33Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-18T15:40:08Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: FAIL
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/e70afc0e-70de-48ae-b227-4e6d61853267/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14536613714
## Task Details


| Platform | build | boot | runned | crashScene | FailScene | 
| :---: | :---: | :---: | :---: | :---: | :---: |  
| web-mobile | PASS | PASS | FAIL  |  | graphics-line-join | 
| ios | PASS | PASS | PASS  |  |  | 
| mac | PASS | PASS | PASS  |  |  | 

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-18T15:40:08Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/cde415fa-ff5d-4a97-85ba-3be2d8dcc2bd/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14536613714
## Task Details

author_association: NONE
user: login: dumganhar
created_at: 2025-04-18T16:00:00Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: dumganhar
created_at: 2025-04-19T08:28:09Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: dumganhar
created_at: 2025-04-19T12:22:06Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: dumganhar
created_at: 2025-04-19T13:34:10Z
body: @cocos-robot run test cases
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-19T13:46:34Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/9e89caf6-b0d7-483a-8573-5e79bf931816/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14549069720
## Task Details

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-19T13:46:35Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/ad6f04f7-e65f-4703-a872-a2c99fd3dd78/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14549069720
## Task Details

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-19T15:11:07Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/3dff9bcb-b9ee-47e1-b873-3b9659707e28/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14549643950
## Task Details

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-19T15:11:07Z
body: @dumganhar, Please check the result of `run test cases`:
- Test Platform: PR-Test
- Editor Version: v3.8.7
- Task Result: PASS
- Task URL: https://cctest.cocos.org/#/reportview/PR-TEST/986ed1b0-4abb-473e-ae38-f8393fc6f1fd/-1
- GitHub Action: https://github.com/cocos/cocos-engine/actions/runs/14549643950
## Task Details

author_association: NONE