2024-05-28 19:07:09 +08:00
|
|
|
import { View } from "cc";
|
|
|
|
|
import { EditBox } from "cc";
|
2024-01-19 11:35:46 +08:00
|
|
|
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();
|
2024-05-28 19:07:09 +08:00
|
|
|
this._extendEditBoxTemp();
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static _extendRender3_x() {
|
2025-10-27 10:28:18 +08:00
|
|
|
const Batcher2D = this.getBatcher2D();
|
|
|
|
|
const batchQueue: Node[][] = [];
|
2024-01-19 11:35:46 +08:00
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
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] = [];
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
2025-10-27 10:28:18 +08:00
|
|
|
|
|
|
|
|
if (node["__levelRender"]) {
|
|
|
|
|
node["__levelLayer"] = layer;
|
|
|
|
|
} else {
|
|
|
|
|
if (node.parent && node.parent["__levelLayer"]) {
|
|
|
|
|
layer = node.parent["__levelLayer"] + 1;
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 10:28:18 +08:00
|
|
|
|
|
|
|
|
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;
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
//出队
|
|
|
|
|
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;
|
2024-05-28 19:07:09 +08:00
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
// 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++;
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
// Render assembler update logic
|
|
|
|
|
if (render && render.enabledInHierarchy) {
|
|
|
|
|
render.fillBuffers(this);// for rendering
|
|
|
|
|
}
|
2024-01-19 11:35:46 +08:00
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
// 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();
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
2025-10-27 10:28:18 +08:00
|
|
|
}
|
2024-01-19 11:35:46 +08:00
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
const isLevelRender = node["__levelRender"] || node["__levelRenderFlag"];
|
|
|
|
|
if (!isLevelRender) {
|
2024-01-19 11:35:46 +08:00
|
|
|
if (children.length > 0 && !node._static) {
|
2025-10-27 10:28:18 +08:00
|
|
|
for (let i = 0; i < children.length; ++i) {
|
|
|
|
|
const child = children[i];
|
|
|
|
|
this.walk(child, level);
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 10:28:18 +08:00
|
|
|
} else {
|
|
|
|
|
if (!node["__levelRenderFlag"]) {
|
|
|
|
|
levelRender(this, node, 0);
|
|
|
|
|
return;
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 10:28:18 +08:00
|
|
|
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();
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 10:28:18 +08:00
|
|
|
|
|
|
|
|
level += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static getBatcher2D() {
|
|
|
|
|
return cclegacy["internal"]["Batcher2D"];
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
2024-05-28 19:07:09 +08:00
|
|
|
|
|
|
|
|
private static _extendEditBoxTemp() {
|
|
|
|
|
EditBox.prototype.onDestroy = function () {
|
|
|
|
|
if (this._impl) {
|
|
|
|
|
View.instance.targetOff(this._impl);
|
|
|
|
|
this._impl.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-19 11:35:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|