Skip to content

Commit fd7d74a

Browse files
committed
fix(textureatlas): Allocate texture space less aggressively on resize
1 parent ee2b381 commit fd7d74a

File tree

2 files changed

+63
-63
lines changed

2 files changed

+63
-63
lines changed

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: 55 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,56 @@ describe("Scene/TextureAtlas", function () {
14561415
expect(guid1).not.toEqual(guid2);
14571416
});
14581417

1418+
it("allocates appropriate space on resize", async function () {
1419+
atlas = new TextureAtlas();
1420+
1421+
// Adapted from https://github.com/CesiumGS/cesium/issues/5154, this represents a total
1422+
// of 2M pixels to be allocated.
1423+
const imageCount = 256;
1424+
const imageWidth = 128;
1425+
const imageHeight = 64;
1426+
1427+
const totalPixels = imageCount * imageWidth * imageHeight;
1428+
1429+
const imageUrl = createImageDataURL(imageWidth, imageHeight);
1430+
1431+
const promises = [];
1432+
for (let i = 0; i < imageCount; i++) {
1433+
promises.push(atlas.addImage(i.toString(), imageUrl));
1434+
}
1435+
1436+
const allPromises = Promise.all(promises);
1437+
1438+
await pollWhilePromise(allPromises, () => {
1439+
atlas.update(scene.frameState.context);
1440+
});
1441+
1442+
await allPromises;
1443+
1444+
const atlasWidth = atlas.texture.width;
1445+
const atlasHeight = atlas.texture.height;
1446+
1447+
// Allocate enough space, but not >2x more.
1448+
expect(atlasWidth * atlasHeight).toBeGreaterThan(totalPixels);
1449+
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(totalPixels * 2);
1450+
1451+
// Aspect ratio should be no more extreme than 1:2.
1452+
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
1453+
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);
1454+
1455+
function createImageDataURL(width, height) {
1456+
const canvas = document.createElement("canvas");
1457+
canvas.width = width;
1458+
canvas.height = height;
1459+
1460+
const ctx = canvas.getContext("2d");
1461+
ctx.fillStyle = "green";
1462+
ctx.fillRect(0, 0, width, height);
1463+
1464+
return canvas.toDataURL();
1465+
}
1466+
});
1467+
14591468
it("destroys successfully while image is queued", async function () {
14601469
atlas = new TextureAtlas();
14611470

0 commit comments

Comments
 (0)