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

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/browser.js CHANGED
@@ -419,6 +419,155 @@
419
419
  return Utils;
420
420
  }();
421
421
 
422
+ /**
423
+ * @internal
424
+ * Visual-layering driven UI batching: solve visual correctness first, batching falls out for free.
425
+ *
426
+ * Think of it as stacking elements onto numbered shelves (depths):
427
+ * - Non-overlapping elements share shelf 0 — free to cluster regardless of material.
428
+ * - Overlapping + batch-compatible — share the same shelf, still cluster.
429
+ * - Overlapping + batch-incompatible — bumped one shelf higher (drawn on top).
430
+ *
431
+ * `depth` is a global tag, not a spatial group: distant elements at the same visual layer
432
+ * share the same depth and get clustered together. Once depth is assigned, sort by
433
+ * (depth, material, texture, hierarchyIndex) — visual order is locked, batching is the
434
+ * by-product of materials clustering within each depth band.
435
+ *
436
+ * Overlap detection is accelerated by a 10×10 spatial grid. Grid origin and cell size
437
+ * are derived from the union AABB of input elements (the bounds transform itself is the
438
+ * work the loop must do anyway, so the union accumulation just piggybacks). This stays
439
+ * correct across SS/WS canvases, any pivot, and any element distribution — no dependency
440
+ * on canvas configuration.
441
+ */ var UIBatchSorter = /*#__PURE__*/ function() {
442
+ function UIBatchSorter() {}
443
+ /**
444
+ * Reorders `elements` in place for optimal batching.
445
+ * @param elements - in hierarchy order (depth-first traversal); mutated in place
446
+ * @param canvasWorldMatrix - reduces world bounds to canvas-local for precise overlap
447
+ */ UIBatchSorter.sort = function sort(elements, canvasWorldMatrix) {
448
+ var count = elements.length;
449
+ if (count <= 1) return;
450
+ var gridDim = this._gridDim;
451
+ var entries = this._entries;
452
+ var grid = this._grid;
453
+ var dirtyCells = this._dirtyCells;
454
+ var localBoundsPool = this._localBoundsPool;
455
+ var worldToLocal = this._worldToLocal;
456
+ engine.Matrix.invert(canvasWorldMatrix, worldToLocal);
457
+ while(entries.length < count)entries.push(new SortEntry());
458
+ // Allocate grid once, reuse forever (each cell must be a real [], not a sparse hole)
459
+ if (grid.length < gridDim) {
460
+ while(grid.length < gridDim)grid.push([]);
461
+ for(var i = 0; i < gridDim; i++){
462
+ var row = grid[i];
463
+ while(row.length < gridDim)row.push([]);
464
+ }
465
+ }
466
+ // Clear only previously-used cells (no full sweep)
467
+ for(var k = 0, n = dirtyCells.length; k < n; k += 2){
468
+ grid[dirtyCells[k]][dirtyCells[k + 1]].length = 0;
469
+ }
470
+ dirtyCells.length = 0;
471
+ // Pass 1: transform world bounds to canvas-local + accumulate the union AABB.
472
+ // Transform is already required to do precise overlap, so the union accumulation
473
+ // is essentially free — just a few minmax compares riding on the existing pass
474
+ var unionMinX = Infinity;
475
+ var unionMinY = Infinity;
476
+ var unionMaxX = -Infinity;
477
+ var unionMaxY = -Infinity;
478
+ for(var i1 = 0; i1 < count; i1++){
479
+ var _localBoundsPool, _i;
480
+ var localBounds = (_localBoundsPool = localBoundsPool)[_i = i1] || (_localBoundsPool[_i] = new engine.BoundingBox());
481
+ engine.BoundingBox.transform(elements[i1].component.bounds, worldToLocal, localBounds);
482
+ var min = localBounds.min, max = localBounds.max;
483
+ if (min.x < unionMinX) unionMinX = min.x;
484
+ if (min.y < unionMinY) unionMinY = min.y;
485
+ if (max.x > unionMaxX) unionMaxX = max.x;
486
+ if (max.y > unionMaxY) unionMaxY = max.y;
487
+ }
488
+ // Guard against a degenerate union (all elements collapsed onto a single point /
489
+ // axis); the grid then collapses to cell 0 which is semantically correct
490
+ var cellSizeX = Math.max(1e-6, unionMaxX - unionMinX) / gridDim;
491
+ var cellSizeY = Math.max(1e-6, unionMaxY - unionMinY) / gridDim;
492
+ var maxCell = gridDim - 1;
493
+ // Pass 2: register each element into grid cells and compute its depth
494
+ for(var i2 = 0; i2 < count; i2++){
495
+ var element = elements[i2];
496
+ var localBounds1 = localBoundsPool[i2];
497
+ var materialId = element.material.instanceId;
498
+ var textureId = element.texture ? element.texture.instanceId : 0;
499
+ // Floor + clamp guards floating-point edge cases (a `max` exactly on the union
500
+ // boundary would otherwise produce `gridDim`)
501
+ var minCellX = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.min.x - unionMinX) / cellSizeX)));
502
+ var maxCellX = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.max.x - unionMinX) / cellSizeX)));
503
+ var minCellY = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.min.y - unionMinY) / cellSizeY)));
504
+ var maxCellY = Math.min(maxCell, Math.max(0, Math.floor((localBounds1.max.y - unionMinY) / cellSizeY)));
505
+ // Find the deepest overlapping prior, and whether that shelf has any incompatible occupant
506
+ var maxDepth = -1;
507
+ var maxDepthIncompatible = false;
508
+ for(var cellY = minCellY; cellY <= maxCellY; cellY++){
509
+ for(var cellX = minCellX; cellX <= maxCellX; cellX++){
510
+ var cell = grid[cellX][cellY];
511
+ for(var j = 0, m = cell.length; j < m; j++){
512
+ var other = cell[j];
513
+ if (!UIBatchSorter._rectOverlap(localBounds1, other.bounds)) continue;
514
+ var otherDepth = other.depth;
515
+ var otherIncompatible = other.materialId !== materialId || other.textureId !== textureId;
516
+ if (otherDepth > maxDepth) {
517
+ maxDepth = otherDepth;
518
+ maxDepthIncompatible = otherIncompatible;
519
+ } else if (otherDepth === maxDepth && otherIncompatible) {
520
+ maxDepthIncompatible = true;
521
+ }
522
+ }
523
+ }
524
+ }
525
+ var entry = entries[i2];
526
+ entry.element = element;
527
+ entry.hierarchyIndex = i2;
528
+ // Share the deepest shelf if compatible; bump up one if blocked by an incompatible occupant
529
+ entry.depth = maxDepth < 0 ? 0 : maxDepth + (maxDepthIncompatible ? 1 : 0);
530
+ entry.materialId = materialId;
531
+ entry.textureId = textureId;
532
+ entry.bounds = localBounds1;
533
+ for(var cellY1 = minCellY; cellY1 <= maxCellY; cellY1++){
534
+ for(var cellX1 = minCellX; cellX1 <= maxCellX; cellX1++){
535
+ var cell1 = grid[cellX1][cellY1];
536
+ if (cell1.length === 0) dirtyCells.push(cellX1, cellY1);
537
+ cell1.push(entry);
538
+ }
539
+ }
540
+ }
541
+ // @ts-ignore — Utils._quickSort is @internal
542
+ engine.Utils._quickSort(entries, 0, count, UIBatchSorter._compareEntries);
543
+ for(var i3 = 0; i3 < count; i3++)elements[i3] = entries[i3].element;
544
+ };
545
+ UIBatchSorter._rectOverlap = function _rectOverlap(a, b) {
546
+ 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;
547
+ };
548
+ UIBatchSorter._compareEntries = function _compareEntries(a, b) {
549
+ return a.depth - b.depth || a.materialId - b.materialId || a.textureId - b.textureId || a.hierarchyIndex - b.hierarchyIndex;
550
+ };
551
+ return UIBatchSorter;
552
+ }();
553
+ // Spatial-hash is fastest when cell size ≈ typical element size (one element per cell).
554
+ // UI elements typically span ~10% of the canvas → 10×10 grid.
555
+ UIBatchSorter._gridDim = 10;
556
+ UIBatchSorter._entries = new Array();
557
+ UIBatchSorter._grid = [];
558
+ // Interleaved (x, y) cell coords touched in the previous frame
559
+ UIBatchSorter._dirtyCells = new Array();
560
+ UIBatchSorter._localBoundsPool = new Array();
561
+ UIBatchSorter._worldToLocal = new engine.Matrix();
562
+ // materialId/textureId/bounds are flattened from RenderElement to avoid
563
+ // multi-hop property access in the hot inner loop and the sort comparator.
564
+ var SortEntry = function SortEntry() {
565
+ this.hierarchyIndex = 0;
566
+ this.depth = 0;
567
+ this.materialId = 0;
568
+ this.textureId = 0;
569
+ };
570
+
422
571
  /**
423
572
  * Render mode for ui canvas.
424
573
  */ var CanvasRenderMode = /*#__PURE__*/ function(CanvasRenderMode) {
@@ -897,17 +1046,17 @@
897
1046
  }
