import { View } from "cc"; import { EditBox } from "cc"; import { EPSILON, Node, RenderData, StencilManager, UIRenderer, approx, cclegacy, clamp, gfx, log } from "cc"; enum Stage { // Stencil disabled DISABLED = 0, // Clear stencil buffer CLEAR = 1, // Entering a new level, should handle new stencil ENTER_LEVEL = 2, // In content ENABLED = 3, // Exiting a level, should restore old stencil or disable EXIT_LEVEL = 4, // Clear stencil buffer & USE INVERTED CLEAR_INVERTED = 5, // Entering a new level & USE INVERTED ENTER_LEVEL_INVERTED = 6, } export default class CCCExtension { static init() { this._extendRender3_x(); this._extendEditBoxTemp(); } private static _extendRender3_x() { const Batcher2D = this.getBatcher2D(); const batchQueue: Node[][] = []; const processNode = (batcher2D, node: Node) => { node["__levelRenderFlag"] = true; batcher2D.walk(node, 0); node["__levelRenderFlag"] = false; } //入队 const enqueue = (node: Node, layer = 0) => { if (!batchQueue[layer]) { batchQueue[layer] = []; } if (node["__levelRender"]) { node["__levelLayer"] = layer; } else { if (node.parent && node.parent["__levelLayer"]) { layer = node.parent["__levelLayer"] + 1; } } if (node.activeInHierarchy) { const queue = batchQueue[layer]; queue.push(node); } layer++; for (let i = 0; i < node.children.length; ++i) { const child = node.children[i]; layer = enqueue(child, layer); } return layer; } //出队 const dequeue = (batcher2D) => batchQueue.forEach(queue => queue.forEach(n => processNode(batcher2D, n))); // 层级渲染 const levelRender = (batcher2D, node: Node, layer = 0) => { if (!node) { return; } processNode(batcher2D, node); node.children.forEach(children => { enqueue(children, layer) }) dequeue(batcher2D); batchQueue.forEach(q => q.length = 0); } Batcher2D.prototype.walk = function (node: Node, level = 0) { if (!node.activeInHierarchy) { return; } const children = node.children; const uiProps = node._uiProps; const render = uiProps.uiComp as UIRenderer; // Save opacity let parentOpacity = this._pOpacity; if (node.parent) { parentOpacity = node.parent._uiProps.opacity; } let opacity = parentOpacity; // TODO Always cascade ui property's local opacity before remove it const selfOpacity = render && render.color ? render.color.a / 255 : 1; this._pOpacity = opacity *= selfOpacity * uiProps.localOpacity; // TODO Set opacity to ui property's opacity before remove it if (uiProps[`setOpacity`]) { uiProps[`setOpacity`](opacity); } uiProps[`_opacity`] = opacity; if (!approx(opacity, 0, EPSILON)) { if (uiProps.colorDirty) { // Cascade color dirty state this._opacityDirty++; } // Render assembler update logic if (render && render.enabledInHierarchy) { render.fillBuffers(this);// for rendering } // 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 (buffer) { buffer.setDirty(); } } const isLevelRender = node["__levelRender"] || node["__levelRenderFlag"]; if (!isLevelRender) { if (children.length > 0 && !node._static) { for (let i = 0; i < children.length; ++i) { const child = children[i]; this.walk(child, level); } } } else { if (!node["__levelRenderFlag"]) { levelRender(this, node, 0); return; } } if (uiProps.colorDirty) { // Reduce cascaded color dirty state this._opacityDirty--; // Reset color dirty uiProps.colorDirty = false; } } // Restore opacity this._pOpacity = parentOpacity; // Post render assembler update logic // ATTENTION: Will also reset colorDirty inside postUpdateAssembler if (render && render.enabledInHierarchy) { render.postUpdateAssembler(this); if ((render.stencilStage as any === Stage.ENTER_LEVEL || render.stencilStage as any === Stage.ENTER_LEVEL_INVERTED) && (StencilManager.sharedManager!.getMaskStackSize() > 0)) { this.autoMergeBatches(this._currComponent!); this.resetRenderStates(); StencilManager.sharedManager!.exitMask(); } } level += 1; } } private static getBatcher2D() { return cclegacy["internal"]["Batcher2D"]; } private static _extendEditBoxTemp() { EditBox.prototype.onDestroy = function () { if (this._impl) { View.instance.targetOff(this._impl); this._impl.clear(); } } } } export function updateOpacity(renderData: RenderData, opacity: number) { const vfmt = renderData.vertexFormat; const vb = renderData.chunk.vb; let attr; let format; let stride; // Color component offset let offset = 0; for (let i = 0; i < vfmt.length; ++i) { attr = vfmt[i]; format = gfx.FormatInfos[attr.format]; if (format.hasAlpha) { stride = renderData.floatStride; if (format.size / format.count === 1) { 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; } } else if (format.size / format.count === 4) { // RGBA32 color, alpha at position 3 for (let alpha = offset + 3; alpha < vb.length; alpha += stride) { vb[alpha] = opacity; } } } offset += format.size >> 2; } }