From 0a53a149f00032e5f85f93de0df2c0720402fa61 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 19 Apr 2018 14:03:21 -0400 Subject: [PATCH] only use mipmaps for power-of-two textures (#6444) also: move texSubImage updating and mipmap generation into Texture This lets Texture (instead of RasterTileSource) handle the actual gl calls and dealing with cases where mipmaps can't be used. --- src/render/draw_hillshade.js | 4 +-- src/render/texture.js | 54 ++++++++++++++++++++++---------- src/source/raster_tile_source.js | 6 ++-- 3 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 275ea16dbdb..4a51cc06b73 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -123,10 +123,10 @@ function prepareHillshade(painter, tile, sourceMaxZoom) { tile.demTexture = tile.demTexture || painter.getTileTexture(tile.tileSize); if (tile.demTexture) { const demTexture = tile.demTexture; - demTexture.update(pixelData, false); + demTexture.update(pixelData, { premultiply: false }); demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); } else { - tile.demTexture = new Texture(context, pixelData, gl.RGBA, false); + tile.demTexture = new Texture(context, pixelData, gl.RGBA, { premultiply: false }); tile.demTexture.bind(gl.NEAREST, gl.CLAMP_TO_EDGE); } diff --git a/src/render/texture.js b/src/render/texture.js index 2caff150334..ac0bf6785ee 100644 --- a/src/render/texture.js +++ b/src/render/texture.js @@ -40,35 +40,49 @@ class Texture { format: TextureFormat; filter: ?TextureFilter; wrap: ?TextureWrap; + useMipmap: boolean; - constructor(context: Context, image: TextureImage, format: TextureFormat, premultiply: ?boolean) { + constructor(context: Context, image: TextureImage, format: TextureFormat, options: ?{ premultiply?: boolean, useMipmap?: boolean }) { this.context = context; - - const {width, height} = image; - this.size = [width, height]; this.format = format; - this.texture = context.gl.createTexture(); - this.update(image, premultiply); + this.update(image, options); } - update(image: TextureImage, premultiply: ?boolean) { + update(image: TextureImage, options: ?{premultiply?: boolean, useMipmap?: boolean}) { const {width, height} = image; - this.size = [width, height]; - + const resize = !this.size || this.size[0] !== width || this.size[1] !== height; const {context} = this; const {gl} = context; + + this.useMipmap = Boolean(options && options.useMipmap); gl.bindTexture(gl.TEXTURE_2D, this.texture); - context.pixelStoreUnpack.set(1); - if (this.format === gl.RGBA && premultiply !== false) { - context.pixelStoreUnpackPremultiplyAlpha.set(true); - } + if (resize) { + this.size = [width, height]; + + context.pixelStoreUnpack.set(1); + + if (this.format === gl.RGBA && (!options || options.premultiply !== false)) { + context.pixelStoreUnpackPremultiplyAlpha.set(true); + } + + if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData) { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, gl.UNSIGNED_BYTE, image); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, gl.UNSIGNED_BYTE, image.data); + } - if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData) { - gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, gl.UNSIGNED_BYTE, image); } else { - gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, gl.UNSIGNED_BYTE, image.data); + if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData) { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image); + } else { + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image.data); + } + } + + if (this.useMipmap && this.isSizePowerOfTwo()) { + gl.generateMipmap(gl.TEXTURE_2D); } } @@ -77,6 +91,10 @@ class Texture { const {gl} = context; gl.bindTexture(gl.TEXTURE_2D, this.texture); + if (minFilter === gl.LINEAR_MIPMAP_NEAREST && !this.isSizePowerOfTwo()) { + minFilter = gl.LINEAR; + } + if (filter !== this.filter) { gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter || filter); @@ -90,6 +108,10 @@ class Texture { } } + isSizePowerOfTwo() { + return this.size[0] === this.size[1] && (Math.log(this.size[0]) / Math.LN2) % 1 === 0; + } + destroy() { const {gl} = this.context; gl.deleteTexture(this.texture); diff --git a/src/source/raster_tile_source.js b/src/source/raster_tile_source.js index d7fe6863173..ee519113172 100644 --- a/src/source/raster_tile_source.js +++ b/src/source/raster_tile_source.js @@ -104,17 +104,15 @@ class RasterTileSource extends Evented implements Source { const gl = context.gl; tile.texture = this.map.painter.getTileTexture(img.width); if (tile.texture) { - tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); - gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, img); + tile.texture.update(img, { useMipmap: true }); } else { - tile.texture = new Texture(context, img, gl.RGBA); + tile.texture = new Texture(context, img, gl.RGBA, { useMipmap: true }); tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); if (context.extTextureFilterAnisotropic) { gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } } - gl.generateMipmap(gl.TEXTURE_2D); tile.state = 'loaded';