898
1047
  var _proto = UIRenderer.prototype;
899
1048
  // @ts-ignore
900
- _proto._canBatch = function _canBatch(elementA, elementB) {
901
- return engine.BatchUtils.canBatchSprite(elementA, elementB);
1049
+ _proto._canBatch = function _canBatch(preElement, curElement) {
1050
+ return engine.VertexMergeBatcher.canBatchSprite(preElement, curElement);
902
1051
  };
903
1052
  // @ts-ignore
904
- _proto._batch = function _batch(elementA, elementB) {
905
- engine.BatchUtils.batchFor2D(elementA, elementB);
1053
+ _proto._batch = function _batch(preElement, curElement) {
1054
+ engine.VertexMergeBatcher.batch(preElement, curElement);
906
1055
  };
907
1056
  // @ts-ignore
908
- _proto._updateTransformShaderData = function _updateTransformShaderData(context, onlyMVP, batched) {
1057
+ _proto._updateTransformShaderData = function _updateTransformShaderData(context, onlyMVP) {
909
1058
  // @ts-ignore
910
- Renderer.prototype._updateTransformShaderData.call(this, context, onlyMVP, true);
1059
+ this._updateWorldSpaceTransformShaderData(context, onlyMVP);
911
1060
  };
912
1061
  // @ts-ignore
913
1062
  _proto._prepareRender = function _prepareRender(context) {
@@ -1366,7 +1515,7 @@
1366
1515
  _inherits(UICanvas, Component);
1367
1516
  function UICanvas(entity) {
1368
1517
  var _this;
1369
- _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 engine.DisorderedArray(), _this._renderMode = CanvasRenderMode.WorldSpace, _this._resolutionAdaptationMode = ResolutionAdaptationMode.HeightAdaptation, _this._sortOrder = 0, _this._distance = 10, _this._referenceResolution = new engine.Vector2(800, 600), _this._referenceResolutionPerUnit = 100, _this._hierarchyVersion = -1, _this._center = new engine.Vector3();
1518
+ _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 engine.DisorderedArray(), _this._renderMode = CanvasRenderMode.WorldSpace, _this._resolutionAdaptationMode = ResolutionAdaptationMode.HeightAdaptation, _this._sortOrder = 0, _this._distance = 10, _this._referenceResolution = new engine.Vector2(800, 600), _this._referenceResolutionPerUnit = 100, _this._hierarchyVersion = -1, _this._center = new engine.Vector3();
1370
1519
  _this._onCanvasSizeListener = _this._onCanvasSizeListener.bind(_this);
1371
1520
  _this._onCameraModifyListener = _this._onCameraModifyListener.bind(_this);
1372
1521
  _this._onCameraTransformListener = _this._onCameraTransformListener.bind(_this);
@@ -1423,11 +1572,10 @@
1423
1572
  var _this = this, engine = _this.engine, mode = _this._realRenderMode;
1424
1573
  var _context_camera = context.camera, enableFrustumCulling = _context_camera.enableFrustumCulling, cullingMask = _context_camera.cullingMask, frustum = _context_camera._frustum;
1425
1574
  var frameCount = engine.time.frameCount;
1426
- // @ts-ignore
1427
- var renderElement = this._renderElement = engine._renderElementPool.get();
1575
+ var renderElements = this._renderElements;
1576
+ renderElements.length = 0;
1428
1577
  var virtualCamera = context.virtualCamera;
1429
1578
  this._updateSortDistance(virtualCamera.isOrthographic, virtualCamera.position, virtualCamera.forward);
1430
- renderElement.set(this.sortOrder, this._sortDistance);
1431
1579
  var _engine_canvas = engine.canvas, width = _engine_canvas.width, height = _engine_canvas.height;
1432
1580
  var renderers = this._getRenderers();
1433
1581
  for(var i = 0, n = renderers.length; i < n; i++){
@@ -1456,6 +1604,13 @@
1456
1604
  renderer._prepareRender(context);
1457
1605
  renderer._renderFrameCount = frameCount;
1458
1606
  }
1607
+ var batchedRenderElements = this._batchedRenderElements;
1608
+ batchedRenderElements.length = 0;
1609
+ UIBatchSorter.sort(renderElements, this.entity.transform.worldMatrix);
1610
+ engine._batcherManager.batch(renderElements, batchedRenderElements);
1611
+ for(var i1 = 0, n1 = batchedRenderElements.length; i1 < n1; i1++){
1612
+ batchedRenderElements[i1].subDistancePriority = i1;
1613
+ }
1459
1614
  };
1460
1615
  /**
1461
1616
  * @internal
@@ -1493,6 +1648,11 @@
1493
1648
  this._setIsRootCanvas(false);
1494
1649
  Utils.cleanRootCanvas(this);
1495
1650
  };
1651
+ // @ts-ignore
1652
+ _proto._onDisable = function _onDisable() {
1653
+ this._renderElements.length = 0;
1654
+ this._batchedRenderElements.length = 0;
1655
+ };
1496
1656
  /**
1497
1657
  * @internal
1498
1658
  */ _proto._rootCanvasListener = function _rootCanvasListener(flag, param) {
@@ -1933,7 +2093,10 @@
1933
2093
  ], exports.UICanvas.prototype, "_isRootCanvas", void 0);
1934
2094
  __decorate([
1935
2095
  engine.ignoreClone
1936
- ], exports.UICanvas.prototype, "_renderElement", void 0);
2096
+ ], exports.UICanvas.prototype, "_renderElements", void 0);
2097
+ __decorate([
2098
+ engine.ignoreClone
2099
+ ], exports.UICanvas.prototype, "_batchedRenderElements", void 0);
1937
2100
  __decorate([
1938
2101
  engine.ignoreClone
1939
2102
  ], exports.UICanvas.prototype, "_sortDistance", void 0);
@@ -2124,16 +2287,14 @@
2124
2287
  dirtyUpdateFlag &= ~UIRendererUpdateFlags.Color;
2125
2288
  }
