Skip to content

Commit 0a49108

Browse files
authored
Merge pull request #13042 from CesiumGS/fix/textureatlas-slow-alloc
fix(textureatlas): Allocate texture space more incrementally on resize
2 parents 3d78834 + 3c40024 commit 0a49108

File tree

3 files changed

+81
-65
lines changed

3 files changed

+81
-65
lines changed

CHANGES.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
#### Fixes :wrench:
88

9-
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
9+
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/pull/13020)
1010
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
1111
- Fixed depth testing bug with billboards and labels clipping through models [#13012](https://github.com/CesiumGS/cesium/issues/13012)
12-
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)
12+
- Fixed unexpected outline artifacts around billboards [#4525](https://github.com/CesiumGS/cesium/issues/4525)
13+
- Fix texture coordinates in large billboard collections [#13042](https://github.com/CesiumGS/cesium/pull/13042)
1314

1415
#### Additions :tada:
1516

packages/engine/Source/Renderer/TextureAtlas.js

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -364,26 +364,17 @@ TextureAtlas.prototype._resize = function (context, queueOffset = 0) {
364364
toPack.push(queue[i]);
365365
}
366366

367-
// At minimum, the texture will need to scale to accommodate the largest width and height
368-
width = Math.max(maxWidth, width);
369-
height = Math.max(maxHeight, height);
367+
// At minimum, atlas must fit its largest input images. Texture coordinates are
368+
// compressed to 0–1 with 12-bit precision, so use power-of-two size to align pixels.
369+
width = CesiumMath.nextPowerOfTwo(Math.max(maxWidth, width));
370+
height = CesiumMath.nextPowerOfTwo(Math.max(maxHeight, height));
370371

371-
if (!context.webgl2) {
372-
width = CesiumMath.nextPowerOfTwo(width);
373-
height = CesiumMath.nextPowerOfTwo(height);
374-
}
375-
376-
// Determine by what factor the texture need to be scaled by at minimum
377-
const areaDifference = areaQueued;
378-
let scalingFactor = 1.0;
379-
while (areaDifference / width / height >= 1.0) {
380-
scalingFactor *= 2.0;
381-
382-
// Resize by one dimension
372+
// Iteratively double the smallest dimension until atlas area is (approximately) sufficient.
373+
while (areaQueued >= width * height) {
383374
if (width > height) {
384-
height *= scalingFactor;
375+
height *= 2;
385376
} else {
386-
width *= scalingFactor;
377+
width *= 2;
387378
}
388379
}
389380

packages/engine/Specs/Renderer/TextureAtlasSpec.js

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -704,56 +704,16 @@ describe("Scene/TextureAtlas", function () {
704704
expect(index2).toEqual(2);
705705
expect(index3).toEqual(3);
706706

707-
// Webgl1 textures should only be powers of 2
708-
const isWebGL2 = scene.frameState.context.webgl2;
709-
const textureWidth = isWebGL2 ? 20 : 32;
710-
const textureHeight = isWebGL2 ? 32 : 16;
707+
const textureWidth = 32;
708+
const textureHeight = 16;
711709

712710
const texture = atlas.texture;
713711
expect(texture.pixelFormat).toEqual(PixelFormat.RGBA);
714712
expect(texture.width).toEqual(textureWidth);
715713
expect(texture.height).toEqual(textureHeight);
716714

717-
if (isWebGL2) {
718-
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
719-
`
720-
....................
721-
....................
722-
....................
723-
....................
724-
....................
725-
....................
726-
2222222222..........
727-
2222222222..........
728-
2222222222..........
729-
2222222222..........
730-
2222222222..........
731-
2222222222..........
732-
2222222222..........
733-
2222222222..........
734-
2222222222..........
735-
2222222222..........
736-
3333333333333333....
737-
3333333333333333....
738-
3333333333333333....
739-
3333333333333333....
740-
3333333333333333....
741-
3333333333333333....
742-
3333333333333333....
743-
3333333333333333....
744-
3333333333333333....
745-
3333333333333333....
746-
3333333333333333....
747-
3333333333333333....
748-
33333333333333330...
749-
33333333333333330...
750-
33333333333333330...
751-
333333333333333301..
752-
`.trim(),
753-
);
754-
} else {
755-
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
756-
`
715+
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
716+
`
757717
3333333333333333................
758718
3333333333333333................
759719
3333333333333333................
@@ -771,8 +731,7 @@ describe("Scene/TextureAtlas", function () {
771731
333333333333333322222222220.....
772732
3333333333333333222222222201....
773733
`.trim(),
774-
);
775-
}
734+
);
776735

777736
let textureCoordinates = atlas.computeTextureCoordinates(index0);
778737
expect(
@@ -1456,6 +1415,71 @@ describe("Scene/TextureAtlas", function () {
14561415
expect(guid1).not.toEqual(guid2);
14571416
});
14581417

1418+
it("allocates appropriate space on resize", async function () {
1419+
const imageWidth = 128;
1420+
const imageHeight = 64;
1421+
1422+
await addImages(25);
1423+
let inputPixels = 25 * imageWidth * imageHeight;
1424+
let atlasWidth = atlas.texture.width;
1425+
let atlasHeight = atlas.texture.height;
1426+
1427+
// Allocate enough space, but not >>2x more. Aspect ratio should be 1:1, 1:2, or 2:1.
1428+
expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
1429+
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
1430+
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
1431+
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);
1432+
1433+
await addImages(75);
1434+
inputPixels = 75 * imageWidth * imageHeight;
1435+
atlasWidth = atlas.texture.width;
1436+
atlasHeight = atlas.texture.height;
1437+
1438+
expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
1439+
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
1440+
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
1441+
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);
1442+
1443+
await addImages(256);
1444+
inputPixels = 256 * imageWidth * imageHeight;
1445+
atlasWidth = atlas.texture.width;
1446+
atlasHeight = atlas.texture.height;
1447+
1448+
expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
1449+
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
1450+
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
1451+
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);
1452+
1453+
async function addImages(count) {
1454+
atlas = new TextureAtlas();
1455+
1456+
const imageUrl = createImageDataURL(imageWidth, imageHeight);
1457+
1458+
const promises = [];
1459+
for (let i = 0; i < count; i++) {
1460+
promises.push(atlas.addImage(i.toString(), imageUrl));
1461+
}
1462+
1463+
await pollWhilePromise(Promise.all(promises), () => {
1464+
atlas.update(scene.frameState.context);
1465+
});
1466+
1467+
return count * imageWidth * imageHeight;
1468+
}
1469+
1470+
function createImageDataURL(width, height) {
1471+
const canvas = document.createElement("canvas");
1472+
canvas.width = width;
1473+
canvas.height = height;
1474+
1475+
const ctx = canvas.getContext("2d");
1476+
ctx.fillStyle = "green";
1477+
ctx.fillRect(0, 0, width, height);
1478+
1479+
return canvas.toDataURL();
1480+
}
1481+
});
1482+
14591483
it("destroys successfully while image is queued", async function () {
14601484
atlas = new TextureAtlas();
14611485

0 commit comments

Comments
 (0)