diff --git a/src/gl/context.js b/src/gl/context.js index 876d8a4ece1..38d6515bd25 100644 --- a/src/gl/context.js +++ b/src/gl/context.js @@ -88,6 +88,10 @@ class Context { pixelStoreUnpack: State; pixelStoreUnpackPremultiplyAlpha: State; + extTextureFilterAnisotropic: any; + extTextureFilterAnisotropicMax: any; + extTextureHalfFloat: any; + constructor(gl: WebGLRenderingContext) { this.gl = gl; this.extVertexArrayObject = this.gl.getExtension('OES_vertex_array_object'); @@ -119,6 +123,21 @@ class Context { this.bindVertexArrayOES = this.extVertexArrayObject && new State(new BindVertexArrayOES(this)); this.pixelStoreUnpack = new State(new PixelStoreUnpack(this)); this.pixelStoreUnpackPremultiplyAlpha = new State(new PixelStoreUnpackPremultiplyAlpha(this)); + + this.extTextureFilterAnisotropic = ( + gl.getExtension('EXT_texture_filter_anisotropic') || + gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || + gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') + ); + if (this.extTextureFilterAnisotropic) { + this.extTextureFilterAnisotropicMax = gl.getParameter(this.extTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT); + } + + this.extTextureHalfFloat = gl.getExtension('OES_texture_half_float'); + if (this.extTextureHalfFloat) { + gl.getExtension('OES_texture_half_float_linear'); + } + } createIndexBuffer(array: TriangleIndexArray | LineIndexArray, dynamicDraw?: boolean) { @@ -140,8 +159,8 @@ class Context { return rbo; } - createFramebuffer() { - return new Framebuffer(this); + createFramebuffer(width: number, height: number) { + return new Framebuffer(this, width, height); } clear({color, depth}: ClearArgs) { diff --git a/src/gl/framebuffer.js b/src/gl/framebuffer.js index 93941c45a2a..7ed0b36c156 100644 --- a/src/gl/framebuffer.js +++ b/src/gl/framebuffer.js @@ -6,18 +6,37 @@ import type Context from './context'; class Framebuffer { context: Context; + width: number; + height: number; framebuffer: WebGLFramebuffer; colorAttachment: State; depthAttachment: State; - constructor(context: Context) { + constructor(context: Context, width: number, height: number) { this.context = context; + this.width = width; + this.height = height; const gl = context.gl; const fbo = this.framebuffer = gl.createFramebuffer(); this.colorAttachment = new State(new ColorAttachment(context, fbo)); this.depthAttachment = new State(new DepthAttachment(context, fbo)); } + + destroy() { + const gl = this.context.gl; + + const texture = this.colorAttachment.get(); + if (texture) gl.deleteTexture(texture); + + const renderbuffer = this.depthAttachment.get(); + if (renderbuffer) gl.deleteRenderbuffer(renderbuffer); + + gl.deleteFramebuffer(this.framebuffer); + + // TODO we could actually preserve framebuffer on resize since it + // has no inherent size-dependent storage, and just destroy attachments... + } } module.exports = Framebuffer; diff --git a/src/gl/value.js b/src/gl/value.js index 1f6751b8ea7..f3066a249d3 100644 --- a/src/gl/value.js +++ b/src/gl/value.js @@ -1,9 +1,7 @@ // @flow -const assert = require('assert'); const Color = require('../style-spec/util/color'); const util = require('../util/util'); -const window = require('../util/window'); import type Context from './context'; import type { @@ -26,13 +24,9 @@ export interface Value { class ContextValue { context: Context; - parent: ?any; - constructor(context: Context, parent: ?any) { + constructor(context: Context) { this.context = context; - if (parent) { - this.parent = parent; - } } static equal(a, b): boolean { @@ -332,13 +326,20 @@ class PixelStoreUnpackPremultiplyAlpha extends ContextValue implements Value { + constructor(context: Context, parent: WebGLFramebuffer) { + super(context); + this.parent = parent; + } +} + +class ColorAttachment extends FramebufferValue implements Value { static default() { return null; } set(v: ?WebGLTexture): void { - assert(this.parent && this.parent instanceof window.WebGLFramebuffer); - const gl = this.context.gl; this.context.bindFramebuffer.set(this.parent); // note: it's possible to attach a renderbuffer to the color @@ -351,12 +352,10 @@ class ColorAttachment extends ContextValue implements Value { } } -class DepthAttachment extends ContextValue implements Value { +class DepthAttachment extends FramebufferValue implements Value { static default() { return null; } set(v: ?WebGLRenderbuffer): void { - assert(this.parent && this.parent instanceof window.WebGLFramebuffer); - const gl = this.context.gl; this.context.bindFramebuffer.set(this.parent); // note: it's possible to attach a texture to the depth attachment diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index 0dedaced459..ff762720b44 100644 --- a/src/render/draw_fill_extrusion.js +++ b/src/render/draw_fill_extrusion.js @@ -2,6 +2,7 @@ const glMatrix = require('@mapbox/gl-matrix'); const pattern = require('./pattern'); +const Texture = require('./texture'); const Color = require('../style-spec/util/color'); const mat3 = glMatrix.mat3; const mat4 = glMatrix.mat4; @@ -20,9 +21,11 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa return; } - if (painter.renderPass === '3d') { + if (painter.renderPass === 'offscreen') { const context = painter.context; + setupFramebuffer(painter, layer); + context.stencilTest.set(false); context.depthTest.set(true); @@ -37,6 +40,34 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa } } +function setupFramebuffer(painter, layer) { + const context = painter.context; + const gl = context.gl; + + let renderTarget = layer.viewportFrame; + + if (painter.depthRboNeedsClear) { + painter.setupOffscreenDepthRenderbuffer(); + } + + if (!renderTarget) { + const texture = new Texture(context, {width: painter.width, height: painter.height, data: null}, gl.RGBA); + texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + + renderTarget = layer.viewportFrame = context.createFramebuffer(painter.width, painter.height); + renderTarget.colorAttachment.set(texture.texture); + } + + context.bindFramebuffer.set(renderTarget.framebuffer); + renderTarget.depthAttachment.set(painter.depthRbo); + + if (painter.depthRboNeedsClear) { + context.clear({ depth: 1 }); + painter.depthRboNeedsClear = false; + } + +} + function drawExtrusionTexture(painter, layer) { const renderedTexture = layer.viewportFrame; if (!renderedTexture) return; diff --git a/src/render/draw_heatmap.js b/src/render/draw_heatmap.js index 537d3339480..054b7d6ae2c 100644 --- a/src/render/draw_heatmap.js +++ b/src/render/draw_heatmap.js @@ -14,141 +14,148 @@ import type {OverscaledTileID} from '../source/tile_id'; module.exports = drawHeatmap; function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapStyleLayer, coords: Array) { - if (painter.isOpaquePass) return; if (layer.paint.get('heatmap-opacity') === 0) { return; } - const context = painter.context; - const gl = context.gl; + if (painter.renderPass === 'offscreen') { + const context = painter.context; + const gl = context.gl; - painter.setDepthSublayer(0); - context.depthMask.set(false); + painter.setDepthSublayer(0); + context.depthMask.set(false); - // Allow kernels to be drawn across boundaries, so that - // large kernels are not clipped to tiles - context.stencilTest.set(false); + // Allow kernels to be drawn across boundaries, so that + // large kernels are not clipped to tiles + context.stencilTest.set(false); - renderToTexture(context, painter, layer); + bindFramebuffer(context, painter, layer); - context.clear({ color: Color.transparent }); + context.clear({ color: Color.transparent }); - // Turn on additive blending for kernels, which is a key aspect of kernel density estimation formula - context.blendFunc.set([gl.ONE, gl.ONE]); + // Turn on additive blending for kernels, which is a key aspect of kernel density estimation formula + context.blendFunc.set([gl.ONE, gl.ONE]); - for (let i = 0; i < coords.length; i++) { - const coord = coords[i]; + for (let i = 0; i < coords.length; i++) { + const coord = coords[i]; - // Skip tiles that have uncovered parents to avoid flickering; we don't need - // to use complex tile masking here because the change between zoom levels is subtle, - // so it's fine to simply render the parent until all its 4 children are loaded - if (sourceCache.hasRenderableParent(coord)) continue; + // Skip tiles that have uncovered parents to avoid flickering; we don't need + // to use complex tile masking here because the change between zoom levels is subtle, + // so it's fine to simply render the parent until all its 4 children are loaded + if (sourceCache.hasRenderableParent(coord)) continue; - const tile = sourceCache.getTile(coord); - const bucket: ?HeatmapBucket = (tile.getBucket(layer): any); - if (!bucket) continue; + const tile = sourceCache.getTile(coord); + const bucket: ?HeatmapBucket = (tile.getBucket(layer): any); + if (!bucket) continue; - const programConfiguration = bucket.programConfigurations.get(layer.id); - const program = painter.useProgram('heatmap', programConfiguration); - const {zoom} = painter.transform; - programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom}); - gl.uniform1f(program.uniforms.u_radius, layer.paint.get('heatmap-radius')); + const programConfiguration = bucket.programConfigurations.get(layer.id); + const program = painter.useProgram('heatmap', programConfiguration); + const {zoom} = painter.transform; + programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom}); + gl.uniform1f(program.uniforms.u_radius, layer.paint.get('heatmap-radius')); - gl.uniform1f(program.uniforms.u_extrude_scale, pixelsToTileUnits(tile, 1, zoom)); + gl.uniform1f(program.uniforms.u_extrude_scale, pixelsToTileUnits(tile, 1, zoom)); - gl.uniform1f(program.uniforms.u_intensity, layer.paint.get('heatmap-intensity')); - gl.uniformMatrix4fv(program.uniforms.u_matrix, false, coord.posMatrix); + gl.uniform1f(program.uniforms.u_intensity, layer.paint.get('heatmap-intensity')); + gl.uniformMatrix4fv(program.uniforms.u_matrix, false, coord.posMatrix); - program.draw( - context, - gl.TRIANGLES, - layer.id, - bucket.layoutVertexBuffer, - bucket.indexBuffer, - bucket.segments, - programConfiguration); - } + program.draw( + context, + gl.TRIANGLES, + layer.id, + bucket.layoutVertexBuffer, + bucket.indexBuffer, + bucket.segments, + programConfiguration); + } - renderTextureToMap(context, painter, layer); + context.viewport.set([0, 0, painter.width, painter.height]); + context.blendFunc.set(painter._showOverdrawInspector ? [gl.CONSTANT_COLOR, gl.ONE] : [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); + + } else if (painter.renderPass === 'translucent') { + renderTextureToMap(painter, layer); + } } -function renderToTexture(context, painter, layer) { +function bindFramebuffer(context, painter, layer) { const gl = context.gl; context.activeTexture.set(gl.TEXTURE1); // Use a 4x downscaled screen texture for better performance context.viewport.set([0, 0, painter.width / 4, painter.height / 4]); - let texture = layer.heatmapTexture; let fbo = layer.heatmapFbo; - if (!texture || !fbo) { - texture = layer.heatmapTexture = gl.createTexture(); + if (!fbo) { + const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - fbo = layer.heatmapFbo = context.createFramebuffer(); + fbo = layer.heatmapFbo = context.createFramebuffer(painter.width / 4, painter.height / 4); - bindTextureFramebuffer(context, painter, texture, fbo); + bindTextureToFramebuffer(context, painter, texture, fbo); } else { - gl.bindTexture(gl.TEXTURE_2D, texture); + gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); context.bindFramebuffer.set(fbo.framebuffer); } } -function bindTextureFramebuffer(context, painter, texture, fbo) { +function bindTextureToFramebuffer(context, painter, texture, fbo) { const gl = context.gl; // Use the higher precision half-float texture where available (producing much smoother looking heatmaps); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, painter.width / 4, painter.height / 4, 0, gl.RGBA, - painter.extTextureHalfFloat ? painter.extTextureHalfFloat.HALF_FLOAT_OES : gl.UNSIGNED_BYTE, null); + context.extTextureHalfFloat ? context.extTextureHalfFloat.HALF_FLOAT_OES : gl.UNSIGNED_BYTE, null); fbo.colorAttachment.set(texture); // If using half-float texture as a render target is not supported, fall back to a low precision texture - if (painter.extTextureHalfFloat && gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { - painter.extTextureHalfFloat = null; + if (context.extTextureHalfFloat && gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { + context.extTextureHalfFloat = null; fbo.colorAttachment.setDirty(); - bindTextureFramebuffer(context, painter, texture, fbo); + bindTextureToFramebuffer(context, painter, texture, fbo); } } -function renderTextureToMap(context, painter, layer) { +function renderTextureToMap(painter, layer) { + const context = painter.context; const gl = context.gl; - context.bindFramebuffer.set(null); - context.activeTexture.set(gl.TEXTURE2); + + // Here we bind two different textures from which we'll sample in drawing + // heatmaps: the kernel texture, prepared in the offscreen pass, and a + // color ramp texture. + const fbo = layer.heatmapFbo; + if (!fbo) return; + context.activeTexture.set(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); + + context.activeTexture.set(gl.TEXTURE1); let colorRampTexture = layer.colorRampTexture; if (!colorRampTexture) { colorRampTexture = layer.colorRampTexture = new Texture(context, layer.colorRamp, gl.RGBA); } colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - context.blendFunc.set(painter._showOverdrawInspector ? [gl.CONSTANT_COLOR, gl.ONE] : [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); + context.depthTest.set(false); const program = painter.useProgram('heatmapTexture'); - context.viewport.set([0, 0, painter.width, painter.height]); - - context.activeTexture.set(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, layer.heatmapTexture); - const opacity = layer.paint.get('heatmap-opacity'); gl.uniform1f(program.uniforms.u_opacity, opacity); - gl.uniform1i(program.uniforms.u_image, 1); - gl.uniform1i(program.uniforms.u_color_ramp, 2); + gl.uniform1i(program.uniforms.u_image, 0); + gl.uniform1i(program.uniforms.u_color_ramp, 1); const matrix = mat4.create(); mat4.ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, matrix); - context.depthTest.set(false); - gl.uniform2f(program.uniforms.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); + painter.viewportVAO.bind(painter.context, program, painter.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index ef466bc6ec4..2646a537809 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -12,7 +12,7 @@ import type {OverscaledTileID} from '../source/tile_id'; module.exports = drawHillshade; function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: HillshadeStyleLayer, tileIDs: Array) { - if (painter.renderPass !== 'hillshadeprepare' && painter.renderPass !== 'translucent') return; + if (painter.renderPass !== 'offscreen' && painter.renderPass !== 'translucent') return; const context = painter.context; @@ -21,7 +21,7 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh for (const tileID of tileIDs) { const tile = sourceCache.getTile(tileID); - if (tile.needsHillshadePrepare && painter.renderPass === 'hillshadeprepare') { + if (tile.needsHillshadePrepare && painter.renderPass === 'offscreen') { prepareHillshade(painter, tile); continue; } else if (painter.renderPass === 'translucent') { @@ -30,7 +30,6 @@ function drawHillshade(painter: Painter, sourceCache: SourceCache, layer: Hillsh } context.viewport.set([0, 0, painter.width, painter.height]); - context.bindFramebuffer.set(null); } function setLight(program, painter, layer) { @@ -51,6 +50,8 @@ function getTileLatRange(painter, tileID: OverscaledTileID) { function renderHillshade(painter, tile, layer) { const context = painter.context; const gl = context.gl; + const fbo = tile.fbo; + if (!fbo) return; const program = painter.useProgram('hillshade'); const posMatrix = painter.transform.calculatePosMatrix(tile.tileID.toUnwrapped()); @@ -59,7 +60,7 @@ function renderHillshade(painter, tile, layer) { const latRange = getTileLatRange(painter, tile.tileID); context.activeTexture.set(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, tile.fbo.colorAttachment.get()); + gl.bindTexture(gl.TEXTURE_2D, fbo.colorAttachment.get()); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); gl.uniform2fv(program.uniforms.u_latrange, latRange); @@ -126,15 +127,17 @@ function prepareHillshade(painter, tile) { } context.activeTexture.set(gl.TEXTURE0); - if (!tile.fbo) { + let fbo = tile.fbo; + + if (!fbo) { const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); renderTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - tile.fbo = context.createFramebuffer(); - tile.fbo.colorAttachment.set(renderTexture.texture); + fbo = tile.fbo = context.createFramebuffer(tileSize, tileSize); + fbo.colorAttachment.set(renderTexture.texture); } - context.bindFramebuffer.set(tile.fbo.framebuffer); + context.bindFramebuffer.set(fbo.framebuffer); context.viewport.set([0, 0, tileSize, tileSize]); const matrix = mat4.create(); diff --git a/src/render/painter.js b/src/render/painter.js index 0f2c4e2bfed..af29c8f5837 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -40,8 +40,9 @@ import type LineAtlas from './line_atlas'; import type ImageManager from './image_manager'; import type GlyphManager from './glyph_manager'; import type VertexBuffer from '../gl/vertex_buffer'; +import type Framebuffer from '../gl/framebuffer'; -export type RenderPass = '3d' | 'hillshadeprepare' | 'opaque' | 'translucent'; +export type RenderPass = 'offscreen' | 'opaque' | 'translucent'; type PainterOptions = { showOverdrawInspector: boolean, @@ -61,6 +62,7 @@ class Painter { context: Context; transform: Transform; _tileTextures: { [number]: Array }; + _tileFramebuffers: { [number]: Array }; numSublayers: number; depthEpsilon: number; lineWidthRange: [number, number]; @@ -68,6 +70,7 @@ class Painter { width: number; height: number; depthRbo: WebGLRenderbuffer; + depthRboNeedsClear: boolean; tileExtentBuffer: VertexBuffer; tileExtentVAO: VertexArrayObject; tileExtentPatternVAO: VertexArrayObject; @@ -77,9 +80,6 @@ class Painter { rasterBoundsVAO: VertexArrayObject; viewportBuffer: VertexBuffer; viewportVAO: VertexArrayObject; - extTextureFilterAnisotropic: any; - extTextureFilterAnisotropicMax: any; - extTextureHalfFloat: any; _tileClippingMaskIDs: { [number]: number }; style: Style; options: PainterOptions; @@ -99,6 +99,7 @@ class Painter { this.context = new Context(gl); this.transform = transform; this._tileTextures = {}; + this._tileFramebuffers = {}; this.setup(); @@ -107,6 +108,8 @@ class Painter { this.numSublayers = SourceCache.maxUnderzooming + SourceCache.maxOverzooming + 1; this.depthEpsilon = 1 / Math.pow(2, 16); + this.depthRboNeedsClear = true; + this.lineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); this.emptyProgramConfiguration = new ProgramConfiguration(); @@ -127,7 +130,7 @@ class Painter { if (this.style) { for (const layerId of this.style._order) { - this.style._layers[layerId].resize(gl); + this.style._layers[layerId].resize(); } } @@ -186,20 +189,6 @@ class Painter { viewportArray.emplaceBack(1, 1); this.viewportBuffer = context.createVertexBuffer(viewportArray); this.viewportVAO = new VertexArrayObject(); - - this.extTextureFilterAnisotropic = ( - gl.getExtension('EXT_texture_filter_anisotropic') || - gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || - gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic') - ); - if (this.extTextureFilterAnisotropic) { - this.extTextureFilterAnisotropicMax = gl.getParameter(this.extTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT); - } - - this.extTextureHalfFloat = gl.getExtension('OES_texture_half_float'); - if (this.extTextureHalfFloat) { - gl.getExtension('OES_texture_half_float_linear'); - } } /* @@ -290,8 +279,6 @@ class Painter { this.imageManager = style.imageManager; this.glyphManager = style.glyphManager; - const gl = this.context.gl; - for (const id in style.sourceCaches) { const sourceCache = this.style.sourceCaches[id]; if (sourceCache.used) { @@ -309,100 +296,41 @@ class Painter { updateTileMasks(visibleTiles, this.context); } - // 3D pass - // We first create a renderbuffer that we'll use to preserve depth - // results across 3D layers, then render each 3D layer to its own - // framebuffer/texture, which we'll use later in the translucent pass - // to render to the main framebuffer. By doing this before we render to - // the main framebuffer we won't have to do an expensive framebuffer - // restore mid-render pass. - // The most important distinction of the 3D pass is that we use the - // depth buffer in an entirely different way (to represent 3D space) - // than we do in the 2D pass (to preserve layer order). - this.renderPass = '3d'; + // Offscreen pass + // We first do all rendering that requires rendering to a separate + // framebuffer, and then save those for rendering back to the map + // later: in doing this we avoid doing expensive framebuffer restores. + this.renderPass = 'offscreen'; { - // We'll wait and only attach the depth renderbuffer if we think we're - // rendering something. - let first = true; - let sourceCache; let coords = []; + this.depthRboNeedsClear = true; for (let i = 0; i < layerIds.length; i++) { const layer = this.style._layers[layerIds[i]]; - if (!layer.has3DPass() || layer.isHidden(this.transform.zoom)) continue; + if (!layer.hasOffscreenPass() || layer.isHidden(this.transform.zoom)) continue; if (layer.source !== (sourceCache && sourceCache.id)) { sourceCache = this.style.sourceCaches[layer.source]; coords = []; if (sourceCache) { - this.clearStencil(); coords = sourceCache.getVisibleCoordinates(); + coords.reverse(); } - - coords.reverse(); } if (!coords.length) continue; - this._setup3DRenderbuffer(); - - let renderTarget = layer.viewportFrame; - if (!renderTarget) { - const texture = new Texture(this.context, {width: this.width, height: this.height, data: null}, gl.RGBA); - texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - - renderTarget = this.context.createFramebuffer(); - renderTarget.colorAttachment.set(texture.texture); - layer.viewportFrame = renderTarget; - } - - this.context.bindFramebuffer.set(renderTarget.framebuffer); - renderTarget.depthAttachment.set(this.depthRbo); - - if (first) { - this.context.clear({ depth: 1 }); - first = false; - } - this.renderLayer(this, (sourceCache: any), layer, coords); } + // Rebind the main framebuffer now that all offscreen layers + // have been rendered: this.context.bindFramebuffer.set(null); } - - - this.renderPass = 'hillshadeprepare'; - - { - let sourceCache; - let coords = []; - - for (let i = 0; i < layerIds.length; i++) { - const layer = this.style._layers[layerIds[i]]; - - if (layer.type !== 'hillshade' || layer.isHidden(this.transform.zoom)) continue; - - if (layer.source !== (sourceCache && sourceCache.id)) { - sourceCache = this.style.sourceCaches[layer.source]; - coords = []; - - if (sourceCache) { - this.clearStencil(); - coords = sourceCache.getVisibleCoordinates(); - } - - coords.reverse(); - } - - this.renderLayer(this, (sourceCache: any), layer, coords); - } - } - - // Clear buffers in preparation for drawing to the main framebuffer this.context.clear({ color: Color.transparent, depth: 1 }); @@ -484,7 +412,7 @@ class Painter { } } - _setup3DRenderbuffer(): void { + setupOffscreenDepthRenderbuffer(): void { const context = this.context; // All of the 3D textures will use the same depth renderbuffer. if (!this.depthRbo) { @@ -552,6 +480,21 @@ class Painter { return textures && textures.length > 0 ? textures.pop() : null; } + saveTileFramebuffer(fbo: Framebuffer) { + const framebuffers = this._tileFramebuffers[fbo.width]; + + if (!framebuffers) { + this._tileFramebuffers[fbo.width] = [fbo]; + } else { + framebuffers.push(fbo); + } + } + + getTileFramebuffer(size: number) { + const framebuffers = this._tileFramebuffers[size]; + return framebuffers && framebuffers.length > 0 ? framebuffers.pop() : null; + } + lineWidth(width: number) { this.context.lineWidth.set(util.clamp(width, this.lineWidthRange[0], this.lineWidthRange[1])); } diff --git a/src/source/raster_tile_source.js b/src/source/raster_tile_source.js index e2e1c0a5cae..c2764fcc068 100644 --- a/src/source/raster_tile_source.js +++ b/src/source/raster_tile_source.js @@ -109,8 +109,8 @@ class RasterTileSource extends Evented implements Source { tile.texture = new Texture(context, img, gl.RGBA); tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); - if (this.map.painter.extTextureFilterAnisotropic) { - gl.texParameterf(gl.TEXTURE_2D, this.map.painter.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this.map.painter.extTextureFilterAnisotropicMax); + if (context.extTextureFilterAnisotropic) { + gl.texParameterf(gl.TEXTURE_2D, context.extTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, context.extTextureFilterAnisotropicMax); } } gl.generateMipmap(gl.TEXTURE_2D); diff --git a/src/source/tile.js b/src/source/tile.js index cedcdd433b9..06219620845 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -35,6 +35,7 @@ import type Context from '../gl/context'; import type IndexBuffer from '../gl/index_buffer'; import type VertexBuffer from '../gl/vertex_buffer'; import type {OverscaledTileID} from './tile_id'; +import type Framebuffer from '../gl/framebuffer'; export type TileState = | 'loading' // Tile data is in the process of loading. @@ -86,14 +87,14 @@ class Tile { needsHillshadePrepare: ?boolean request: any; texture: any; - fbo: any; + fbo: ?Framebuffer; demTexture: ?Texture; refreshedUponExpiration: boolean; reloadCallback: any; justReloaded: boolean; /** - * @param {OverscaledTileID} tileID + * @param {OverscaledTileID} tileID * @param size */ constructor(tileID: OverscaledTileID, size: number) { diff --git a/src/style/style_layer.js b/src/style/style_layer.js index b7a4ff65ac1..40367d8a59d 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -197,11 +197,11 @@ class StyleLayer extends Evented { })); } - has3DPass() { + hasOffscreenPass() { return false; } - resize(gl: WebGLRenderingContext) { // eslint-disable-line + resize() { // eslint-disable-line // noop } } diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 112bda15fcc..5d37f516ecc 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -46,15 +46,13 @@ class FillExtrusionStyleLayer extends StyleLayer { return multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry); } - has3DPass() { + hasOffscreenPass() { return this.paint.get('fill-extrusion-opacity') !== 0 && this.visibility !== 'none'; } - resize(gl: WebGLRenderingContext) { + resize() { if (this.viewportFrame) { - const {colorAttachment, framebuffer} = this.viewportFrame; - gl.deleteTexture(colorAttachment.get()); - gl.deleteFramebuffer(framebuffer); + this.viewportFrame.destroy(); this.viewportFrame = null; } } diff --git a/src/style/style_layer/heatmap_style_layer.js b/src/style/style_layer/heatmap_style_layer.js index be9d887b7d5..57e3175b97b 100644 --- a/src/style/style_layer/heatmap_style_layer.js +++ b/src/style/style_layer/heatmap_style_layer.js @@ -17,7 +17,6 @@ import type {PaintProps} from './heatmap_style_layer_properties'; class HeatmapStyleLayer extends StyleLayer { - heatmapTexture: ?WebGLTexture; heatmapFbo: ?Framebuffer; colorRamp: RGBAImage; colorRampTexture: ?Texture; @@ -61,13 +60,9 @@ class HeatmapStyleLayer extends StyleLayer { this.colorRampTexture = null; } - resize(gl: WebGLRenderingContext) { - if (this.heatmapTexture) { - gl.deleteTexture(this.heatmapTexture); - this.heatmapTexture = null; - } + resize() { if (this.heatmapFbo) { - gl.deleteFramebuffer(this.heatmapFbo.framebuffer); + this.heatmapFbo.destroy(); this.heatmapFbo = null; } } @@ -79,6 +74,10 @@ class HeatmapStyleLayer extends StyleLayer { queryIntersectsFeature(): boolean { return false; } + + hasOffscreenPass() { + return this.paint.get('heatmap-opacity') !== 0 && this.visibility !== 'none'; + } } module.exports = HeatmapStyleLayer; diff --git a/src/style/style_layer/hillshade_style_layer.js b/src/style/style_layer/hillshade_style_layer.js index d0920f30a96..1e159202aaa 100644 --- a/src/style/style_layer/hillshade_style_layer.js +++ b/src/style/style_layer/hillshade_style_layer.js @@ -19,6 +19,10 @@ class HillshadeStyleLayer extends StyleLayer { constructor(layer: LayerSpecification) { super(layer, properties); } + + hasOffscreenPass() { + return this.paint.get('hillshade-exaggeration') !== 0 && this.visibility !== 'none'; + } } module.exports = HillshadeStyleLayer;