2126
2289
  this._dirtyUpdateFlag = dirtyUpdateFlag;
2127
- // Init sub render element.
2128
2290
  var engine$1 = context.camera.engine;
2129
- var subRenderElement = engine$1._subRenderElementPool.get();
2291
+ var renderElement = engine$1._renderElementPool.get();
2130
2292
  var subChunk = this._subChunk;
2131
- subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk);
2132
- if (canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay) {
2133
- subRenderElement.shaderPasses = material.shader.subShaders[0].passes;
2134
- subRenderElement.renderQueueFlags = engine.RenderQueueFlags.All;
2135
- }
2136
- canvas._renderElement.addSubRenderElement(subRenderElement);
2293
+ renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, this.sprite.texture, subChunk);
2294
+ renderElement.subShader = material.shader.subShaders[0];
2295
+ renderElement.priority = canvas.sortOrder;
2296
+ renderElement.distanceForSort = canvas._sortDistance;
2297
+ canvas._renderElements.push(renderElement);
2137
2298
  };
2138
2299
  _proto._onTransformChanged = function _onTransformChanged(type) {
2139
2300
  if (type & UITransformModifyFlags.Size && this._drawMode === engine.SpriteDrawMode.Tiled) {
@@ -2396,24 +2557,25 @@
2396
2557
  this._setDirtyFlagFalse(UIRendererUpdateFlags.Color);
2397
2558
  }
2398
2559
  var engine$1 = context.camera.engine;
2399
- var textSubRenderElementPool = engine$1._textSubRenderElementPool;
2560
+ var textRenderElementPool = engine$1._textRenderElementPool;
2400
2561
  var material = this.getMaterial();
2401
- var renderElement = canvas._renderElement;
2562
+ var renderElements = canvas._renderElements;
2563
+ var priority = canvas.sortOrder;
2564
+ var distanceForSort = canvas._sortDistance;
2402
2565
  var textChunks = this._textChunks;
2403
- var isOverlay = canvas._realRenderMode === CanvasRenderMode.ScreenSpaceOverlay;
2566
+ var subShader = material.shader.subShaders[0];
2404
2567
  for(var i = 0, n = textChunks.length; i < n; ++i){
2405
2568
  var // @ts-ignore
2406
- _subRenderElement;
2569
+ _renderElement;
2407
2570
  var _textChunks_i = textChunks[i], subChunk = _textChunks_i.subChunk, texture = _textChunks_i.texture;
2408
- var subRenderElement = textSubRenderElementPool.get();
2409
- subRenderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk);
2410
- (_subRenderElement = subRenderElement).shaderData || (_subRenderElement.shaderData = new engine.ShaderData(engine.ShaderDataGroup.RenderElement));
2411
- subRenderElement.shaderData.setTexture(Text._textTextureProperty, texture);
2412
- if (isOverlay) {
2413
- subRenderElement.shaderPasses = material.shader.subShaders[0].passes;
2414
- subRenderElement.renderQueueFlags = engine.RenderQueueFlags.All;
2415
- }
2416
- renderElement.addSubRenderElement(subRenderElement);
2571
+ var renderElement = textRenderElementPool.get();
2572
+ renderElement.set(this, material, subChunk.chunk.primitive, subChunk.subMesh, texture, subChunk);
2573
+ (_renderElement = renderElement).shaderData || (_renderElement.shaderData = new engine.ShaderData(engine.ShaderDataGroup.RenderElement));
2574
+ renderElement.shaderData.setTexture(Text._textTextureProperty, texture);
2575
+ renderElement.subShader = subShader;
2576
+ renderElement.priority = priority;
2577
+ renderElement.distanceForSort = distanceForSort;
2578
+ renderElements.push(renderElement);
2417
2579
  }
2418
2580
  };
2419
2581
  _proto._resetSubFont = function _resetSubFont() {