diff --git a/CHANGES.md b/CHANGES.md index ddfc6d8b6fc..eecd5163c20 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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: diff --git a/packages/engine/Source/Renderer/TextureAtlas.js b/packages/engine/Source/Renderer/TextureAtlas.js index ba3575b57e9..f61822f826a 100644 --- a/packages/engine/Source/Renderer/TextureAtlas.js +++ b/packages/engine/Source/Renderer/TextureAtlas.js @@ -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; - - // 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; } } diff --git a/packages/engine/Source/Scene/BillboardCollection.js b/packages/engine/Source/Scene/BillboardCollection.js index 23d28da8bf9..a2ab50ea202 100644 --- a/packages/engine/Source/Scene/BillboardCollection.js +++ b/packages/engine/Source/Scene/BillboardCollection.js @@ -57,25 +57,7 @@ const SDF_INDEX = Billboard.SDF_INDEX; const SPLIT_DIRECTION_INDEX = Billboard.SPLIT_DIRECTION_INDEX; const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES; -let attributeLocations; - -const attributeLocationsBatched = { - positionHighAndScale: 0, - positionLowAndRotation: 1, - compressedAttribute0: 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates - compressedAttribute1: 3, // aligned axis, translucency by distance, image width - compressedAttribute2: 4, // image height, color, pick color, size in meters, valid aligned axis, 13 bits free - eyeOffset: 5, // 4 bytes free - scaleByDistance: 6, - pixelOffsetScaleByDistance: 7, - compressedAttribute3: 8, - textureCoordinateBoundsOrLabelTranslate: 9, - a_batchId: 10, - sdf: 11, - splitDirection: 12, -}; - -const attributeLocationsInstanced = { +const attributeLocations = { direction: 0, positionHighAndScale: 1, positionLowAndRotation: 2, // texture offset in w @@ -674,44 +656,7 @@ BillboardCollection.prototype.get = function (index) { return this._billboards[index]; }; -let getIndexBuffer; - -function getIndexBufferBatched(context) { - const sixteenK = 16 * 1024; - - let indexBuffer = context.cache.billboardCollection_indexBufferBatched; - if (defined(indexBuffer)) { - return indexBuffer; - } - - // Subtract 6 because the last index is reserverd for primitive restart. - // https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.18 - const length = sixteenK * 6 - 6; - const indices = new Uint16Array(length); - for (let i = 0, j = 0; i < length; i += 6, j += 4) { - indices[i] = j; - indices[i + 1] = j + 1; - indices[i + 2] = j + 2; - - indices[i + 3] = j + 0; - indices[i + 4] = j + 2; - indices[i + 5] = j + 3; - } - - // PERFORMANCE_IDEA: Should we reference count billboard collections, and eventually delete this? - // Is this too much memory to allocate up front? Should we dynamically grow it? - indexBuffer = Buffer.createIndexBuffer({ - context: context, - typedArray: indices, - usage: BufferUsage.STATIC_DRAW, - indexDatatype: IndexDatatype.UNSIGNED_SHORT, - }); - indexBuffer.vertexArrayDestroyable = false; - context.cache.billboardCollection_indexBufferBatched = indexBuffer; - return indexBuffer; -} - -function getIndexBufferInstanced(context) { +function getIndexBuffer(context) { let indexBuffer = context.cache.billboardCollection_indexBufferInstanced; if (defined(indexBuffer)) { return indexBuffer; @@ -761,14 +706,7 @@ BillboardCollection.prototype.computeNewBuffersUsage = function () { return usageChanged; }; -function createVAF( - context, - numberOfBillboards, - buffersUsage, - instanced, - batchTable, - sdf, -) { +function createVAF(context, numberOfBillboards, buffersUsage, batchTable, sdf) { const attributes = [ { index: attributeLocations.positionHighAndScale, @@ -839,14 +777,12 @@ function createVAF( ]; // Instancing requires one non-instanced attribute. - if (instanced) { - attributes.push({ - index: attributeLocations.direction, - componentsPerAttribute: 2, - componentDatatype: ComponentDatatype.FLOAT, - vertexBuffer: getVertexBufferInstanced(context), - }); - } + attributes.push({ + index: attributeLocations.direction, + componentsPerAttribute: 2, + componentDatatype: ComponentDatatype.FLOAT, + vertexBuffer: getVertexBufferInstanced(context), + }); if (defined(batchTable)) { attributes.push({ @@ -867,10 +803,8 @@ function createVAF( } // When instancing is enabled, only one vertex is needed for each billboard. - const sizeInVertices = instanced - ? numberOfBillboards - : 4 * numberOfBillboards; - return new VertexArrayFacade(context, attributes, sizeInVertices, instanced); + const sizeInVertices = numberOfBillboards; + return new VertexArrayFacade(context, attributes, sizeInVertices, true); } /////////////////////////////////////////////////////////////////////////// @@ -888,7 +822,6 @@ function writePositionScaleAndRotation( vafWriters, billboard, ) { - let i; const positionHighWriter = vafWriters[attributeLocations.positionHighAndScale]; const positionLowWriter = @@ -920,22 +853,9 @@ function writePositionScaleAndRotation( const high = writePositionScratch.high; const low = writePositionScratch.low; - if (billboardCollection._instanced) { - i = billboard._index; - positionHighWriter(i, high.x, high.y, high.z, scale); - positionLowWriter(i, low.x, low.y, low.z, rotation); - } else { - i = billboard._index * 4; - positionHighWriter(i + 0, high.x, high.y, high.z, scale); - positionHighWriter(i + 1, high.x, high.y, high.z, scale); - positionHighWriter(i + 2, high.x, high.y, high.z, scale); - positionHighWriter(i + 3, high.x, high.y, high.z, scale); - - positionLowWriter(i + 0, low.x, low.y, low.z, rotation); - positionLowWriter(i + 1, low.x, low.y, low.z, rotation); - positionLowWriter(i + 2, low.x, low.y, low.z, rotation); - positionLowWriter(i + 3, low.x, low.y, low.z, rotation); - } + const i = billboard._index; + positionHighWriter(i, high.x, high.y, high.z, scale); + positionLowWriter(i, low.x, low.y, low.z, rotation); } const scratchCartesian2 = new Cartesian2(); @@ -952,11 +872,6 @@ const LEFT_SHIFT2 = 4.0; const RIGHT_SHIFT8 = 1.0 / 256.0; -const LOWER_LEFT = 0.0; -const LOWER_RIGHT = 2.0; -const UPPER_RIGHT = 3.0; -const UPPER_LEFT = 1.0; - const scratchBoundingRectangle = new BoundingRectangle(); function writeCompressedAttrib0( @@ -965,7 +880,6 @@ function writeCompressedAttrib0( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.compressedAttribute0]; const pixelOffset = billboard.pixelOffset; const pixelOffsetX = pixelOffset.x; @@ -1005,8 +919,6 @@ function writeCompressedAttrib0( let bottomLeftX = 0; let bottomLeftY = 0; - let width = 0; - let height = 0; if (billboard.ready) { const imageRectangle = billboard.computeTextureCoordinates( scratchBoundingRectangle, @@ -1014,11 +926,7 @@ function writeCompressedAttrib0( bottomLeftX = imageRectangle.x; bottomLeftY = imageRectangle.y; - width = imageRectangle.width; - height = imageRectangle.height; } - const topRightX = bottomLeftX + width; - const topRightY = bottomLeftY + height; let compressed0 = Math.floor( @@ -1059,50 +967,9 @@ function writeCompressedAttrib0( scratchCartesian2.y = bottomLeftY; const compressedTexCoordsLL = AttributeCompression.compressTextureCoordinates(scratchCartesian2); - scratchCartesian2.x = topRightX; - const compressedTexCoordsLR = - AttributeCompression.compressTextureCoordinates(scratchCartesian2); - scratchCartesian2.y = topRightY; - const compressedTexCoordsUR = - AttributeCompression.compressTextureCoordinates(scratchCartesian2); - scratchCartesian2.x = bottomLeftX; - const compressedTexCoordsUL = - AttributeCompression.compressTextureCoordinates(scratchCartesian2); - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL); - } else { - i = billboard._index * 4; - writer( - i + 0, - compressed0 + LOWER_LEFT, - compressed1, - compressed2, - compressedTexCoordsLL, - ); - writer( - i + 1, - compressed0 + LOWER_RIGHT, - compressed1, - compressed2, - compressedTexCoordsLR, - ); - writer( - i + 2, - compressed0 + UPPER_RIGHT, - compressed1, - compressed2, - compressedTexCoordsUR, - ); - writer( - i + 3, - compressed0 + UPPER_LEFT, - compressed1, - compressed2, - compressedTexCoordsUL, - ); - } + const i = billboard._index; + writer(i, compressed0, compressed1, compressed2, compressedTexCoordsLL); } function writeCompressedAttrib1( @@ -1111,7 +978,6 @@ function writeCompressedAttrib1( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.compressedAttribute1]; const alignedAxis = billboard.alignedAxis; if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) { @@ -1161,16 +1027,8 @@ function writeCompressedAttrib1( farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0; compressed1 = compressed1 * LEFT_SHIFT8 + farValue; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, compressed0, compressed1, near, far); - } else { - i = billboard._index * 4; - writer(i + 0, compressed0, compressed1, near, far); - writer(i + 1, compressed0, compressed1, near, far); - writer(i + 2, compressed0, compressed1, near, far); - writer(i + 3, compressed0, compressed1, near, far); - } + const i = billboard._index; + writer(i, compressed0, compressed1, near, far); } function writeCompressedAttrib2( @@ -1179,7 +1037,6 @@ function writeCompressedAttrib2( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.compressedAttribute2]; const color = billboard.color; const pickColor = !defined(billboardCollection._batchTable) @@ -1219,16 +1076,8 @@ function writeCompressedAttrib2( Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8; compressed2 += sizeInMeters * 2.0 + validAlignedAxis; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, compressed0, compressed1, compressed2, compressed3); - } else { - i = billboard._index * 4; - writer(i + 0, compressed0, compressed1, compressed2, compressed3); - writer(i + 1, compressed0, compressed1, compressed2, compressed3); - writer(i + 2, compressed0, compressed1, compressed2, compressed3); - writer(i + 3, compressed0, compressed1, compressed2, compressed3); - } + const i = billboard._index; + writer(i, compressed0, compressed1, compressed2, compressed3); } function writeEyeOffset( @@ -1237,7 +1086,6 @@ function writeEyeOffset( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.eyeOffset]; const eyeOffset = billboard.eyeOffset; @@ -1253,31 +1101,23 @@ function writeEyeOffset( Math.abs(eyeOffsetZ), ); - if (billboardCollection._instanced) { - scratchCartesian2.x = 0; - scratchCartesian2.y = 0; + scratchCartesian2.x = 0; + scratchCartesian2.y = 0; - if (billboard.ready) { - const imageRectangle = billboard.computeTextureCoordinates( - scratchBoundingRectangle, - ); + if (billboard.ready) { + const imageRectangle = billboard.computeTextureCoordinates( + scratchBoundingRectangle, + ); - scratchCartesian2.x = imageRectangle.width; - scratchCartesian2.y = imageRectangle.height; - } + scratchCartesian2.x = imageRectangle.width; + scratchCartesian2.y = imageRectangle.height; + } - const compressedTexCoordsRange = - AttributeCompression.compressTextureCoordinates(scratchCartesian2); + const compressedTexCoordsRange = + AttributeCompression.compressTextureCoordinates(scratchCartesian2); - i = billboard._index; - writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange); - } else { - i = billboard._index * 4; - writer(i + 0, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0); - writer(i + 1, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0); - writer(i + 2, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0); - writer(i + 3, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0); - } + const i = billboard._index; + writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, compressedTexCoordsRange); } function writeScaleByDistance( @@ -1286,7 +1126,6 @@ function writeScaleByDistance( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.scaleByDistance]; let near = 0.0; let nearValue = 1.0; @@ -1307,16 +1146,8 @@ function writeScaleByDistance( } } - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, near, nearValue, far, farValue); - } else { - i = billboard._index * 4; - writer(i + 0, near, nearValue, far, farValue); - writer(i + 1, near, nearValue, far, farValue); - writer(i + 2, near, nearValue, far, farValue); - writer(i + 3, near, nearValue, far, farValue); - } + const i = billboard._index; + writer(i, near, nearValue, far, farValue); } function writePixelOffsetScaleByDistance( @@ -1325,7 +1156,6 @@ function writePixelOffsetScaleByDistance( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance]; let near = 0.0; let nearValue = 1.0; @@ -1346,16 +1176,8 @@ function writePixelOffsetScaleByDistance( } } - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, near, nearValue, far, farValue); - } else { - i = billboard._index * 4; - writer(i + 0, near, nearValue, far, farValue); - writer(i + 1, near, nearValue, far, farValue); - writer(i + 2, near, nearValue, far, farValue); - writer(i + 3, near, nearValue, far, farValue); - } + const i = billboard._index; + writer(i, near, nearValue, far, farValue); } function writeCompressedAttribute3( @@ -1364,7 +1186,6 @@ function writeCompressedAttribute3( vafWriters, billboard, ) { - let i; const writer = vafWriters[attributeLocations.compressedAttribute3]; let near = 0.0; let far = Number.MAX_VALUE; @@ -1408,16 +1229,8 @@ function writeCompressedAttribute3( const h = Math.floor(CesiumMath.clamp(imageHeight, 0.0, LEFT_SHIFT12)); const dimensions = w * LEFT_SHIFT12 + h; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, near, far, disableDepthTestDistance, dimensions); - } else { - i = billboard._index * 4; - writer(i + 0, near, far, disableDepthTestDistance, dimensions); - writer(i + 1, near, far, disableDepthTestDistance, dimensions); - writer(i + 2, near, far, disableDepthTestDistance, dimensions); - writer(i + 3, near, far, disableDepthTestDistance, dimensions); - } + const i = billboard._index; + writer(i, near, far, disableDepthTestDistance, dimensions); } function writeTextureCoordinateBoundsOrLabelTranslate( @@ -1449,16 +1262,9 @@ function writeTextureCoordinateBoundsOrLabelTranslate( translateX = billboard._labelTranslate.x; translateY = billboard._labelTranslate.y; } - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, translateX, translateY, 0.0, 0.0); - } else { - i = billboard._index * 4; - writer(i + 0, translateX, translateY, 0.0, 0.0); - writer(i + 1, translateX, translateY, 0.0, 0.0); - writer(i + 2, translateX, translateY, 0.0, 0.0); - writer(i + 3, translateX, translateY, 0.0, 0.0); - } + + i = billboard._index; + writer(i, translateX, translateY, 0.0, 0.0); return; } @@ -1480,16 +1286,8 @@ function writeTextureCoordinateBoundsOrLabelTranslate( const maxX = minX + width; const maxY = minY + height; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, minX, minY, maxX, maxY); - } else { - i = billboard._index * 4; - writer(i + 0, minX, minY, maxX, maxY); - writer(i + 1, minX, minY, maxX, maxY); - writer(i + 2, minX, minY, maxX, maxY); - writer(i + 3, minX, minY, maxX, maxY); - } + i = billboard._index; + writer(i, minX, minY, maxX, maxY); } function writeBatchId(billboardCollection, frameState, vafWriters, billboard) { @@ -1500,17 +1298,8 @@ function writeBatchId(billboardCollection, frameState, vafWriters, billboard) { const writer = vafWriters[attributeLocations.a_batchId]; const id = billboard._batchIndex; - let i; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, id); - } else { - i = billboard._index * 4; - writer(i + 0, id); - writer(i + 1, id); - writer(i + 2, id); - writer(i + 3, id); - } + const i = billboard._index; + writer(i, id); } function writeSDF(billboardCollection, frameState, vafWriters, billboard) { @@ -1518,7 +1307,6 @@ function writeSDF(billboardCollection, frameState, vafWriters, billboard) { return; } - let i; const writer = vafWriters[attributeLocations.sdf]; const outlineColor = billboard.outlineColor; @@ -1535,16 +1323,8 @@ function writeSDF(billboardCollection, frameState, vafWriters, billboard) { Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT16 + Color.floatToByte(outlineDistance) * LEFT_SHIFT8; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, compressed0, compressed1); - } else { - i = billboard._index * 4; - writer(i + 0, compressed0 + LOWER_LEFT, compressed1); - writer(i + 1, compressed0 + LOWER_RIGHT, compressed1); - writer(i + 2, compressed0 + UPPER_RIGHT, compressed1); - writer(i + 3, compressed0 + UPPER_LEFT, compressed1); - } + const i = billboard._index; + writer(i, compressed0, compressed1); } function writeSplitDirection( @@ -1561,17 +1341,8 @@ function writeSplitDirection( direction = split; } - let i; - if (billboardCollection._instanced) { - i = billboard._index; - writer(i, direction); - } else { - i = billboard._index * 4; - writer(i + 0, direction); - writer(i + 1, direction); - writer(i + 2, direction); - writer(i + 3, direction); - } + const i = billboard._index; + writer(i, direction); } function writeBillboard( @@ -1783,13 +1554,6 @@ BillboardCollection.prototype.update = function (frameState) { } const context = frameState.context; - this._instanced = context.instancedArrays; - attributeLocations = this._instanced - ? attributeLocationsInstanced - : attributeLocationsBatched; - getIndexBuffer = this._instanced - ? getIndexBufferInstanced - : getIndexBufferBatched; let billboards = this._billboards; let billboardsLength = billboards.length; @@ -1862,7 +1626,6 @@ BillboardCollection.prototype.update = function (frameState) { context, billboardsLength, this._buffersUsage, - this._instanced, this._batchTable, this._sdf, ); @@ -1902,9 +1665,7 @@ BillboardCollection.prototype.update = function (frameState) { properties[SHOW_INDEX] ) { writers.push(writeCompressedAttrib0); - if (this._instanced) { - writers.push(writeEyeOffset); - } + writers.push(writeEyeOffset); } if ( @@ -1981,11 +1742,7 @@ BillboardCollection.prototype.update = function (frameState) { writers[o](this, frameState, vafWriters, bb); } - if (this._instanced) { - this._vaf.subCommit(bb._index, 1); - } else { - this._vaf.subCommit(bb._index * 4, 4); - } + this._vaf.subCommit(bb._index, 1); } this._vaf.endSubCommits(); } @@ -2119,9 +1876,6 @@ BillboardCollection.prototype.update = function (frameState) { defines: vertDefines, sources: [vsSource], }); - if (this._instanced) { - vs.defines.push("INSTANCED"); - } if (this._shaderRotation) { vs.defines.push("ROTATION"); } @@ -2321,10 +2075,8 @@ BillboardCollection.prototype.update = function (frameState) { command.debugShowBoundingVolume = this.debugShowBoundingVolume; command.pickId = pickId; - if (this._instanced) { - command.count = 6; - command.instanceCount = billboardsLength; - } + command.count = 6; + command.instanceCount = billboardsLength; commandList.push(command); } diff --git a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl index c258b09bb10..c4c61c3bdd2 100644 --- a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl +++ b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl @@ -1,7 +1,5 @@ uniform float u_threePointDepthTestDistance; -#ifdef INSTANCED in vec2 direction; -#endif in vec4 positionHighAndScale; in vec4 positionLowAndRotation; in vec4 compressedAttribute0; // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset) @@ -146,17 +144,9 @@ void main() float show = floor(compressed * SHIFT_RIGHT2); compressed -= show * SHIFT_LEFT2; -#ifdef INSTANCED vec2 textureCoordinatesBottomLeft = czm_decompressTextureCoordinates(compressedAttribute0.w); vec2 textureCoordinatesRange = czm_decompressTextureCoordinates(eyeOffset.w); vec2 textureCoordinates = textureCoordinatesBottomLeft + direction * textureCoordinatesRange; -#else - vec2 direction; - direction.x = floor(compressed * SHIFT_RIGHT1); - direction.y = compressed - direction.x * SHIFT_LEFT1; - - vec2 textureCoordinates = czm_decompressTextureCoordinates(compressedAttribute0.w); -#endif float temp = compressedAttribute0.y * SHIFT_RIGHT8; pixelOffset.y = -(floor(temp) - UPPER_BOUND); @@ -328,7 +318,7 @@ void main() if (lengthSq < (u_threePointDepthTestDistance * u_threePointDepthTestDistance) && (enableDepthCheck == 1.0)) { float depthsilon = 10.0; vec2 depthOrigin; - // Horizontal origin for labels comes from a special attribute. If that value is 0, this is a billboard - use the regular origin. + // Horizontal origin for labels comes from a special attribute. If that value is 0, this is a billboard - use the regular origin. // Otherwise, transform the label origin to -1, 0, 1 (right, center, left). depthOrigin.x = floor(compressedAttribute2.w - (temp2 * SHIFT_LEFT2)); depthOrigin.x = czm_branchFreeTernary(depthOrigin.x == 0.0, origin.x, depthOrigin.x - 2.0); diff --git a/packages/engine/Specs/Renderer/TextureAtlasSpec.js b/packages/engine/Specs/Renderer/TextureAtlasSpec.js index a5026921a73..4faeeb02130 100644 --- a/packages/engine/Specs/Renderer/TextureAtlasSpec.js +++ b/packages/engine/Specs/Renderer/TextureAtlasSpec.js @@ -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................ @@ -771,8 +731,7 @@ describe("Scene/TextureAtlas", function () { 333333333333333322222222220..... 3333333333333333222222222201.... `.trim(), - ); - } + ); let textureCoordinates = atlas.computeTextureCoordinates(index0); expect( @@ -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();