From dacc45254559237ed68945c69a3b66a423dadc28 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 30 Nov 2016 15:21:12 -0500 Subject: [PATCH 1/3] Add falling back to packing floats into unsigned byte texture when floating point textures aren't supported. --- Source/Scene/BatchTable.js | 254 +++++++++++++++++++++++--- Source/Scene/PolylineCollection.js | 36 ++-- Source/Scene/Primitive.js | 2 +- Specs/Scene/BatchTableSpec.js | 138 ++++++++++---- Specs/Scene/PolylineCollectionSpec.js | 4 - 5 files changed, 346 insertions(+), 88 deletions(-) diff --git a/Source/Scene/BatchTable.js b/Source/Scene/BatchTable.js index db4f835a36f8..2014e9cc1af0 100644 --- a/Source/Scene/BatchTable.js +++ b/Source/Scene/BatchTable.js @@ -44,6 +44,7 @@ define([ * @constructor * @private * + * @param {Context} context The context in which the batch table is created. * @param {Object[]} attributes An array of objects describing a per instance attribute. Each object contains a datatype, components per attributes, whether it is normalized and a function name * to retrieve the value in the vertex shader. * @param {Number} numberOfInstances The number of instances in a batch table. @@ -60,7 +61,7 @@ define([ * componentsPerAttribute : 4, * normalize : true * }]; - * var batchTable = new BatchTable(attributes, 5); + * var batchTable = new BatchTable(context, attributes, 5); * * // when creating the draw commands, update the uniform map and the vertex shader * vertexShaderSource = batchTable.getVertexShaderCallback()(vertexShaderSource); @@ -83,8 +84,11 @@ define([ * // ... * } */ - function BatchTable(attributes, numberOfInstances) { + function BatchTable(context, attributes, numberOfInstances) { //>>includeStart('debug', pragmas.debug); + if (!defined(context)) { + throw new DeveloperError('context is required'); + } if (!defined(attributes)) { throw new DeveloperError('attributes is required'); } @@ -96,13 +100,20 @@ define([ this._attributes = attributes; this._numberOfInstances = numberOfInstances; + if (attributes.length === 0) { + return; + } + var pixelDatatype = getDatatype(attributes); + var textureFloatSupported = context.floatingPointTexture; + var packFloats = pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported; + var offsets = createOffsets(attributes, packFloats); - var numberOfAttributes = attributes.length; - var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / numberOfAttributes); + var stride = getStride(offsets, attributes, packFloats); + var maxNumberOfInstancesPerRow = Math.floor(ContextLimits.maximumTextureSize / stride); var instancesPerWidth = Math.min(numberOfInstances, maxNumberOfInstancesPerRow); - var width = numberOfAttributes * instancesPerWidth; + var width = stride * instancesPerWidth; var height = Math.ceil(numberOfInstances / instancesPerWidth); var stepX = 1.0 / width; @@ -112,11 +123,14 @@ define([ this._textureDimensions = new Cartesian2(width, height); this._textureStep = new Cartesian4(stepX, centerX, stepY, centerY); - this._pixelDatatype = pixelDatatype; + this._pixelDatatype = !packFloats ? pixelDatatype : PixelDatatype.UNSIGNED_BYTE; + this._packFloats = packFloats; + this._offsets = offsets; + this._stride = stride; this._texture = undefined; - var batchLength = width * height * 4; - this._batchValues = pixelDatatype === PixelDatatype.FLOAT ? new Float32Array(batchLength) : new Uint8Array(batchLength); + var batchLength = 4 * width * height; + this._batchValues = pixelDatatype === PixelDatatype.FLOAT && !packFloats ? new Float32Array(batchLength) : new Uint8Array(batchLength); this._batchValuesDirty = false; } @@ -169,6 +183,134 @@ define([ return Number; } + function createOffsets(attributes, packFloats) { + var offsets = new Array(attributes.length); + + var currentOffset = 0; + var attributesLength = attributes.length; + for (var i = 0; i < attributesLength; ++i) { + var attribute = attributes[i]; + var componentDatatype = attribute.componentDatatype; + + offsets[i] = currentOffset; + + if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) { + currentOffset += 4; + } else { + ++currentOffset; + } + } + + return offsets; + } + + function getStride(offsets, attributes, packFloats) { + var length = offsets.length; + var lastOffset = offsets[length - 1]; + var lastAttribute = attributes[length - 1]; + var componentDatatype = lastAttribute.componentDatatype; + + if (componentDatatype !== ComponentDatatype.UNSIGNED_BYTE && packFloats) { + return lastOffset + 4; + } + return lastOffset + 1; + } + + var scratchPackedFloatCartesian4 = new Cartesian4(); + + var SHIFT_LEFT_8 = 256.0; + var SHIFT_LEFT_16 = 65536.0; + var SHIFT_LEFT_24 = 16777216.0; + + var SHIFT_RIGHT_8 = 1.0 / SHIFT_LEFT_8; + var SHIFT_RIGHT_16 = 1.0 / SHIFT_LEFT_16; + var SHIFT_RIGHT_24 = 1.0 / SHIFT_LEFT_24; + + var BIAS = 38.0; + + function unpackFloat(value) { + var temp = value.w / 2.0; + var exponent = Math.floor(temp); + var sign = (temp - exponent) * 2.0; + exponent = exponent - BIAS; + + sign = sign * 2.0 - 1.0; + sign = -sign; + + if (exponent >= BIAS) { + return sign < 0.0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY; + } + + var unpacked = sign * value.x * SHIFT_RIGHT_8; + unpacked += sign * value.y * SHIFT_RIGHT_16; + unpacked += sign * value.z * SHIFT_RIGHT_24; + + return unpacked * Math.pow(10.0, exponent); + } + + function getPackedFloat(array, index, result) { + var packed = Cartesian4.unpack(array, index, scratchPackedFloatCartesian4); + var x = unpackFloat(packed); + + packed = Cartesian4.unpack(array, index + 4, scratchPackedFloatCartesian4); + var y = unpackFloat(packed); + + packed = Cartesian4.unpack(array, index + 8, scratchPackedFloatCartesian4); + var z = unpackFloat(packed); + + packed = Cartesian4.unpack(array, index + 12, scratchPackedFloatCartesian4); + var w = unpackFloat(packed); + + return Cartesian4.fromElements(x, y, z, w, result); + } + + var scratchFloatArray = new Float32Array(1); + + function packFloat(value, result) { + scratchFloatArray[0] = value; + value = scratchFloatArray[0]; + + if (value === 0.0) { + return Cartesian4.clone(Cartesian4.ZERO, result); + } + + var sign = value < 0.0 ? 1.0 : 0.0; + var exponent; + + if (!isFinite(value)) { + value = 0.1; + exponent = BIAS; + } else { + value = Math.abs(value); + exponent = Math.floor(Math.log10(value)) + 1.0; + value = value / Math.pow(10.0, exponent); + } + + var temp = value * SHIFT_LEFT_8; + result.x = Math.floor(temp); + temp = (temp - result.x) * SHIFT_LEFT_8; + result.y = Math.floor(temp); + temp = (temp - result.y) * SHIFT_LEFT_8; + result.z = Math.floor(temp); + result.w = (exponent + BIAS) * 2.0 + sign; + + return result; + } + + function setPackedAttribute(value, array, index) { + var packed = packFloat(value.x, scratchPackedFloatCartesian4); + Cartesian4.pack(packed, array, index); + + packed = packFloat(value.y, packed); + Cartesian4.pack(packed, array, index + 4); + + packed = packFloat(value.z, packed); + Cartesian4.pack(packed, array, index + 8); + + packed = packFloat(value.w, packed); + Cartesian4.pack(packed, array, index + 12); + } + var scratchGetAttributeCartesian4 = new Cartesian4(); /** @@ -193,8 +335,17 @@ define([ //>>includeEnd('debug'); var attributes = this._attributes; - var index = 4 * attributes.length * instanceIndex + 4 * attributeIndex; - var value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4); + var offset = this._offsets[attributeIndex]; + var stride = this._stride; + + var index = 4 * stride * instanceIndex + 4 * offset; + var value; + + if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) { + value = getPackedFloat(this._batchValues, index, scratchGetAttributeCartesian4); + } else { + value = Cartesian4.unpack(this._batchValues, index, scratchGetAttributeCartesian4); + } var attributeType = getAttributeType(attributes, attributeIndex); if (defined(attributeType.fromCartesian4)) { @@ -247,8 +398,15 @@ define([ attributeValue.z = defined(value.z) ? value.z : 0.0; attributeValue.w = defined(value.w) ? value.w : 0.0; - var index = 4 * attributes.length * instanceIndex + 4 * attributeIndex; - Cartesian4.pack(attributeValue, this._batchValues, index); + var offset = this._offsets[attributeIndex]; + var stride = this._stride; + var index = 4 * stride * instanceIndex + 4 * offset; + + if (this._packFloats && attributes[attributeIndex].componentDatatype !== PixelDatatype.UNSIGNED_BYTE) { + setPackedAttribute(attributeValue, this._batchValues, index); + } else { + Cartesian4.pack(attributeValue, this._batchValues, index); + } this._batchValuesDirty = true; }; @@ -284,20 +442,14 @@ define([ * @exception {RuntimeError} The floating point texture extension is required but not supported. */ BatchTable.prototype.update = function(frameState) { - var context = frameState.context; - if (this._pixelDatatype === PixelDatatype.FLOAT && !context.floatingPointTexture) { - // We could probably pack the floats to RGBA unsigned bytes but that would add a lot CPU and memory overhead. - throw new RuntimeError('The floating point texture extension is required but not supported.'); - } - - if (defined(this._texture) && !this._batchValuesDirty) { + if ((defined(this._texture) && !this._batchValuesDirty) || this._attributes.length === 0) { return; } this._batchValuesDirty = false; if (!defined(this._texture)) { - createTexture(this, context); + createTexture(this, frameState.context); } updateTexture(this); }; @@ -310,6 +462,10 @@ define([ BatchTable.prototype.getUniformMapCallback = function() { var that = this; return function(uniformMap) { + if (that._attributes.length === 0) { + return uniformMap; + } + var batchUniformMap = { batchTexture : function() { return that._texture; @@ -321,13 +477,12 @@ define([ return that._textureStep; } }; - return combine(uniformMap, batchUniformMap); }; }; function getGlslComputeSt(batchTable) { - var numberOfAttributes = batchTable._attributes.length; + var stride = batchTable._stride; // GLSL batchId is zero-based: [0, numberOfInstances - 1] if (batchTable._textureDimensions.y === 1) { @@ -336,7 +491,7 @@ define([ '{ \n' + ' float stepX = batchTextureStep.x; \n' + ' float centerX = batchTextureStep.y; \n' + - ' float numberOfAttributes = float('+ numberOfAttributes + '); \n' + + ' float numberOfAttributes = float('+ stride + '); \n' + ' return vec2(centerX + (batchId * numberOfAttributes * stepX), 0.5); \n' + '} \n'; } @@ -349,13 +504,34 @@ define([ ' float centerX = batchTextureStep.y; \n' + ' float stepY = batchTextureStep.z; \n' + ' float centerY = batchTextureStep.w; \n' + - ' float numberOfAttributes = float('+ numberOfAttributes + '); \n' + + ' float numberOfAttributes = float('+ stride + '); \n' + ' float xId = mod(batchId * numberOfAttributes, batchTextureDimensions.x); \n' + ' float yId = floor(batchId * numberOfAttributes / batchTextureDimensions.x); \n' + ' return vec2(centerX + (xId * stepX), 1.0 - (centerY + (yId * stepY))); \n' + '} \n'; } + function getGlslUnpackFloat(batchTable) { + if (!batchTable._packFloats) { + return ''; + } + + return 'float unpackFloat(vec4 value) \n' + + '{ \n' + + ' value *= 255.0; \n' + + ' float temp = value.w / 2.0; \n' + + ' float exponent = floor(temp); \n' + + ' float sign = (temp - exponent) * 2.0; \n' + + ' exponent = exponent - float(' + BIAS + '); \n' + + ' sign = sign * 2.0 - 1.0; \n' + + ' sign = -sign; \n' + + ' float unpacked = sign * value.x * float(' + SHIFT_RIGHT_8 + '); \n' + + ' unpacked += sign * value.y * float(' + SHIFT_RIGHT_16 + '); \n' + + ' unpacked += sign * value.z * float(' + SHIFT_RIGHT_24 + '); \n' + + ' return unpacked * pow(10.0, exponent); \n' + + '} \n'; + } + function getComponentType(componentsPerAttribute) { if (componentsPerAttribute === 1) { return 'float'; @@ -382,15 +558,28 @@ define([ var functionReturnType = getComponentType(componentsPerAttribute); var functionReturnValue = getComponentSwizzle(componentsPerAttribute); + var offset = batchTable._offsets[attributeIndex]; + var glslFunction = functionReturnType + ' ' + functionName + '(float batchId) \n' + '{ \n' + ' vec2 st = computeSt(batchId); \n' + - ' st.x += batchTextureStep.x * float(' + attributeIndex + '); \n' + - ' vec4 textureValue = texture2D(batchTexture, st); \n' + - ' ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n'; + ' st.x += batchTextureStep.x * float(' + offset + '); \n'; - if (batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE && !attribute.normalize) { + if (batchTable._packFloats && attribute.componentDatatype !== PixelDatatype.UNSIGNED_BYTE) { + glslFunction += 'vec4 textureValue; \n' + + 'textureValue.x = unpackFloat(texture2D(batchTexture, st)); \n' + + 'textureValue.y = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x, 0.0))); \n' + + 'textureValue.z = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 2.0, 0.0))); \n' + + 'textureValue.w = unpackFloat(texture2D(batchTexture, st + vec2(batchTextureStep.x * 3.0, 0.0))); \n'; + + } else { + glslFunction += ' vec4 textureValue = texture2D(batchTexture, st); \n'; + } + + glslFunction += ' ' + functionReturnType + ' value = textureValue' + functionReturnValue + '; \n'; + + if (batchTable._pixelDatatype === PixelDatatype.UNSIGNED_BYTE && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && !attribute.normalize) { glslFunction += 'value *= 255.0; \n'; } else if (batchTable._pixelDatatype === PixelDatatype.FLOAT && attribute.componentDatatype === ComponentDatatype.UNSIGNED_BYTE && attribute.normalize) { glslFunction += 'value /= 255.0; \n'; @@ -408,10 +597,17 @@ define([ * @returns {BatchTable~updateVertexShaderSourceCallback} A callback for updating a vertex shader source. */ BatchTable.prototype.getVertexShaderCallback = function() { + var attributes = this._attributes; + if (attributes.length === 0) { + return function(source) { + return source; + }; + } + var batchTableShader = 'uniform sampler2D batchTexture; \n'; batchTableShader += getGlslComputeSt(this) + '\n'; + batchTableShader += getGlslUnpackFloat(this) + '\n'; - var attributes = this._attributes; var length = attributes.length; for (var i = 0; i < length; ++i) { batchTableShader += getGlslAttributeFunction(this, i); diff --git a/Source/Scene/PolylineCollection.js b/Source/Scene/PolylineCollection.js index eb0b35c292d3..3e24d43528c3 100644 --- a/Source/Scene/PolylineCollection.js +++ b/Source/Scene/PolylineCollection.js @@ -391,25 +391,21 @@ define([ componentDatatype : ComponentDatatype.UNSIGNED_BYTE, componentsPerAttribute : 4, normalize : true + }, { + functionName : 'batchTable_getCenterHigh', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 3 + }, { + functionName : 'batchTable_getCenterLowAndRadius', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 4 + }, { + functionName : 'batchTable_getDistanceDisplayCondition', + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : 2 }]; - if (defined(context.floatingPointTexture)) { - attributes.push({ - functionName : 'batchTable_getCenterHigh', - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3 - }, { - functionName : 'batchTable_getCenterLowAndRadius', - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 4 - }, { - functionName : 'batchTable_getDistanceDisplayCondition', - componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 2 - }); - } - - collection._batchTable = new BatchTable(attributes, collection._polylines.length); + collection._batchTable = new BatchTable(context, attributes, collection._polylines.length); } var scratchUpdatePolylineEncodedCartesian = new EncodedCartesian3(); @@ -1150,11 +1146,7 @@ define([ return; } - var defines = []; - if (context.floatingPointTexture) { - defines.push('DISTANCE_DISPLAY_CONDITION'); - } - + var defines = ['DISTANCE_DISPLAY_CONDITION']; var vsSource = batchTable.getVertexShaderCallback()(PolylineVS); var vs = new ShaderSource({ defines : defines, diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 51205cf09503..dd05d4749feb 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -608,7 +608,7 @@ define([ } var attributesLength = attributes.length; - var batchTable = new BatchTable(attributes, numberOfInstances); + var batchTable = new BatchTable(context, attributes, numberOfInstances); for (i = 0; i < numberOfInstances; ++i) { var instance = instances[i]; diff --git a/Specs/Scene/BatchTableSpec.js b/Specs/Scene/BatchTableSpec.js index 015fa808b21d..4630dc86e3d0 100644 --- a/Specs/Scene/BatchTableSpec.js +++ b/Specs/Scene/BatchTableSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'Scene/BatchTable', 'Core/Cartesian4', 'Core/ComponentDatatype', + 'Core/Math', 'Renderer/PixelDatatype', 'Renderer/Texture', 'Specs/createScene' @@ -10,6 +11,7 @@ defineSuite([ BatchTable, Cartesian4, ComponentDatatype, + CesiumMath, PixelDatatype, Texture, createScene) { @@ -33,14 +35,16 @@ defineSuite([ }, { functionName : 'batchTable_getCenter', componentDatatype : ComponentDatatype.FLOAT, - componentsPerAttribute : 3 + componentsPerAttribute : 4 }]; var batchTable; var scene; + var context; beforeAll(function() { scene = createScene(); + context = scene.context; }); afterAll(function() { @@ -52,25 +56,31 @@ defineSuite([ }); it('constructor', function() { - batchTable = new BatchTable(unsignedByteAttributes, 2); + batchTable = new BatchTable(context, unsignedByteAttributes, 2); expect(batchTable.attributes).toBe(unsignedByteAttributes); expect(batchTable.numberOfInstances).toEqual(2); }); + it('constructior throws without context', function() { + expect(function() { + batchTable = new BatchTable(undefined, unsignedByteAttributes, 5); + }).toThrowDeveloperError(); + }); + it('constructior throws without attributes', function() { expect(function() { - batchTable = new BatchTable(undefined, 5); + batchTable = new BatchTable(context, undefined, 5); }).toThrowDeveloperError(); }); it('constructor throws without number of instances', function() { expect(function() { - batchTable = new BatchTable(unsignedByteAttributes, undefined); + batchTable = new BatchTable(context, unsignedByteAttributes, undefined); }).toThrowDeveloperError(); }); it('sets and gets entries in the table', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); var i; var color = new Cartesian4(0, 1, 2, 3); @@ -92,8 +102,69 @@ defineSuite([ expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color); }); + it('sets and gets entries in the table with float attributes', function() { + var context = { + floatingPointTexture : true + }; + batchTable = new BatchTable(context, floatAttributes, 5); + + var i; + var color = new Cartesian4(0, 1, 2, 3); + + for (i = 0; i < batchTable.numberOfInstances; ++i) { + batchTable.setBatchedAttribute(i, 0, 1); + batchTable.setBatchedAttribute(i, 1, color); + } + + for (i = 0; i < batchTable.numberOfInstances; ++i) { + expect(batchTable.getBatchedAttribute(3, 0)).toEqual(1); + expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color); + } + + color = new Cartesian4(4, 5, 6, 7); + batchTable.setBatchedAttribute(3, 0, 0); + batchTable.setBatchedAttribute(3, 1, color); + expect(batchTable.getBatchedAttribute(3, 0)).toEqual(0); + expect(batchTable.getBatchedAttribute(3, 1)).toEqual(color); + }); + + it('sets and gets entries in the table with float attributes and forced packing', function() { + var context = { + floatingPointTexture : false + }; + batchTable = new BatchTable(context, floatAttributes, 5); + + var i; + var color = new Cartesian4(1.23456e12, -2.34567e30, 3.45678e-6, -4.56789e-10); + + for (i = 0; i < batchTable.numberOfInstances; ++i) { + batchTable.setBatchedAttribute(i, 0, 1); + batchTable.setBatchedAttribute(i, 1, color); + } + + var value; + for (i = 0; i < batchTable.numberOfInstances; ++i) { + value = batchTable.getBatchedAttribute(3, 0); + expect(value).toEqual(1); + value = batchTable.getBatchedAttribute(3, 1); + expect(value).toEqualEpsilon(color, CesiumMath.EPSILON6); + } + + color = new Cartesian4(0, Number.MAX_VALUE, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY); + batchTable.setBatchedAttribute(3, 0, 0); + batchTable.setBatchedAttribute(3, 1, color); + + value = batchTable.getBatchedAttribute(3, 0); + expect(value).toEqual(0); + value = batchTable.getBatchedAttribute(3, 1); + expect(value.x).toEqual(0.0); + expect(value.y).toEqual(Number.POSITIVE_INFINITY); + expect(value.z).toEqual(Number.POSITIVE_INFINITY); + expect(value.w).toEqual(Number.NEGATIVE_INFINITY); + }); + it('gets with result parameter', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); var color = new Cartesian4(0, 1, 2, 3); batchTable.setBatchedAttribute(0, 1, color); @@ -104,7 +175,7 @@ defineSuite([ }); it('get entry throws when instance index is out of range', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(function() { batchTable.getBatchedAttribute(-1, 0); }).toThrowDeveloperError(); @@ -114,7 +185,7 @@ defineSuite([ }); it('get entry throws when attribute index is out of range', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(function() { batchTable.getBatchedAttribute(0, -1); }).toThrowDeveloperError(); @@ -124,7 +195,7 @@ defineSuite([ }); it('set entry throws when instance index is out of range', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(function() { batchTable.setBatchedAttribute(-1, 0, 0); }).toThrowDeveloperError(); @@ -134,7 +205,7 @@ defineSuite([ }); it('set entry throws when attribute index is out of range', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(function() { batchTable.setBatchedAttribute(0, -1, 1); }).toThrowDeveloperError(); @@ -144,14 +215,14 @@ defineSuite([ }); it('set entry throws when value is undefined', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(function() { batchTable.setBatchedAttribute(0, 0, undefined); }).toThrowDeveloperError(); }); it('creates a uniform callback with unsigned byte texture', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); batchTable.update(scene.frameState); var uniforms = batchTable.getUniformMapCallback()({}); @@ -168,33 +239,36 @@ defineSuite([ expect(uniforms.batchTextureStep().w).toBeGreaterThan(0); }); - it('creates a uniform callback with unsigned byte texture', function() { - batchTable = new BatchTable(floatAttributes, 5); + it('creates a uniform callback with float texture', function() { + if (!context.floatingPointTexture) { + return; + } - if (scene.context.floatingPointTexture) { - batchTable.update(scene.frameState); + batchTable = new BatchTable(context, floatAttributes, 5); + batchTable.update(scene.frameState); + + var uniforms = batchTable.getUniformMapCallback()({}); + expect(uniforms.batchTexture).toBeDefined(); + expect(uniforms.batchTexture()).toBeInstanceOf(Texture); + expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.FLOAT); + expect(uniforms.batchTextureDimensions).toBeDefined(); + expect(uniforms.batchTextureDimensions().x).toBeGreaterThan(0); + expect(uniforms.batchTextureDimensions().y).toBeGreaterThan(0); + expect(uniforms.batchTextureStep).toBeDefined(); + expect(uniforms.batchTextureStep().x).toBeGreaterThan(0); + expect(uniforms.batchTextureStep().y).toBeGreaterThan(0); + expect(uniforms.batchTextureStep().z).toBeGreaterThan(0); + expect(uniforms.batchTextureStep().w).toBeGreaterThan(0); - var uniforms = batchTable.getUniformMapCallback()({}); - expect(uniforms.batchTexture).toBeDefined(); - expect(uniforms.batchTexture()).toBeInstanceOf(Texture); + if (scene.context.floatingPointTexture) { expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.FLOAT); - expect(uniforms.batchTextureDimensions).toBeDefined(); - expect(uniforms.batchTextureDimensions().x).toBeGreaterThan(0); - expect(uniforms.batchTextureDimensions().y).toBeGreaterThan(0); - expect(uniforms.batchTextureStep).toBeDefined(); - expect(uniforms.batchTextureStep().x).toBeGreaterThan(0); - expect(uniforms.batchTextureStep().y).toBeGreaterThan(0); - expect(uniforms.batchTextureStep().z).toBeGreaterThan(0); - expect(uniforms.batchTextureStep().w).toBeGreaterThan(0); } else { - expect(function() { - batchTable.update(scene.frameState); - }).toThrowRuntimeError(); + expect(uniforms.batchTexture().pixelDatatype).toEqual(PixelDatatype.UNSIGNED_BYTE); } }); it('create shader functions', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); var shader = 'void main() { gl_Position = vec4(0.0); }'; var modifiedShader = batchTable.getVertexShaderCallback()(shader); @@ -203,7 +277,7 @@ defineSuite([ }); it('isDestroyed', function() { - batchTable = new BatchTable(unsignedByteAttributes, 5); + batchTable = new BatchTable(context, unsignedByteAttributes, 5); expect(batchTable.isDestroyed()).toEqual(false); batchTable.destroy(); expect(batchTable.isDestroyed()).toEqual(true); diff --git a/Specs/Scene/PolylineCollectionSpec.js b/Specs/Scene/PolylineCollectionSpec.js index 6a2cb9753638..69b3fdbdcf51 100644 --- a/Specs/Scene/PolylineCollectionSpec.js +++ b/Specs/Scene/PolylineCollectionSpec.js @@ -1227,10 +1227,6 @@ defineSuite([ }); it('renders with a distance display condition', function() { - if (!scene.context.floatingPointTexture) { - return; - } - var near = 100.0; var far = 10000.0; From f7553babeb50cd2fabd672c13c915d17fc22f8f9 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 30 Nov 2016 15:40:14 -0500 Subject: [PATCH 2/3] Update CHANGES.md. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 4e0d169780ac..f443e0d21495 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Change Log * `TextureAtlas.borderWidthInPixels` has always been applied to the upper and right edges of each internal texture, but is now also applied to the bottom and left edges of the entire TextureAtlas, guaranteeing borders on all sides regardless of position within the atlas. * Added support for saving html and css in Github Gists. [#4125](https://github.com/AnalyticalGraphicsInc/cesium/issues/4125) * Fixed `Cartographic.fromCartesian` when the cartesian is not on the ellipsoid surface. [#4611](https://github.com/AnalyticalGraphicsInc/cesium/issues/4611) +* Fall back to packing floats into an unsigned byte texture when floating point textures are unsupported. [#4563](https://github.com/AnalyticalGraphicsInc/cesium/issues/4563) ### 1.27 - 2016-11-01 * Deprecated From 57be72cef56bdf1aa78b70e4828656c35a991357 Mon Sep 17 00:00:00 2001 From: Dan Bagnell Date: Wed, 30 Nov 2016 16:55:06 -0500 Subject: [PATCH 3/3] Add comment about memory inefficiency. --- Source/Scene/BatchTable.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/Scene/BatchTable.js b/Source/Scene/BatchTable.js index 2014e9cc1af0..6ae2c54c550e 100644 --- a/Source/Scene/BatchTable.js +++ b/Source/Scene/BatchTable.js @@ -104,6 +104,13 @@ define([ return; } + // PERFORMANCE_IDEA: We may be able to arrange the attributes so they can be packing into fewer texels. + // Right now, an attribute with one component uses an entire texel when 4 single component attributes can + // be packed into a texel. + // + // Packing floats into unsigned byte textures makes the problem worse. A single component float attribute + // will be packed into a single texel leaving 3 texels unused. 4 texels are reserved for each float attribute + // regardless of how many components it has. var pixelDatatype = getDatatype(attributes); var textureFloatSupported = context.floatingPointTexture; var packFloats = pixelDatatype === PixelDatatype.FLOAT && !textureFloatSupported;