diff --git a/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html new file mode 100644 index 000000000000..c82ceb588ff8 --- /dev/null +++ b/Apps/Sandcastle/gallery/Imagery Layers Texture Filters.html @@ -0,0 +1,110 @@ + + +
+ + + + + +textureMinificationFilter
with respect to the possible enum values.
+ *
+ * @private
+ *
+ * @param textureMagnificationFilter
+ * @returns {Boolean} true
if textureMagnificationFilter
is valid.
+ */
validate : function(textureMagnificationFilter) {
return ((textureMagnificationFilter === TextureMagnificationFilter.NEAREST) ||
(textureMagnificationFilter === TextureMagnificationFilter.LINEAR));
diff --git a/Source/Renderer/TextureMinificationFilter.js b/Source/Renderer/TextureMinificationFilter.js
index 270d5d9ecc39..0f31ea0ee810 100644
--- a/Source/Renderer/TextureMinificationFilter.js
+++ b/Source/Renderer/TextureMinificationFilter.js
@@ -7,16 +7,66 @@ define([
'use strict';
/**
- * @private
+ * Enumerates all possible filters used when minifying WebGL textures, which takes places when zooming
+ * out of imagery. Provides the possible values for the {@link ImageryLayer#minificationFilter} property.
+ *
+ * @exports TextureMinificationFilter
+ *
+ * @see TextureMagnificationFilter
+ * @see ImageryLayer#minificationFilter
*/
var TextureMinificationFilter = {
+ /**
+ * Nearest neighbor sampling of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
NEAREST : WebGLConstants.NEAREST,
+ /**
+ * Bi-linear interpolation of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
LINEAR : WebGLConstants.LINEAR,
+ /**
+ * WebGL NEAREST_MIPMAP_NEAREST
interpolation of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
NEAREST_MIPMAP_NEAREST : WebGLConstants.NEAREST_MIPMAP_NEAREST,
+ /**
+ * WebGL LINEAR_MIPMAP_NEAREST
interpolation of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
LINEAR_MIPMAP_NEAREST : WebGLConstants.LINEAR_MIPMAP_NEAREST,
+ /**
+ * WebGL NEAREST_MIPMAP_LINEAR
interpolation of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
NEAREST_MIPMAP_LINEAR : WebGLConstants.NEAREST_MIPMAP_LINEAR,
+ /**
+ * WebGL LINEAR_MIPMAP_LINEAR
interpolation of image pixels to texture.
+ *
+ * @type {Number}
+ * @constant
+ */
LINEAR_MIPMAP_LINEAR : WebGLConstants.LINEAR_MIPMAP_LINEAR,
+ /**
+ * Validates the given textureMinificationFilter
with respect to the possible enum values.
+ *
+ * @private
+ *
+ * @param textureMinificationFilter
+ * @returns {Boolean} true
if textureMinificationFilter
is valid.
+ */
validate : function(textureMinificationFilter) {
return ((textureMinificationFilter === TextureMinificationFilter.NEAREST) ||
(textureMinificationFilter === TextureMinificationFilter.LINEAR) ||
diff --git a/Source/Scene/ImageryLayer.js b/Source/Scene/ImageryLayer.js
index c03383ec04c1..839da40ee183 100644
--- a/Source/Scene/ImageryLayer.js
+++ b/Source/Scene/ImageryLayer.js
@@ -136,6 +136,14 @@ define([
* the gamma value to use for the tile. The function is executed for every
* frame and for every tile, so it must be fast.
* @param {ImagerySplitDirection|Function} [options.splitDirection=ImagerySplitDirection.NONE] The {@link ImagerySplitDirection} split to apply to this layer.
+ * @param {TextureMinificationFilter} [options.minificationFilter=TextureMinificationFilter.LINEAR] The
+ * texture minification filter to apply to this layer. Possible values
+ * are TextureMinificationFilter.LINEAR
and
+ * TextureMinificationFilter.NEAREST
.
+ * @param {TextureMagnificationFilter} [options.magnificationFilter=TextureMagnificationFilter.LINEAR] The
+ * texture minification filter to apply to this layer. Possible values
+ * are TextureMagnificationFilter.LINEAR
and
+ * TextureMagnificationFilter.NEAREST
.
* @param {Boolean} [options.show=true] True if the layer is shown; otherwise, false.
* @param {Number} [options.maximumAnisotropy=maximum supported] The maximum anisotropy level to use
* for texture filtering. If this parameter is not specified, the maximum anisotropy supported
@@ -211,6 +219,32 @@ define([
*/
this.splitDirection = defaultValue(options.splitDirection, defaultValue(imageryProvider.defaultSplit, ImageryLayer.DEFAULT_SPLIT));
+ /**
+ * The {@link TextureMinificationFilter} to apply to this layer.
+ * Possible values are {@link TextureMinificationFilter.LINEAR} (the default)
+ * and {@link TextureMinificationFilter.NEAREST}.
+ *
+ * To take effect, this property must be set immediately after adding the imagery layer.
+ * Once a texture is loaded it won't be possible to change the texture filter used.
+ *
+ * @type {TextureMinificationFilter}
+ * @default {@link ImageryLayer.DEFAULT_MINIFICATION_FILTER}
+ */
+ this.minificationFilter = defaultValue(options.minificationFilter, defaultValue(imageryProvider.defaultMinificationFilter, ImageryLayer.DEFAULT_MINIFICATION_FILTER));
+
+ /**
+ * The {@link TextureMagnificationFilter} to apply to this layer.
+ * Possible values are {@link TextureMagnificationFilter.LINEAR} (the default)
+ * and {@link TextureMagnificationFilter.NEAREST}.
+ *
+ * To take effect, this property must be set immediately after adding the imagery layer.
+ * Once a texture is loaded it won't be possible to change the texture filter used.
+ *
+ * @type {TextureMagnificationFilter}
+ * @default {@link ImageryLayer.DEFAULT_MAGNIFICATION_FILTER}
+ */
+ this.magnificationFilter = defaultValue(options.magnificationFilter, defaultValue(imageryProvider.defaultMagnificationFilter, ImageryLayer.DEFAULT_MAGNIFICATION_FILTER));
+
/**
* Determines if this layer is shown.
*
@@ -309,13 +343,29 @@ define([
ImageryLayer.DEFAULT_GAMMA = 1.0;
/**
- * This value is used as the default spliat for the imagery layer if one is not provided during construction
+ * This value is used as the default split for the imagery layer if one is not provided during construction
* or by the imagery provider.
* @type {ImagerySplitDirection}
* @default ImagerySplitDirection.NONE
*/
ImageryLayer.DEFAULT_SPLIT = ImagerySplitDirection.NONE;
+ /**
+ * This value is used as the default texture minification filter for the imagery layer if one is not provided
+ * during construction or by the imagery provider.
+ * @type {TextureMinificationFilter}
+ * @default TextureMinificationFilter.LINEAR
+ */
+ ImageryLayer.DEFAULT_MINIFICATION_FILTER = TextureMinificationFilter.LINEAR;
+
+ /**
+ * This value is used as the default texture magnification filter for the imagery layer if one is not provided
+ * during construction or by the imagery provider.
+ * @type {TextureMagnificationFilter}
+ * @default TextureMagnificationFilter.LINEAR
+ */
+ ImageryLayer.DEFAULT_MAGNIFICATION_FILTER = TextureMagnificationFilter.LINEAR;
+
/**
* Gets a value indicating whether this layer is the base layer in the
* {@link ImageryLayerCollection}. The base layer is the one that underlies all
@@ -764,6 +814,11 @@ define([
}
}
+ var sampler = new Sampler({
+ minificationFilter : this.minificationFilter,
+ magnificationFilter : this.magnificationFilter
+ });
+
// Imagery does not need to be discarded, so upload it to WebGL.
var texture;
if (defined(image.internalFormat)) {
@@ -774,13 +829,15 @@ define([
height : image.height,
source : {
arrayBufferView : image.bufferView
- }
+ },
+ sampler : sampler
});
} else {
texture = new Texture({
context : context,
source : image,
- pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB
+ pixelFormat : imageryProvider.hasAlphaChannel ? PixelFormat.RGBA : PixelFormat.RGB,
+ sampler : sampler
});
}
@@ -793,30 +850,52 @@ define([
imagery.state = ImageryState.TEXTURE_LOADED;
};
+ function getSamplerKey(minificationFilter, magnificationFilter, maximumAnisotropy) {
+ return minificationFilter + ':' + magnificationFilter + ':' + maximumAnisotropy;
+ }
+
function finalizeReprojectTexture(imageryLayer, context, imagery, texture) {
+ var minificationFilter = imageryLayer.minificationFilter;
+ var magnificationFilter = imageryLayer.magnificationFilter;
+ var usesLinearTextureFilter = minificationFilter === TextureMinificationFilter.LINEAR && magnificationFilter === TextureMagnificationFilter.LINEAR;
// Use mipmaps if this texture has power-of-two dimensions.
- if (!PixelFormat.isCompressedFormat(texture.pixelFormat) && CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
- var mipmapSampler = context.cache.imageryLayer_mipmapSampler;
+ // In addition, mipmaps are only generated if the texture filters are both LINEAR.
+ if (usesLinearTextureFilter && !PixelFormat.isCompressedFormat(texture.pixelFormat) && CesiumMath.isPowerOfTwo(texture.width) && CesiumMath.isPowerOfTwo(texture.height)) {
+ minificationFilter = TextureMinificationFilter.LINEAR_MIPMAP_LINEAR;
+ var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy;
+ var maximumAnisotropy = Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy));
+ var mipmapSamplerKey = getSamplerKey(minificationFilter, magnificationFilter, maximumAnisotropy);
+ var mipmapSamplers = context.cache.imageryLayerMipmapSamplers;
+ if (!defined(mipmapSamplers)) {
+ mipmapSamplers = {};
+ context.cache.imageryLayerMipmapSamplers = mipmapSamplers;
+ }
+ var mipmapSampler = mipmapSamplers[mipmapSamplerKey];
if (!defined(mipmapSampler)) {
- var maximumSupportedAnisotropy = ContextLimits.maximumTextureFilterAnisotropy;
- mipmapSampler = context.cache.imageryLayer_mipmapSampler = new Sampler({
+ mipmapSampler = mipmapSamplers[mipmapSamplerKey] = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,
- magnificationFilter : TextureMagnificationFilter.LINEAR,
- maximumAnisotropy : Math.min(maximumSupportedAnisotropy, defaultValue(imageryLayer._maximumAnisotropy, maximumSupportedAnisotropy))
+ minificationFilter : minificationFilter,
+ magnificationFilter : magnificationFilter,
+ maximumAnisotropy : maximumAnisotropy
});
}
texture.generateMipmap(MipmapHint.NICEST);
texture.sampler = mipmapSampler;
} else {
- var nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler;
+ var nonMipmapSamplerKey = getSamplerKey(minificationFilter, magnificationFilter, 0);
+ var nonMipmapSamplers = context.cache.imageryLayerNonMipmapSamplers;
+ if (!defined(nonMipmapSamplers)) {
+ nonMipmapSamplers = {};
+ context.cache.imageryLayerNonMipmapSamplers = nonMipmapSamplers;
+ }
+ var nonMipmapSampler = nonMipmapSamplers[nonMipmapSamplerKey];
if (!defined(nonMipmapSampler)) {
- nonMipmapSampler = context.cache.imageryLayer_nonMipmapSampler = new Sampler({
+ nonMipmapSampler = nonMipmapSamplers[nonMipmapSamplerKey] = new Sampler({
wrapS : TextureWrap.CLAMP_TO_EDGE,
wrapT : TextureWrap.CLAMP_TO_EDGE,
- minificationFilter : TextureMinificationFilter.LINEAR,
- magnificationFilter : TextureMagnificationFilter.LINEAR
+ minificationFilter : minificationFilter,
+ magnificationFilter : magnificationFilter
});
}
texture.sampler = nonMipmapSampler;
diff --git a/Source/Scene/ImageryProvider.js b/Source/Scene/ImageryProvider.js
index 3cfdc11e9ace..efb784ee01fb 100644
--- a/Source/Scene/ImageryProvider.js
+++ b/Source/Scene/ImageryProvider.js
@@ -93,6 +93,22 @@ define([
*/
this.defaultGamma = undefined;
+ /**
+ * The default texture minification filter to apply to this provider.
+ *
+ * @type {TextureMinificationFilter}
+ * @default undefined
+ */
+ this.defaultMinificationFilter = undefined;
+
+ /**
+ * The default texture magnification filter to apply to this provider.
+ *
+ * @type {TextureMagnificationFilter}
+ * @default undefined
+ */
+ this.defaultMagnificationFilter = undefined;
+
DeveloperError.throwInstantiationError();
}
diff --git a/Specs/Scene/ImageryLayerSpec.js b/Specs/Scene/ImageryLayerSpec.js
index fbb3211d453e..22fe1c728999 100644
--- a/Specs/Scene/ImageryLayerSpec.js
+++ b/Specs/Scene/ImageryLayerSpec.js
@@ -7,6 +7,8 @@ defineSuite([
'Core/Rectangle',
'Core/RequestScheduler',
'Renderer/ComputeEngine',
+ 'Renderer/TextureMagnificationFilter',
+ 'Renderer/TextureMinificationFilter',
'Scene/ArcGisMapServerImageryProvider',
'Scene/BingMapsImageryProvider',
'Scene/createTileMapServiceImageryProvider',
@@ -30,6 +32,8 @@ defineSuite([
Rectangle,
RequestScheduler,
ComputeEngine,
+ TextureMagnificationFilter,
+ TextureMinificationFilter,
ArcGisMapServerImageryProvider,
BingMapsImageryProvider,
createTileMapServiceImageryProvider,
@@ -187,6 +191,9 @@ defineSuite([
return imagery.state === ImageryState.READY;
}).then(function() {
expect(imagery.texture).toBeDefined();
+ expect(imagery.texture.sampler).toBeDefined();
+ expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
+ expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR);
expect(textureBeforeReprojection).not.toEqual(imagery.texture);
imagery.releaseReference();
});
@@ -269,6 +276,9 @@ defineSuite([
return imagery.state === ImageryState.READY;
}).then(function() {
expect(imagery.texture).toBeDefined();
+ expect(imagery.texture.sampler).toBeDefined();
+ expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
+ expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR);
expect(textureBeforeReprojection).not.toEqual(imagery.texture);
imagery.releaseReference();
});
@@ -315,6 +325,9 @@ defineSuite([
return imagery.state === ImageryState.READY;
}).then(function() {
expect(imagery.texture).toBeDefined();
+ expect(imagery.texture.sampler).toBeDefined();
+ expect(imagery.texture.sampler.minificationFilter).toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR);
+ expect(imagery.texture.sampler.magnificationFilter).toEqual(TextureMinificationFilter.LINEAR);
expect(imagery.texture).toBe(imagery.textureWebMercator);
imagery.releaseReference();
});
@@ -367,6 +380,41 @@ defineSuite([
expect(layer.isDestroyed()).toEqual(true);
});
+ it('allows setting texture filter properties', function() {
+ var provider = new SingleTileImageryProvider({
+ url : 'Data/Images/Red16x16.png'
+ });
+
+ // expect default LINEAR
+ var layer = new ImageryLayer(provider);
+ expect(layer.minificationFilter).toEqual(TextureMinificationFilter.LINEAR);
+ expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.LINEAR);
+ layer.destroy();
+
+ // change to NEAREST
+ layer = new ImageryLayer(provider, {
+ minificationFilter: TextureMinificationFilter.NEAREST,
+ magnificationFilter: TextureMagnificationFilter.NEAREST
+ });
+ expect(layer.minificationFilter).toEqual(TextureMinificationFilter.NEAREST);
+ expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.NEAREST);
+ layer.destroy();
+ });
+
+ it('uses default texture filter properties of ImageryProvider', function() {
+ var provider = new SingleTileImageryProvider({
+ url : 'Data/Images/Red16x16.png'
+ });
+
+ provider.defaultMinificationFilter = TextureMinificationFilter.NEAREST;
+ provider.defaultMagnificationFilter = TextureMinificationFilter.NEAREST;
+
+ var layer = new ImageryLayer(provider);
+ expect(layer.minificationFilter).toEqual(TextureMinificationFilter.NEAREST);
+ expect(layer.magnificationFilter).toEqual(TextureMagnificationFilter.NEAREST);
+ layer.destroy();
+ });
+
it('returns HTTP status code information in TileProviderError', function() {
// Web browsers unfortunately provide very little information about what went wrong when an Image fails
// to load. But when an imagery provider is configured to use a TileDiscardPolicy, Cesium downloads the image