Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)
- Fixed image alignment in large billboard collections [#13042](https://github.com/CesiumGS/cesium/issues/13042)

#### Additions :tada:

Expand Down
25 changes: 8 additions & 17 deletions packages/engine/Source/Renderer/TextureAtlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,26 +364,17 @@ TextureAtlas.prototype._resize = function (context, queueOffset = 0) {
toPack.push(queue[i]);
}

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

if (!context.webgl2) {
width = CesiumMath.nextPowerOfTwo(width);
height = CesiumMath.nextPowerOfTwo(height);
}

// Determine by what factor the texture need to be scaled by at minimum
const areaDifference = areaQueued;
let scalingFactor = 1.0;
while (areaDifference / width / height >= 1.0) {
scalingFactor *= 2.0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we'll have ... er, other challenges ... if the texture atlas size exceeds 16K x 16K, in practice this loop can't spend more than 28 iterations for the largest supportable atlas size. No need to use a sliding scale factor (which may cause us to miss more optimal atlas sizes) to save a small number of iterations.


// Resize by one dimension
// Iteratively double the smallest dimension until atlas area is (approximately) sufficient.
while (areaQueued >= width * height) {
if (width > height) {
height *= scalingFactor;
height *= 2;
} else {
width *= scalingFactor;
width *= 2;
}
}

Expand Down
116 changes: 70 additions & 46 deletions packages/engine/Specs/Renderer/TextureAtlasSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -704,56 +704,16 @@ describe("Scene/TextureAtlas", function () {
expect(index2).toEqual(2);
expect(index3).toEqual(3);

// Webgl1 textures should only be powers of 2
const isWebGL2 = scene.frameState.context.webgl2;
const textureWidth = isWebGL2 ? 20 : 32;
const textureHeight = isWebGL2 ? 32 : 16;
const textureWidth = 32;
const textureHeight = 16;

const texture = atlas.texture;
expect(texture.pixelFormat).toEqual(PixelFormat.RGBA);
expect(texture.width).toEqual(textureWidth);
expect(texture.height).toEqual(textureHeight);

if (isWebGL2) {
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
`
....................
....................
....................
....................
....................
....................
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
2222222222..........
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
3333333333333333....
33333333333333330...
33333333333333330...
33333333333333330...
333333333333333301..
`.trim(),
);
} else {
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
`
expect(drawAtlas(atlas, [index0, index1, index2, index3])).toBe(
`
3333333333333333................
3333333333333333................
3333333333333333................
Expand All @@ -771,8 +731,7 @@ describe("Scene/TextureAtlas", function () {
333333333333333322222222220.....
3333333333333333222222222201....
`.trim(),
);
}
);

let textureCoordinates = atlas.computeTextureCoordinates(index0);
expect(
Expand Down Expand Up @@ -1456,6 +1415,71 @@ describe("Scene/TextureAtlas", function () {
expect(guid1).not.toEqual(guid2);
});

it("allocates appropriate space on resize", async function () {
const imageWidth = 128;
const imageHeight = 64;

await addImages(25);
let inputPixels = 25 * imageWidth * imageHeight;
let atlasWidth = atlas.texture.width;
let atlasHeight = atlas.texture.height;

// Allocate enough space, but not >>2x more. Aspect ratio should be 1:1, 1:2, or 2:1.
expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);

await addImages(75);
inputPixels = 75 * imageWidth * imageHeight;
atlasWidth = atlas.texture.width;
atlasHeight = atlas.texture.height;

expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);

await addImages(256);
inputPixels = 256 * imageWidth * imageHeight;
atlasWidth = atlas.texture.width;
atlasHeight = atlas.texture.height;

expect(atlasWidth * atlasHeight).toBeGreaterThan(inputPixels);
expect(atlasWidth * atlasHeight).toBeLessThanOrEqual(inputPixels * 3);
expect(atlasWidth / atlasHeight).toBeGreaterThanOrEqual(0.5);
expect(atlasWidth / atlasHeight).toBeLessThanOrEqual(2.0);

async function addImages(count) {
atlas = new TextureAtlas();

const imageUrl = createImageDataURL(imageWidth, imageHeight);

const promises = [];
for (let i = 0; i < count; i++) {
promises.push(atlas.addImage(i.toString(), imageUrl));
}

await pollWhilePromise(Promise.all(promises), () => {
atlas.update(scene.frameState.context);
});

return count * imageWidth * imageHeight;
}

function createImageDataURL(width, height) {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;

const ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(0, 0, width, height);

return canvas.toDataURL();
}
});

it("destroys successfully while image is queued", async function () {
atlas = new TextureAtlas();

Expand Down