Compare commits

..

3 Commits

Author SHA1 Message Date
Don McCurdy eecd3b43b1 additional fixes for billboard image coords
deploy / deploy (push) Has been cancelled Details
2025-11-25 16:45:43 -06:00
Don McCurdy 94e2932c09 add comment on bit packing 2025-11-25 11:10:32 -06:00
Don McCurdy 2f3a731840 fix(BillboardCollection): Fix precision loss in billboard image texcoords 2025-11-25 11:01:20 -06:00
5 changed files with 368 additions and 77 deletions

View File

@ -193,22 +193,21 @@ Object.defineProperties(TextureAtlas.prototype, {
}); });
/** /**
* Get the texture coordinates for reading the associated image in shaders. * TODO
* @param {number} index The index of the image region. * @param {number} index The index of the image region.
* @param {BoundingRectangle} [result] The object into which to store the result. * @param {BoundingRectangle} [result] The object into which to store the result.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided. * @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private * @private
* @example * @example
* const index = await atlas.addImage("myImage", image); * const index = await atlas.addImage("myImage", image);
* const rectangle = atlas.computeTextureCoordinates(index); * const rectangle = atlas.computeImageCoordinates(index);
* BoundingRectangle.pack(rectangle, bufferView); * BoundingRectangle.pack(rectangle, bufferView);
*/ */
TextureAtlas.prototype.computeTextureCoordinates = function (index, result) { TextureAtlas.prototype.computeImageCoordinates = function (index, result) {
//>>includeStart('debug', pragmas.debug); //>>includeStart('debug', pragmas.debug);
Check.typeOf.number.greaterThanOrEquals("index", index, 0); Check.typeOf.number.greaterThanOrEquals("index", index, 0);
//>>includeEnd('debug'); //>>includeEnd('debug');
const texture = this._texture;
const rectangle = this._rectangles[index]; const rectangle = this._rectangles[index];
if (!defined(result)) { if (!defined(result)) {
@ -224,11 +223,6 @@ TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
return result; return result;
} }
const atlasWidth = texture.width;
const atlasHeight = texture.height;
const width = rectangle.width;
const height = rectangle.height;
let x = rectangle.x; let x = rectangle.x;
let y = rectangle.y; let y = rectangle.y;
@ -240,10 +234,36 @@ TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
y += parentRectangle.y; y += parentRectangle.y;
} }
result.x = x / atlasWidth; result.x = x;
result.y = y / atlasHeight; result.y = y;
result.width = width / atlasWidth; result.width = rectangle.width;
result.height = height / atlasHeight; result.height = rectangle.height;
return result;
};
/**
* Get the texture coordinates for reading the associated image in shaders.
* @param {number} index The index of the image region.
* @param {BoundingRectangle} [result] The object into which to store the result.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
* @example
* const index = await atlas.addImage("myImage", image);
* const rectangle = atlas.computeTextureCoordinates(index);
* BoundingRectangle.pack(rectangle, bufferView);
*/
TextureAtlas.prototype.computeTextureCoordinates = function (index, result) {
result = this.computeImageCoordinates(index, result);
const texture = this._texture;
const atlasWidth = texture.width;
const atlasHeight = texture.height;
result.x /= atlasWidth;
result.y /= atlasHeight;
result.width /= atlasWidth;
result.height /= atlasHeight;
return result; return result;
}; };

View File

