@galacean/engine-ui 2.0.0-alpha.33 → 2.0.0-alpha.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { EntityModifyFlags, ignoreClone, assignmentClone, DisorderedArray, Component, TransformModifyFlags, Transform, deepClone, Vector2, Rect, MathUtil, Vector3, Matrix, Plane, ShaderProperty, dependentComponents, DependentMode, BatchUtils, ShaderMacroCollection, Renderer, Vector4, Color, RendererUpdateFlags, Script, CameraModifyFlags, Logger, Signal, SpriteDrawMode, RenderQueueFlags, SpriteModifyFlags, SpriteTileMode, TiledSpriteAssembler, SlicedSpriteAssembler, SimpleSpriteAssembler, BoundingBox, ShaderData, ShaderDataGroup, TextUtils, Engine, TextVerticalAlignment, TextHorizontalAlignment, OverflowMode, CharRenderInfo, FontStyle, ReferResource, registerPointerEventEmitter, PointerEventEmitter, CameraClearFlags, Material, Entity, Loader, Shader } from '@galacean/engine';
1
+ import { EntityModifyFlags, ignoreClone, assignmentClone, DisorderedArray, Component, Matrix, BoundingBox, Utils as Utils$1, TransformModifyFlags, Transform, deepClone, Vector2, Rect, MathUtil, Vector3, Plane, ShaderProperty, dependentComponents, DependentMode, VertexMergeBatcher, ShaderMacroCollection, Renderer, Vector4, Color, RendererUpdateFlags, Script, CameraModifyFlags, Logger, Signal, SpriteDrawMode, SpriteModifyFlags, SpriteTileMode, TiledSpriteAssembler, SlicedSpriteAssembler, SimpleSpriteAssembler, ShaderData, ShaderDataGroup, TextUtils, Engine, TextVerticalAlignment, TextHorizontalAlignment, OverflowMode, CharRenderInfo, FontStyle, ReferResource, registerPointerEventEmitter, PointerEventEmitter, CameraClearFlags, Material, Entity, Loader, Shader } from '@galacean/engine';
2
2
 