@ -1195,6 +1195,16 @@ Billboard._updateClamping = function (collection, owner) {
updateFunction(scratchCartographic); updateFunction(scratchCartographic);
}; };
/**
* Get the image coordinates for reading the loaded texture in shaders.
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @private
*/
Billboard.prototype.computeImageCoordinates = function (result) {
return this._imageTexture.computeImageCoordinates(result);
};
/** /**
* Get the texture coordinates for reading the loaded texture in shaders. * Get the texture coordinates for reading the loaded texture in shaders.
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided. * @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.

View File

@ -59,11 +59,29 @@ const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES;
const scratchTextureSize = new Cartesian2(); const scratchTextureSize = new Cartesian2();
const attributeLocations = { let attributeLocations;
const attributeLocationsBatched = {
positionHighAndScale: 0,
positionLowAndRotation: 1,
compressedAttribute0: 2, // pixel offset, translate, horizontal origin, vertical origin, show, direction, image coordinates (px)
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 = {
direction: 0, direction: 0,
positionHighAndScale: 1, positionHighAndScale: 1,
positionLowAndRotation: 2, // texture offset in w positionLowAndRotation: 2,
compressedAttribute0: 3, compressedAttribute0: 3, // image lower-left coordinates (px) in w
compressedAttribute1: 4, compressedAttribute1: 4,
compressedAttribute2: 5, compressedAttribute2: 5,
eyeOffset: 6, eyeOffset: 6,
@ -662,7 +680,44 @@ BillboardCollection.prototype.get = function (index) {
return this._billboards[index]; return this._billboards[index];
}; };
function getIndexBuffer(context) { 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) {
let indexBuffer = context.cache.billboardCollection_indexBufferInstanced; let indexBuffer = context.cache.billboardCollection_indexBufferInstanced;
if (defined(indexBuffer)) { if (defined(indexBuffer)) {
return indexBuffer; return indexBuffer;
@ -712,7 +767,14 @@ BillboardCollection.prototype.computeNewBuffersUsage = function () {
return usageChanged; return usageChanged;
}; };
function createVAF(context, numberOfBillboards, buffersUsage, batchTable, sdf) { function createVAF(
context,
numberOfBillboards,
buffersUsage,
instanced,
batchTable,
sdf,
) {
const attributes = [ const attributes = [
{ {
index: attributeLocations.positionHighAndScale, index: attributeLocations.positionHighAndScale,
@ -783,12 +845,14 @@ function createVAF(context, numberOfBillboards, buffersUsage, batchTable, sdf) {
]; ];
// Instancing requires one non-instanced attribute. // Instancing requires one non-instanced attribute.
attributes.push({ if (instanced) {
index: attributeLocations.direction, attributes.push({
componentsPerAttribute: 2, index: attributeLocations.direction,
componentDatatype: ComponentDatatype.FLOAT, componentsPerAttribute: 2,
vertexBuffer: getVertexBufferInstanced(context), componentDatatype: ComponentDatatype.FLOAT,
}); vertexBuffer: getVertexBufferInstanced(context),
});
}
if (defined(batchTable)) { if (defined(batchTable)) {
attributes.push({ attributes.push({
@ -809,8 +873,10 @@ function createVAF(context, numberOfBillboards, buffersUsage, batchTable, sdf) {
} }
// When instancing is enabled, only one vertex is needed for each billboard. // When instancing is enabled, only one vertex is needed for each billboard.
const sizeInVertices = numberOfBillboards; const sizeInVertices = instanced
return new VertexArrayFacade(context, attributes, sizeInVertices, true); ? numberOfBillboards
: 4 * numberOfBillboards;
return new VertexArrayFacade(context, attributes, sizeInVertices, instanced);
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -828,6 +894,7 @@ function writePositionScaleAndRotation(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const positionHighWriter = const positionHighWriter =
vafWriters[attributeLocations.positionHighAndScale]; vafWriters[attributeLocations.positionHighAndScale];
const positionLowWriter = const positionLowWriter =
@ -859,9 +926,22 @@ function writePositionScaleAndRotation(
const high = writePositionScratch.high; const high = writePositionScratch.high;
const low = writePositionScratch.low; const low = writePositionScratch.low;
const i = billboard._index; if (billboardCollection._instanced) {
positionHighWriter(i, high.x, high.y, high.z, scale); i = billboard._index;
positionLowWriter(i, low.x, low.y, low.z, rotation); 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 UPPER_BOUND = 32768.0; // 2^15 const UPPER_BOUND = 32768.0; // 2^15
@ -876,6 +956,11 @@ const LEFT_SHIFT2 = 4.0;
const RIGHT_SHIFT8 = 1.0 / 256.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(); const scratchBoundingRectangle = new BoundingRectangle();
function writeCompressedAttrib0( function writeCompressedAttrib0(
@ -884,6 +969,7 @@ function writeCompressedAttrib0(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.compressedAttribute0]; const writer = vafWriters[attributeLocations.compressedAttribute0];
const pixelOffset = billboard.pixelOffset; const pixelOffset = billboard.pixelOffset;
const pixelOffsetX = pixelOffset.x; const pixelOffsetX = pixelOffset.x;
@ -921,17 +1007,17 @@ function writeCompressedAttrib0(
billboardCollection._allVerticalCenter && billboardCollection._allVerticalCenter &&
verticalOrigin === VerticalOrigin.CENTER; verticalOrigin === VerticalOrigin.CENTER;
// Compute offset (in pixels) from lower-left of the texture atlas. // Compute image coordinates and size, in pixels. Coordinates are from lower-left of texture atlas.Z
let imageOffsetX = 0; let imageX = 0;
let imageOffsetY = 0; let imageY = 0;
let imageWidth = 0;
let imageHeight = 0;
if (billboard.ready) { if (billboard.ready) {
const imageRectangle = billboard.computeTextureCoordinates( billboard.computeImageCoordinates(scratchBoundingRectangle);
scratchBoundingRectangle, imageX = scratchBoundingRectangle.x;
); imageY = scratchBoundingRectangle.y;
const { width: atlasWidth, height: atlasHeight } = imageWidth = scratchBoundingRectangle.width;
billboardCollection.textureAtlas.texture; imageHeight = scratchBoundingRectangle.height;
imageOffsetX = imageRectangle.x * atlasWidth;
imageOffsetY = imageRectangle.y * atlasHeight;
} }
let compressed0 = let compressed0 =
@ -969,10 +1055,48 @@ function writeCompressedAttrib0(
compressed1 += upperTranslateY; compressed1 += upperTranslateY;
compressed2 += lowerTranslateY; compressed2 += lowerTranslateY;
const compressedImageOffsetLL = imageOffsetX * LEFT_SHIFT16 + imageOffsetY; // Compress image coordinates (px), integers 0-2^12 from lower-left of atlas. Avoid
// `AttributeCompression.compressTextureCoordinates` for lossless pixel values.
const compressedImageLL = imageX * LEFT_SHIFT12 + imageY;
const compressedImageLR = (imageX + imageWidth) * LEFT_SHIFT12 + imageY;
const compressedImageUR =
(imageX + imageWidth) * LEFT_SHIFT12 + imageY + imageHeight;
const compressedImageUL = imageX * LEFT_SHIFT12 + imageY + imageHeight;
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, compressed0, compressed1, compressed2, compressedImageOffsetLL); i = billboard._index;
writer(i, compressed0, compressed1, compressed2, compressedImageLL);
} else {
i = billboard._index * 4;
writer(
i + 0,
compressed0 + LOWER_LEFT,
compressed1,
compressed2,
compressedImageLL,
);
writer(
i + 1,
compressed0 + LOWER_RIGHT,
compressed1,
compressed2,
compressedImageLR,
);
writer(
i + 2,
compressed0 + UPPER_RIGHT,
compressed1,
compressed2,
compressedImageUR,
);
writer(
i + 3,
compressed0 + UPPER_LEFT,
compressed1,
compressed2,
compressedImageUL,
);
}
} }
function writeCompressedAttrib1( function writeCompressedAttrib1(
@ -981,6 +1105,7 @@ function writeCompressedAttrib1(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.compressedAttribute1]; const writer = vafWriters[attributeLocations.compressedAttribute1];
const alignedAxis = billboard.alignedAxis; const alignedAxis = billboard.alignedAxis;
if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) { if (!Cartesian3.equals(alignedAxis, Cartesian3.ZERO)) {
@ -1030,8 +1155,16 @@ function writeCompressedAttrib1(
farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0; farValue = farValue === 1.0 ? 255.0 : (farValue * 255.0) | 0;
compressed1 = compressed1 * LEFT_SHIFT8 + farValue; compressed1 = compressed1 * LEFT_SHIFT8 + farValue;
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, compressed0, compressed1, near, far); 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);
}
} }
function writeCompressedAttrib2( function writeCompressedAttrib2(
@ -1040,6 +1173,7 @@ function writeCompressedAttrib2(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.compressedAttribute2]; const writer = vafWriters[attributeLocations.compressedAttribute2];
const color = billboard.color; const color = billboard.color;
const pickColor = !defined(billboardCollection._batchTable) const pickColor = !defined(billboardCollection._batchTable)
@ -1079,8 +1213,16 @@ function writeCompressedAttrib2(
Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8; Color.floatToByte(pickColor.alpha) * LEFT_SHIFT8;
compressed2 += sizeInMeters * 2.0 + validAlignedAxis; compressed2 += sizeInMeters * 2.0 + validAlignedAxis;
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, compressed0, compressed1, compressed2, compressed3); 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);
}
} }
function writeEyeOffset( function writeEyeOffset(
@ -1089,6 +1231,7 @@ function writeEyeOffset(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.eyeOffset]; const writer = vafWriters[attributeLocations.eyeOffset];
const eyeOffset = billboard.eyeOffset; const eyeOffset = billboard.eyeOffset;
@ -1104,8 +1247,16 @@ function writeEyeOffset(
Math.abs(eyeOffsetZ), Math.abs(eyeOffsetZ),
); );
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0); i = billboard._index;
writer(i, eyeOffset.x, eyeOffset.y, eyeOffsetZ, 0.0);
} 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);
}
} }
function writeScaleByDistance( function writeScaleByDistance(
@ -1114,6 +1265,7 @@ function writeScaleByDistance(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.scaleByDistance]; const writer = vafWriters[attributeLocations.scaleByDistance];
let near = 0.0; let near = 0.0;
let nearValue = 1.0; let nearValue = 1.0;
@ -1134,8 +1286,16 @@ function writeScaleByDistance(
} }
} }
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, near, nearValue, far, farValue); 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);
}
} }
function writePixelOffsetScaleByDistance( function writePixelOffsetScaleByDistance(
@ -1144,6 +1304,7 @@ function writePixelOffsetScaleByDistance(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance]; const writer = vafWriters[attributeLocations.pixelOffsetScaleByDistance];
let near = 0.0; let near = 0.0;
let nearValue = 1.0; let nearValue = 1.0;
@ -1164,8 +1325,16 @@ function writePixelOffsetScaleByDistance(
} }
} }
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, near, nearValue, far, farValue); 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);
}
} }
function writeCompressedAttribute3( function writeCompressedAttribute3(
@ -1174,6 +1343,7 @@ function writeCompressedAttribute3(
vafWriters, vafWriters,
billboard, billboard,
) { ) {
let i;
const writer = vafWriters[attributeLocations.compressedAttribute3]; const writer = vafWriters[attributeLocations.compressedAttribute3];
let near = 0.0; let near = 0.0;
let far = Number.MAX_VALUE; let far = Number.MAX_VALUE;
@ -1217,8 +1387,16 @@ function writeCompressedAttribute3(
const h = Math.floor(CesiumMath.clamp(imageHeight, 0.0, LEFT_SHIFT12)); const h = Math.floor(CesiumMath.clamp(imageHeight, 0.0, LEFT_SHIFT12));
const dimensions = w * LEFT_SHIFT12 + h; const dimensions = w * LEFT_SHIFT12 + h;
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, near, far, disableDepthTestDistance, dimensions); 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);
}
} }
function writeTextureCoordinateBoundsOrLabelTranslate( function writeTextureCoordinateBoundsOrLabelTranslate(
@ -1250,9 +1428,16 @@ function writeTextureCoordinateBoundsOrLabelTranslate(
translateX = billboard._labelTranslate.x; translateX = billboard._labelTranslate.x;
translateY = billboard._labelTranslate.y; translateY = billboard._labelTranslate.y;
} }
if (billboardCollection._instanced) {
i = billboard._index; i = billboard._index;
writer(i, translateX, translateY, 0.0, 0.0); 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);
}
return; return;
} }
@ -1274,8 +1459,16 @@ function writeTextureCoordinateBoundsOrLabelTranslate(
const maxX = minX + width; const maxX = minX + width;
const maxY = minY + height; const maxY = minY + height;
i = billboard._index; if (billboardCollection._instanced) {
writer(i, minX, minY, maxX, maxY); 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);
}
} }
function writeBatchId(billboardCollection, frameState, vafWriters, billboard) { function writeBatchId(billboardCollection, frameState, vafWriters, billboard) {
@ -1286,8 +1479,17 @@ function writeBatchId(billboardCollection, frameState, vafWriters, billboard) {
const writer = vafWriters[attributeLocations.a_batchId]; const writer = vafWriters[attributeLocations.a_batchId];
const id = billboard._batchIndex; const id = billboard._batchIndex;
const i = billboard._index; let i;
writer(i, id); 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);
}
} }
function writeSDF(billboardCollection, frameState, vafWriters, billboard) { function writeSDF(billboardCollection, frameState, vafWriters, billboard) {
@ -1295,6 +1497,7 @@ function writeSDF(billboardCollection, frameState, vafWriters, billboard) {
return; return;
} }
let i;
const writer = vafWriters[attributeLocations.sdf]; const writer = vafWriters[attributeLocations.sdf];
const outlineColor = billboard.outlineColor; const outlineColor = billboard.outlineColor;
@ -1311,8 +1514,16 @@ function writeSDF(billboardCollection, frameState, vafWriters, billboard) {
Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT16 + Color.floatToByte(outlineColor.alpha) * LEFT_SHIFT16 +
Color.floatToByte(outlineDistance) * LEFT_SHIFT8; Color.floatToByte(outlineDistance) * LEFT_SHIFT8;
const i = billboard._index; if (billboardCollection._instanced) {
writer(i, compressed0, compressed1); 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);
}
} }
function writeSplitDirection( function writeSplitDirection(
@ -1329,8 +1540,17 @@ function writeSplitDirection(
direction = split; direction = split;
} }
const i = billboard._index; let i;
writer(i, direction); 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);
}
} }
function writeBillboard( function writeBillboard(
@ -1542,6 +1762,13 @@ BillboardCollection.prototype.update = function (frameState) {
} }
const context = frameState.context; const context = frameState.context;
this._instanced = context.instancedArrays;
attributeLocations = this._instanced
? attributeLocationsInstanced
: attributeLocationsBatched;
getIndexBuffer = this._instanced
? getIndexBufferInstanced
: getIndexBufferBatched;
let billboards = this._billboards; let billboards = this._billboards;
let billboardsLength = billboards.length; let billboardsLength = billboards.length;
@ -1614,6 +1841,7 @@ BillboardCollection.prototype.update = function (frameState) {
context, context,
billboardsLength, billboardsLength,
this._buffersUsage, this._buffersUsage,
this._instanced,
this._batchTable, this._batchTable,
this._sdf, this._sdf,
); );
@ -1653,7 +1881,9 @@ BillboardCollection.prototype.update = function (frameState) {
properties[SHOW_INDEX] properties[SHOW_INDEX]
) { ) {
writers.push(writeCompressedAttrib0); writers.push(writeCompressedAttrib0);
writers.push(writeEyeOffset); if (this._instanced) {
writers.push(writeEyeOffset);
}
} }
if ( if (
@ -1730,7 +1960,11 @@ BillboardCollection.prototype.update = function (frameState) {
writers[o](this, frameState, vafWriters, bb); writers[o](this, frameState, vafWriters, bb);
} }
this._vaf.subCommit(bb._index, 1); if (this._instanced) {
this._vaf.subCommit(bb._index, 1);
} else {
this._vaf.subCommit(bb._index * 4, 4);
}
} }
this._vaf.endSubCommits(); this._vaf.endSubCommits();
} }
@ -1864,6 +2098,9 @@ BillboardCollection.prototype.update = function (frameState) {
defines: vertDefines, defines: vertDefines,
sources: [vsSource], sources: [vsSource],
}); });
if (this._instanced) {
vs.defines.push("INSTANCED");
}
if (this._shaderRotation) { if (this._shaderRotation) {
vs.defines.push("ROTATION"); vs.defines.push("ROTATION");
} }
@ -2063,8 +2300,10 @@ BillboardCollection.prototype.update = function (frameState) {
command.debugShowBoundingVolume = this.debugShowBoundingVolume; command.debugShowBoundingVolume = this.debugShowBoundingVolume;
command.pickId = pickId; command.pickId = pickId;
command.count = 6; if (this._instanced) {
command.instanceCount = billboardsLength; command.count = 6;
command.instanceCount = billboardsLength;
}
commandList.push(command); commandList.push(command);
} }

View File

@ -334,6 +334,17 @@ BillboardTexture.prototype.setImageSubRegion = function (index, subRegion) {
this.dirty = true; this.dirty = true;
}; };
/**
* Get the image coordinates for reading the loaded texture in shaders.
* @private
* @param {BoundingRectangle} [result] The modified result parameter or a new BoundingRectangle instance if one was not provided.
* @return {BoundingRectangle} The modified result parameter or a new BoundingRectangle instance if one was not provided.
*/
BillboardTexture.prototype.computeImageCoordinates = function (result) {
const atlas = this._billboardCollection.textureAtlas;
return atlas.computeImageCoordinates(this._index, result);
};
/** /**
* Get the texture coordinates for reading the loaded texture in shaders. * Get the texture coordinates for reading the loaded texture in shaders.
* @private * @private

View File

@ -1,6 +1,8 @@
uniform vec2 u_atlasSize; uniform vec2 u_atlasSize;
uniform float u_threePointDepthTestDistance; uniform float u_threePointDepthTestDistance;
#ifdef INSTANCED
in vec2 direction; in vec2 direction;
#endif
in vec4 positionHighAndScale; in vec4 positionHighAndScale;
in vec4 positionLowAndRotation; in vec4 positionLowAndRotation;
in vec4 compressedAttribute0; // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset) in vec4 compressedAttribute0; // pixel offset, translate, horizontal origin, vertical origin, show, direction, texture coordinates (texture offset)
@ -146,6 +148,12 @@ void main()
float show = floor(compressed * SHIFT_RIGHT2); float show = floor(compressed * SHIFT_RIGHT2);
compressed -= show * SHIFT_LEFT2; compressed -= show * SHIFT_LEFT2;
#ifndef INSTANCED
vec2 direction;
direction.x = floor(compressed * SHIFT_RIGHT1);
direction.y = compressed - direction.x * SHIFT_LEFT1;
#endif
float temp = compressedAttribute0.y * SHIFT_RIGHT8; float temp = compressedAttribute0.y * SHIFT_RIGHT8;
pixelOffset.y = -(floor(temp) - UPPER_BOUND); pixelOffset.y = -(floor(temp) - UPPER_BOUND);
@ -160,16 +168,19 @@ void main()
translate.y -= UPPER_BOUND; translate.y -= UPPER_BOUND;
translate.y *= SHIFT_RIGHT2; translate.y *= SHIFT_RIGHT2;
temp = compressedAttribute1.x * SHIFT_RIGHT8; // TODO(donmccurdy): This is billboard size (px) on screen, not image size (px) in atlas?
float temp2 = floor(compressedAttribute2.w * SHIFT_RIGHT2); float imageWidth = floor(compressedAttribute1.x * SHIFT_RIGHT8);
float imageHeight = floor(compressedAttribute2.w * SHIFT_RIGHT2);
vec2 imageSize = vec2(imageWidth, imageHeight);
vec2 imageSize = vec2(floor(temp), temp2); float imageOffsetX = floor(compressedAttribute0.w * SHIFT_RIGHT12);
float imageOffsetY = compressedAttribute0.w - (imageOffsetX * SHIFT_LEFT12);
vec2 textureCoordinates = vec2(imageOffsetX + 0.5, imageOffsetY + 0.5) / u_atlasSize.xy;
float imageOffsetX = floor(compressedAttribute0.w * SHIFT_RIGHT16); #ifdef INSTANCED
float imageOffsetY = compressedAttribute0.w - (imageOffsetX * SHIFT_LEFT16); vec2 textureCoordinatesRange = imageSize.xy / u_atlasSize.xy; // TODO(donmccurdy): Needs -1.0 offset?
vec2 textureCoordinatesBottomLeft = vec2(imageOffsetX, imageOffsetY) / u_atlasSize.xy; textureCoordinates += direction * textureCoordinatesRange;
vec2 textureCoordinatesRange = imageSize.xy / u_atlasSize.xy; #endif
vec2 textureCoordinates = textureCoordinatesBottomLeft + direction * textureCoordinatesRange;
#ifdef FS_THREE_POINT_DEPTH_CHECK #ifdef FS_THREE_POINT_DEPTH_CHECK
float labelHorizontalOrigin = floor(compressedAttribute2.w - (temp2 * SHIFT_LEFT2)); float labelHorizontalOrigin = floor(compressedAttribute2.w - (temp2 * SHIFT_LEFT2));