3
3
  function _defineProperties(target, props) {
4
4
  for (var i = 0; i < props.length; i++) {
@@ -415,6 +415,155 @@ var Utils = /*#__PURE__*/ function() {
415
415
  return Utils;
416
416
  }();
417
417
 
418
+ /**
419
+ * @internal
420
+ * Visual-layering driven UI batching: solve visual correctness first, batching falls out for free.
421
+ *
422
+ * Think of it as stacking elements onto numbered shelves (depths):
423
+ * - Non-overlapping elements share shelf 0 — free to cluster regardless of material.
424
+ * - Overlapping + batch-compatible — share the same shelf, still cluster.
425
+ * - Overlapping + batch-incompatible — bumped one shelf higher (drawn on top).
426
+ *
427
+ * `depth` is a global tag, not a spatial group: distant elements at the same visual layer
428
+ * share the same depth and get clustered together. Once depth is assigned, sort by
429
+ * (depth, material, texture, hierarchyIndex) — visual order is locked, batching is the
430
+ * by-product of materials clustering within each depth band.
431
+ *
432
+ * Overlap detection is accelerated by a 10×10 spatial grid. Grid origin and cell size
433
+ * are derived from the union AABB of input elements (the bounds transform itself is the
434
+ * work the loop must do anyway, so the union accumulation just piggybacks). This stays
435
+ * correct across SS/WS canvases, any pivot, and any element distribution — no dependency
436
+ * on canvas configuration.
437
+ */ var UIBatchSorter = /*#__PURE__*/ function() {
438
+ function UIBatchSorter() {}
439
+ /**
440
+ * Reorders `elements` in place for optimal batching.
441
+ * @param elements - in hierarchy order (depth-first traversal); mutated in place
442
+ * @param canvasWorldMatrix - reduces world bounds to canvas-local for precise overlap
443
+ */ UIBatchSorter.sort = function sort(elements, canvasWorldMatrix) {
444
+ var count = elements.length;
445
+ if (count <= 1) return;
446
+ var gridDim = this._gridDim;
447
+ var entries = this._entries;
448
+ var grid = this._grid;
449
+ var dirtyCells = this._dirtyCells;
450
+ var localBoundsPool = this._localBoundsPool;
451
+ var worldToLocal = this._worldToLocal;
452
+ Matrix.invert(canvasWorldMatrix, worldToLocal);
453
+ while(entries.length < count)entries.push(new SortEntry());
454
+ // Allocate grid once, reuse forever (each cell must be a real [], not a sparse hole)
455
+ if (grid.length < gridDim) {
456
+ while(grid.length < gridDim)grid.push([]);
457
+ for(var i = 0; i < gridDim; i++){
458
+ var row = grid[i];
459
+ while(row.length < gridDim)row.push([]);
460
+ }
461
+ }
462
+ // Clear only previously-used cells (no full sweep)
463
+ for(var k = 0, n = dirtyCells.length; k < n; k += 2){
464
+ grid[dirtyCells[k]][dirtyCells[k + 1]].length = 0;
465
+ }
466
+ dirtyCells.length = 0;
467
+ // Pass 1: transform world bounds to canvas-local + accumulate the union AABB.
468
+ // Transform is already required to do precise overlap, so the union accumulation
469
+ // is essentially free — just a few minmax compares riding on the existing pass
470
+ var unionMinX = Infinity;
471
+ var unionMinY = Infinity;
472
+ var unionMaxX = -Infinity;
473
+ var unionMaxY = -Infinity;
474
+ for(var i1 = 0; i1 < count; i1++){
475
+ var _localBoundsPool, _i;
476
+ var localBounds = (_localBoundsPool = localBoundsPool)[_i = i1] || (_localBoundsPool[_i] = new BoundingBox());
477
+ BoundingBox.transform(elements[i1].component.bounds, worldToLocal, localBounds);
478
+ var min = localBounds.min, max = localBounds.max;
479
+ if (min.x < unionMinX) unionMinX = min.x;
480
+ if (min.y < unionMinY) unionMinY = min.y;
481
+ if (max.x > unionMaxX) unionMaxX = max.x;
482
+ if (max.y > unionMaxY) unionMaxY = max.y;
483
+ }
484
+ // Guard against a degenerate union (all elements collapsed onto a single point /
485
+ // axis); the grid then collapses to cell 0 which is semantically correct
486
+ var cellSizeX = Math.max(1e-6, unionMaxX - unionMinX) / gridDim;
487
+ var cellSizeY = Math.max(1e-6, unionMaxY - unionMinY) / gridDim;
488
+ var maxCell = gridDim - 1;
489
+ // Pass 2: register each element into grid cells and compute its depth
490
+ for(var i2 = 0; i2 < count; i2++){
491
+ var element = elements[i2];
492
+ var localBounds1 = localBoundsPool[i2];
493
+ var materialId = element.material.instanceId;
494
+ var textureId = element.texture ? element.texture.instanceId : 0;
495
+ // Floor + clamp guards floating-point edge cases (a `max` exactly on the union
496
+ // boundary would otherwise produce `gridDim`)
497
+ var minCellX = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.min.x - unionMinX) / cellSizeX)));
498
+ var maxCellX = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.max.x - unionMinX) / cellSizeX)));
499
+ var minCellY = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.min.y - unionMinY) / cellSizeY)));
500
+ var maxCellY = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.max.y - unionMinY) / cellSizeY)));
501
+ // Find the deepest overlapping prior, and whether that shelf has any incompatible occupant
502
+ var maxDepth = -1;
503
+ var maxDepthIncompatible = false;
504
+ for(var cellY = minCellY; cellY <= maxCellY; cellY++){
505
+ for(var cellX = minCellX; cellX <= maxCellX; cellX++){
506
+ var cell = grid[cellX][cellY];
507
+ for(var j = 0, m = cell.length; j < m; j++){
508
+ var other = cell[j];
509
+ if (!UIBatchSorter._rectOverlap(localBounds1, other.bounds)) continue;
510
+ var otherDepth = other.depth;
511
+ var otherIncompatible = other.materialId !== materialId || other.textureId !== textureId;
512
+ if (otherDepth > maxDepth) {
513
+ maxDepth = otherDepth;
514
+ maxDepthIncompatible = otherIncompatible;
515
+ } else if (otherDepth === maxDepth && otherIncompatible) {
516
+ maxDepthIncompatible = true;
517
+ }
518
+ }
519
+ }
520
+ }
521
+ var entry = entries[i2];
522
+ entry.element = element;
523
+ entry.hierarchyIndex = i2;
524
+ // Share the deepest shelf if compatible; bump up one if blocked by an incompatible occupant
525
+ entry.depth = maxDepth < 0 ? 0 : maxDepth + (maxDepthIncompatible ? 1 : 0);
526
+ entry.materialId = materialId;
527
+ entry.textureId = textureId;
528
+ entry.bounds = localBounds1;
529
+ for(var cellY1 = minCellY; cellY1 <= maxCellY; cellY1++){
530
+ for(var cellX1 = minCellX; cellX1 <= maxCellX; cellX1++){
531
+ var cell1 = grid[cellX1][cellY1];
532
+ if (cell1.length === 0) dirtyCells.push(cellX1, cellY1);
533
+ cell1.push(entry);
534
+ }
535
+ }
536
+ }
537
+ // @ts-ignore — Utils._quickSort is @internal
538
+ Utils$1._quickSort(entries, 0, count, UIBatchSorter._compareEntries);
539
+ for(var i3 = 0; i3 < count; i3++)elements[i3] = entries[i3].element;
540
+ };
541
+ UIBatchSorter._rectOverlap = function _rectOverlap(a, b) {
542
+ return a.min.x < b.max.x && a.max.x > b.min.x && a.min.y < b.max.y && a.max.y > b.min.y;
543
+ };
544
+ UIBatchSorter._compareEntries = function _compareEntries(a, b) {
545
+ return a.depth - b.depth || a.materialId - b.materialId || a.textureId - b.textureId || a.hierarchyIndex - b.hierarchyIndex;
546
+ };
547
+ return UIBatchSorter;
548
+ }();
549
+ // Spatial-hash is fastest when cell size ≈ typical element size (one element per cell).
550
+ // UI elements typically span ~10% of the canvas → 10×10 grid.
551
+ UIBatchSorter._gridDim = 10;
552
+ UIBatchSorter._entries = new Array();
553
+ UIBatchSorter._grid = [];
554
+ // Interleaved (x, y) cell coords touched in the previous frame
555
+ UIBatchSorter._dirtyCells = new Array();
556
+ UIBatchSorter._localBoundsPool = new Array();
557
+ UIBatchSorter._worldToLocal = new Matrix();
558
+ // materialId/textureId/bounds are flattened from RenderElement to avoid
559
+ // multi-hop property access in the hot inner loop and the sort comparator.
560
+ var SortEntry = function SortEntry() {
561
+ this.hierarchyIndex = 0;
562
+ this.depth = 0;
563
+ this.materialId = 0;
564
+ this.textureId = 0;
565
+ };
566
+
418
567
  /**
419
568
  * Render mode for ui canvas.
420
569
  */ var CanvasRenderMode = /*#__PURE__*/ function(CanvasRenderMode) {
@@ -893,17 +1042,17 @@ var UIRenderer = /*#__PURE__*/ function(Renderer) {
893
1042
  }
894
1043
  var _proto = UIRenderer.prototype;
895
1044
  // @ts-ignore
896
- _proto._canBatch = function _canBatch(elementA, elementB) {
897
- return BatchUtils.canBatchSprite(elementA, elementB);
1045
+ _proto._canBatch = function _canBatch(preElement, curElement) {
1046
+ return VertexMergeBatcher.canBatchSprite(preElement, curElement);
898
1047
  };
899
1048
  // @ts-ignore
900
- _proto._batch = function _batch(elementA, elementB) {
901
- BatchUtils.batchFor2D(elementA, elementB);
1049
+ _proto._batch = function _batch(preElement, curElement) {
1050
+ VertexMergeBatcher.batch(preElement, curElement);
902
1051
  };
903
1052
  // @ts-ignore
904
- _proto._updateTransformShaderData = function _updateTransformShaderData(context, onlyMVP, batched) {
1053
+ _proto._updateTransformShaderData = function _updateTransformShaderData(context, onlyMVP) {
905
1054
  // @ts-ignore
906
- Renderer.prototype._updateTransformShaderData.call(this, context, onlyMVP, true);
1055
+ this._updateWorldSpaceTransformShaderData(context, onlyMVP);
907
1056
  };
908
1057
  // @ts-ignore
909
1058
  _proto._prepareRender = function _prepareRender(context) {
@@ -1362,7 +1511,7 @@ var UICanvas = /*#__PURE__*/ function(Component) {
1362
1511
  _inherits(UICanvas, Component);
1363
1512
  function UICanvas(entity) {
1364
1513
  var _this;
1365
- _this = Component.call(this, entity) || this, /** @internal */ _this._canvasIndex = -1, /** @internal */ _this._indexInRootCanvas = -1, /** @internal */ _this._isRootCanvasDirty = false, /** @internal */ _this._rootCanvasListeningEntities = [], /** @internal */ _this._isRootCanvas = false, /** @internal */ _this._sortDistance = 0, /** @internal */ _this._orderedRenderers = [], /** @internal */ _this._realRenderMode = 4, /** @internal */ _this._disorderedElements = new DisorderedArray(), _this._renderMode = CanvasRenderMode.WorldSpace, _this._resolutionAdaptationMode = ResolutionAdaptationMode.HeightAdaptation, _this._sortOrder = 0, _this._distance = 10, _this._referenceResolution = new Vector2(800, 600), _this._referenceResolutionPerUnit = 100, _this._hierarchyVersion = -1, _this._center = new Vector3();
1514
+ _this = Component.call(this, entity) || this, /** @internal */ _this._canvasIndex = -1, /** @internal */ _this._indexInRootCanvas = -1, /** @internal */ _this._isRootCanvasDirty = false, /** @internal */ _this._rootCanvasListeningEntities = [], /** @internal */ _this._isRootCanvas = false, /** @internal */ _this._renderElements = [], /** @internal */ _this._batchedRenderElements = [], /** @internal */ _this._sortDistance = 0, /** @internal */ _this._orderedRenderers = [], /** @internal */ _this._realRenderMode = 4, /** @internal */ _this._disorderedElements = new DisorderedArray(), _this._renderMode = CanvasRenderMode.WorldSpace, _this._resolutionAdaptationMode = ResolutionAdaptationMode.HeightAdaptation, _this._sortOrder = 0, _this._distance = 10, _this._referenceResolution = new Vector2(800, 600), _this._referenceResolutionPerUnit = 100, _this._hierarchyVersion = -1, _this._center = new Vector3();
1366
1515
  _this._onCanvasSizeListener = _this._onCanvasSizeListener.bind(_this);
1367
1516
  _this._onCameraModifyListener = _this._onCameraModifyListener.bind(_this);
1368
1517
  _this._onCameraTransformListener = _this._onCameraTransformListener.bind(_this);
@@ -1419,11 +1568,10 @@ var UICanvas = /*#__PURE__*/ function(Component) {
1419
1568
  var _this = this, engine = _this.engine, mode = _this._realRenderMode;
1420
1569
  var _context_camera = context.camera, enableFrustumCulling = _context_camera.enableFrustumCulling, cullingMask = _context_camera.cullingMask, frustum = _context_camera._frustum;
1421
1570
  var frameCount = engine.time.frameCount;
1422
- // @ts-ignore
1423
- var renderElement = this._renderElement = engine._renderElementPool.get();
1571
+ var renderElements = this._renderElements;
1572
+ renderElements.length = 0;
1424
1573
  var virtualCamera = context.virtualCamera;
1425
1574
  this._updateSortDistance(virtualCamera.isOrthographic, virtualCamera.position, virtualCamera.forward);
1426
- renderElement.set(this.sortOrder, this._sortDistance);
1427
1575
  var _engine_canvas = engine.canvas, width = _engine_canvas.width, height = _engine_canvas.height;
1428
1576
  var renderers = this._getRenderers();
1429
1577
  for(var i = 0, n = renderers.length; i < n; i++){
@@ -1452,6 +1600,13 @@ var UICanvas = /*#__PURE__*/ function(Component) {
1452
1600
  renderer._prepareRender(context);
1453
1601
  renderer._renderFrameCount = frameCount;
1454
1602
  }
1603
+ var batchedRenderElements = this._batchedRenderElements;
1604
+ batchedRenderElements.length = 0;
1605
+ UIBatchSorter.sort(renderElements, this.entity.transform.worldMatrix);
1606
+ engine._batcherManager.batch(renderElements, batchedRenderElements);
1607
+ for(var i1 = 0, n1 = batchedRenderElements.length; i1 < n1; i1++){
1608
+ batchedRenderElements[i1].subDistancePriority = i1;
1609
+ }
1455
1610
  };
1456
1611
  /**
1457
1612
  * @internal
@@ -1489,6 +1644,11 @@ var UICanvas = /*#__PURE__*/ function(Component) {
1489
1644
  this._setIsRootCanvas(false);
1490
1645
  Utils.cleanRootCanvas(this);
1491
1646
  };
1647
+ // @ts-ignore
1648
+ _proto._onDisable = function _onDisable() {
1649
+ this._renderElements.length = 0;
1650
+ this._batchedRenderElements.length = 0;
1651
+ };
1492
1652
  /**
1493
1653
  * @internal
1494
1654
  */ _proto._rootCanvasListener = function _rootCanvasListener(flag, param) {
@@ -1929,7 +2089,10 @@ __decorate([
1929
2089
  ], UICanvas.prototype, "_isRootCanvas", void 0);
1930
2090
  __decorate([
1931
2091
  ignoreClone
1932
- ], UICanvas.prototype, "_renderElement", void 0);
2092
+ ], UICanvas.prototype, "_renderElements", void 0);
2093
+ __decorate([
2094
+ ignoreClone
2095
+ ], UICanvas.prototype, "_batchedRenderElements", void 0);
1933
2096
  __decorate([
1934
2097
  ignoreClone
1935
2098
  ], UICanvas.prototype, "_sortDistance", void 0);
@@ -2120,16 +2283,14 @@ __decorate([
2120
2283
  dirtyUpdateFlag &= ~UIRendererUpdateFlags.Color;
2121
2284
  }
2122
2285
  this._dirtyUpdateFlag = dirtyUpdateFlag;
2123
- // Init sub render element.
2124
2286
  var engine = context.camera.engine;
2125
- var subRenderElement = engine._subRenderElementPool.get();
2287
+ var renderElement = engine._renderElementPool.get();
2126
2288
  var subChunk = this._subChunk;
2127
- subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk);
2128
- if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
2129
- subRenderElement.shaderPasses = material.shader.subShaders[0].passes;
2130
- subRenderElement.renderQueueFlags = RenderQueueFlags.All;
2131
- }
2132
- canvas._renderElement.addSubRenderElement(subRenderElement);
2289
+ renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk);
2290
+ renderElement.subShader = material.shader.subShaders[0];
2291
+ renderElement.priority = canvas.sortOrder;
2292
+ renderElement.distanceForSort = canvas._sortDistance;
2293
+ canvas._renderElements.push(renderElement);
2133
2294
  };
2134
2295
  _proto._onTransformChanged = function _onTransformChanged(type) {
2135
2296
  if (type & UITransformModifyFlags.Size && this._drawMode === SpriteDrawMode.Tiled) {
@@ -2392,24 +2553,25 @@ __decorate([
2392
2553
  this._setDirtyFlagFalse(UIRendererUpdateFlags.Color);
2393
2554
  }
2394
2555
  var engine = context.camera.engine;
2395
- var textSubRenderElementPool = engine._textSubRenderElementPool;
2556
+ var textRenderElementPool = engine._textRenderElementPool;
2396
2557
  var material = this.getMaterial();
2397
- var renderElement = canvas._renderElement;
2558
+ var renderElements = canvas._renderElements;
2559
+ var priority = canvas.sortOrder;
2560
+ var distanceForSort = canvas._sortDistance;
2398
2561
  var textChunks = this._textChunks;
2399
- var isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay;
2562
+ var subShader = material.shader.subShaders[0];
2400
2563
  for(var i = 0, n = textChunks.length; i < n; ++i){
2401
2564
  var // @ts-ignore
2402
- _subRenderElement;
2565
+ _renderElement;
2403
2566
  var _textChunks_i = textChunks[i], subChunk = _textChunks_i.subChunk, texture = _textChunks_i.texture;
2404
- var subRenderElement = textSubRenderElementPool.get();
2405
- subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk);
2406
- (_subRenderElement = subRenderElement).shaderData || (_subRenderElement.shaderData = new ShaderData(ShaderDataGroup.RenderElement));
2407
- subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture);
2408
- if (isOverlay) {
2409
- subRenderElement.shaderPasses = material.shader.subShaders[0].passes;
2410
- subRenderElement.renderQueueFlags = RenderQueueFlags.All;
2411
- }
2412
- renderElement.addSubRenderElement(subRenderElement);
2567
+ var renderElement = textRenderElementPool.get();
2568
+ renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk);
2569
+ (_renderElement = renderElement).shaderData || (_renderElement.shaderData = new ShaderData(ShaderDataGroup.RenderElement));
2570
+ renderElement.shaderData.setTexture(Text._textTextureProperty, texture);
2571
+ renderElement.subShader = subShader;
2572
+ renderElement.priority = priority;
2573
+ renderElement.distanceForSort = distanceForSort;
2574
+ renderElements.push(renderElement);
2413
2575
  }
2414
2576
  };
2415
2577
  _proto._resetSubFont = function _resetSubFont() {