From b91fce01a05af108ffb8dde94bb4cbcaacdc46c8 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Wed, 22 Nov 2017 16:31:11 -0800 Subject: [PATCH 1/7] Create Framebuffer class, remove RenderTexture --- flow-typed/window.js | 3 +- src/gl/context.js | 16 +++++ src/gl/framebuffer.js | 23 +++++++ src/gl/state.js | 10 ++- src/gl/value.js | 52 +++++++++++++++- src/render/draw_fill_extrusion.js | 4 +- src/render/draw_heatmap.js | 10 +-- src/render/draw_hillshade.js | 18 +++--- src/render/painter.js | 40 ++++++------ src/render/render_texture.js | 61 ------------------- src/render/texture.js | 9 ++- src/style/style_layer.js | 4 +- .../style_layer/fill_extrusion_style_layer.js | 6 +- src/style/style_layer/heatmap_style_layer.js | 5 +- src/util/window.js | 1 + 15 files changed, 155 insertions(+), 107 deletions(-) create mode 100644 src/gl/framebuffer.js delete mode 100644 src/render/render_texture.js diff --git a/flow-typed/window.js b/flow-typed/window.js index 82e7e2522c6..33dcf650154 100644 --- a/flow-typed/window.js +++ b/flow-typed/window.js @@ -121,8 +121,9 @@ declare interface Window extends EventTarget, IDBEnvironment { Image: typeof Image; ImageData: typeof ImageData; URL: typeof URL; - webkitURL: typeof URL; URLSearchParams: typeof URLSearchParams; + WebGLFramebuffer: typeof WebGLFramebuffer; + webkitURL: typeof URL; WheelEvent: typeof WheelEvent; Worker: typeof Worker; XMLHttpRequest: typeof XMLHttpRequest; diff --git a/src/gl/context.js b/src/gl/context.js index 2afe6605a2c..876d8a4ece1 100644 --- a/src/gl/context.js +++ b/src/gl/context.js @@ -1,6 +1,7 @@ // @flow const IndexBuffer = require('./index_buffer'); const VertexBuffer = require('./vertex_buffer'); +const Framebuffer = require('./framebuffer'); const State = require('./state'); const { ClearColor, @@ -128,6 +129,21 @@ class Context { return new VertexBuffer(this, array, dynamicDraw); } + createRenderbuffer(storageFormat: number, width: number, height: number) { + const gl = this.gl; + + const rbo = gl.createRenderbuffer(); + this.bindRenderbuffer.set(rbo); + gl.renderbufferStorage(gl.RENDERBUFFER, storageFormat, width, height); + this.bindRenderbuffer.set(null); + + return rbo; + } + + createFramebuffer() { + return new Framebuffer(this); + } + clear({color, depth}: ClearArgs) { const gl = this.gl; let mask = 0; diff --git a/src/gl/framebuffer.js b/src/gl/framebuffer.js new file mode 100644 index 00000000000..93941c45a2a --- /dev/null +++ b/src/gl/framebuffer.js @@ -0,0 +1,23 @@ +// @flow +const State = require('./state'); +const { ColorAttachment, DepthAttachment } = require('./value'); + +import type Context from './context'; + +class Framebuffer { + context: Context; + framebuffer: WebGLFramebuffer; + colorAttachment: State; + depthAttachment: State; + + constructor(context: Context) { + this.context = context; + 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)); + } +} + +module.exports = Framebuffer; diff --git a/src/gl/state.js b/src/gl/state.js index cd591991c79..0a83042887d 100644 --- a/src/gl/state.js +++ b/src/gl/state.js @@ -17,11 +17,19 @@ class State { } set(t: T) { - if (!this.value.constructor.equal(this.current, t)) { + if (this.dirty || !this.value.constructor.equal(this.current, t)) { this.current = t; this.value.set(t); } } + + isDirty(): boolean { + return this.dirty; + } + + setDirty(): void { + this.dirty = true; + } } module.exports = State; diff --git a/src/gl/value.js b/src/gl/value.js index fb15aad3843..1f6751b8ea7 100644 --- a/src/gl/value.js +++ b/src/gl/value.js @@ -1,7 +1,9 @@ // @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 { @@ -15,7 +17,6 @@ import type { ViewportType, } from './types'; - export interface Value { context: Context; static default(context?: Context): T; @@ -25,9 +26,13 @@ export interface Value { class ContextValue { context: Context; + parent: ?any; - constructor(context: Context) { + constructor(context: Context, parent: ?any) { this.context = context; + if (parent) { + this.parent = parent; + } } static equal(a, b): boolean { @@ -324,6 +329,46 @@ class PixelStoreUnpackPremultiplyAlpha extends ContextValue 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 + // attachment point, but thus far MBGL only uses textures for color + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, v, 0); + } + + static equal(a, b): boolean { + return a === b; + } +} + +class DepthAttachment extends ContextValue 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 + // point, but thus far MBGL only uses renderbuffers for depth + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, v); + } + + static equal(a, b): boolean { + return a === b; + } +} + module.exports = { ClearColor, ClearDepth, @@ -352,4 +397,7 @@ module.exports = { BindVertexArrayOES, PixelStoreUnpack, PixelStoreUnpackPremultiplyAlpha, + + ColorAttachment, + DepthAttachment, }; diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index b001182157b..0dedaced459 100644 --- a/src/render/draw_fill_extrusion.js +++ b/src/render/draw_fill_extrusion.js @@ -48,8 +48,8 @@ function drawExtrusionTexture(painter, layer) { context.stencilTest.set(false); context.depthTest.set(false); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, renderedTexture.texture); + context.activeTexture.set(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, renderedTexture.colorAttachment.get()); gl.uniform1f(program.uniforms.u_opacity, layer.paint.get('fill-extrusion-opacity')); gl.uniform1i(program.uniforms.u_image, 0); diff --git a/src/render/draw_heatmap.js b/src/render/draw_heatmap.js index 0f1498c166a..537d3339480 100644 --- a/src/render/draw_heatmap.js +++ b/src/render/draw_heatmap.js @@ -82,7 +82,7 @@ function renderToTexture(context, painter, layer) { let texture = layer.heatmapTexture; let fbo = layer.heatmapFbo; - if (!texture) { + if (!texture || !fbo) { texture = layer.heatmapTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); @@ -90,13 +90,13 @@ function renderToTexture(context, painter, layer) { 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 = gl.createFramebuffer(); + fbo = layer.heatmapFbo = context.createFramebuffer(); bindTextureFramebuffer(context, painter, texture, fbo); } else { gl.bindTexture(gl.TEXTURE_2D, texture); - context.bindFramebuffer.set(fbo); + context.bindFramebuffer.set(fbo.framebuffer); } } @@ -106,12 +106,12 @@ function bindTextureFramebuffer(context, painter, texture, fbo) { 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.bindFramebuffer.set(fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + 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; + fbo.colorAttachment.setDirty(); bindTextureFramebuffer(context, painter, texture, fbo); } } diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index b2144a02219..c356e3fe89a 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -1,7 +1,6 @@ // @flow const Coordinate = require('../geo/coordinate'); const Texture = require('./texture'); -const RenderTexture = require('./render_texture'); const EXTENT = require('../data/extent'); const mat4 = require('@mapbox/gl-matrix').mat4; @@ -30,6 +29,8 @@ 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) { @@ -57,7 +58,8 @@ function renderHillshade(painter, tile, layer) { // for scaling the magnitude of a points slope by its latitude const latRange = getTileLatRange(painter, tile.tileID); context.activeTexture.set(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, tile.texture.texture); + + gl.bindTexture(gl.TEXTURE_2D, tile.texture.colorAttachment.get()); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); gl.uniform2fv(program.uniforms.u_latrange, latRange); @@ -125,10 +127,14 @@ function prepareHillshade(painter, tile) { context.activeTexture.set(gl.TEXTURE0); if (!tile.texture) { - tile.texture = new RenderTexture(painter, tileSize, tileSize); - } else { - tile.texture.bindFbo(); + const _texture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); + _texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + + tile.texture = context.createFramebuffer(); + tile.texture.colorAttachment.set(_texture.texture); } + + context.bindFramebuffer.set(tile.texture.framebuffer); context.viewport.set([0, 0, tileSize, tileSize]); const matrix = mat4.create(); @@ -149,8 +155,6 @@ function prepareHillshade(painter, tile) { vao.bind(context, program, buffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.length); - tile.texture.unbind(); - context.viewport.set([0, 0, painter.width, painter.height]); tile.needsHillshadePrepare = false; } } diff --git a/src/render/painter.js b/src/render/painter.js index 38b651f97f7..0f2c4e2bfed 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -14,7 +14,7 @@ const CrossTileSymbolIndex = require('../symbol/cross_tile_symbol_index'); const shaders = require('../shaders'); const Program = require('./program'); const Context = require('../gl/context'); -const RenderTexture = require('./render_texture'); +const Texture = require('./texture'); const updateTileMasks = require('./tile_mask'); const Color = require('../style-spec/util/color'); @@ -37,7 +37,6 @@ import type {OverscaledTileID} from '../source/tile_id'; import type Style from '../style/style'; import type StyleLayer from '../style/style_layer'; import type LineAtlas from './line_atlas'; -import type Texture from './texture'; import type ImageManager from './image_manager'; import type GlyphManager from './glyph_manager'; import type VertexBuffer from '../gl/vertex_buffer'; @@ -69,7 +68,6 @@ class Painter { width: number; height: number; depthRbo: WebGLRenderbuffer; - depthRboAttached: boolean; tileExtentBuffer: VertexBuffer; tileExtentVAO: VertexArrayObject; tileExtentPatternVAO: VertexArrayObject; @@ -292,7 +290,7 @@ class Painter { this.imageManager = style.imageManager; this.glyphManager = style.glyphManager; - const context = this.context; + const gl = this.context.gl; for (const id in style.sourceCaches) { const sourceCache = this.style.sourceCaches[id]; @@ -351,19 +349,28 @@ class Painter { this._setup3DRenderbuffer(); - const renderTarget = layer.viewportFrame || new RenderTexture(this); - layer.viewportFrame = renderTarget; - renderTarget.bindWithDepth(this.depthRbo); + 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) { - context.clear({ depth: 1 }); + this.context.clear({ depth: 1 }); first = false; } this.renderLayer(this, (sourceCache: any), layer, coords); - - renderTarget.unbind(); } + + this.context.bindFramebuffer.set(null); } @@ -397,7 +404,7 @@ class Painter { // Clear buffers in preparation for drawing to the main framebuffer - context.clear({ color: Color.transparent, depth: 1 }); + this.context.clear({ color: Color.transparent, depth: 1 }); this.showOverdrawInspector(options.showOverdrawInspector); @@ -477,19 +484,12 @@ class Painter { } } - _setup3DRenderbuffer() { + _setup3DRenderbuffer(): void { const context = this.context; // All of the 3D textures will use the same depth renderbuffer. if (!this.depthRbo) { - const gl = this.context.gl; - this.depthRbo = gl.createRenderbuffer(); - - context.bindRenderbuffer.set(this.depthRbo); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); - context.bindRenderbuffer.set(null); + this.depthRbo = context.createRenderbuffer(context.gl.DEPTH_COMPONENT16, this.width, this.height); } - - this.depthRboAttached = true; } renderLayer(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array) { diff --git a/src/render/render_texture.js b/src/render/render_texture.js deleted file mode 100644 index 1ff7133fdc0..00000000000 --- a/src/render/render_texture.js +++ /dev/null @@ -1,61 +0,0 @@ -// @flow - -import type Context from '../gl/context'; -import type VertexBuffer from '../gl/vertex_buffer'; -import type VertexArrayObject from './vertex_array_object'; - -import type Painter from './painter'; - -class RenderTexture { - context: Context; - texture: WebGLTexture; - fbo: WebGLFramebuffer; - buffer: VertexBuffer; - vao: VertexArrayObject; - attachedRbo: ?WebGLRenderbuffer; - - constructor(painter: Painter, height: ?number, width: ?number) { - const context = this.context = painter.context; - const gl = context.gl; - - const texture = this.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); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width || painter.width, height || painter.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - - gl.bindTexture(gl.TEXTURE_2D, null); - - const fbo = this.fbo = gl.createFramebuffer(); - context.bindFramebuffer.set(fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - } - - bindFbo() { - const context = this.context; - const gl = context.gl; - - gl.bindTexture(gl.TEXTURE_2D, null); - context.bindFramebuffer.set(this.fbo); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); - - } - - bindWithDepth(depthRbo: WebGLRenderbuffer) { - const context = this.context; - const gl = context.gl; - context.bindFramebuffer.set(this.fbo); - if (this.attachedRbo !== depthRbo) { - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRbo); - this.attachedRbo = depthRbo; - } - } - - unbind() { - this.context.bindFramebuffer.set(null); - } -} - -module.exports = RenderTexture; diff --git a/src/render/texture.js b/src/render/texture.js index d8542b7b52b..7c1eef5db96 100644 --- a/src/render/texture.js +++ b/src/render/texture.js @@ -18,10 +18,17 @@ export type TextureWrap = | $PropertyType | $PropertyType; +type EmptyImage = { + width: number, + height: number, + data: null +} + export type TextureImage = | RGBAImage | AlphaImage - | ImageTextureSource; + | ImageTextureSource + | EmptyImage; class Texture { context: Context; diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 50f3137e948..b7a4ff65ac1 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -15,7 +15,7 @@ const { import type {Bucket} from '../data/bucket'; import type Point from '@mapbox/point-geometry'; -import type RenderTexture from '../render/render_texture'; +import type Framebuffer from '../gl/framebuffer'; import type {FeatureFilter} from '../style-spec/feature_filter'; import type {TransitionParameters} from './properties'; import type EvaluationParameters from './evaluation_parameters'; @@ -42,7 +42,7 @@ class StyleLayer extends Evented { _transitioningPaint: Transitioning; +paint: mixed; - viewportFrame: ?RenderTexture; + viewportFrame: ?Framebuffer; _featureFilter: FeatureFilter; +queryRadius: (bucket: Bucket) => number; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index f042aa4095e..112bda15fcc 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -52,9 +52,9 @@ class FillExtrusionStyleLayer extends StyleLayer { resize(gl: WebGLRenderingContext) { if (this.viewportFrame) { - const {texture, fbo} = this.viewportFrame; - gl.deleteTexture(texture); - gl.deleteFramebuffer(fbo); + const {colorAttachment, framebuffer} = this.viewportFrame; + gl.deleteTexture(colorAttachment.get()); + gl.deleteFramebuffer(framebuffer); this.viewportFrame = null; } } diff --git a/src/style/style_layer/heatmap_style_layer.js b/src/style/style_layer/heatmap_style_layer.js index 2ea0fd5b83f..be9d887b7d5 100644 --- a/src/style/style_layer/heatmap_style_layer.js +++ b/src/style/style_layer/heatmap_style_layer.js @@ -12,12 +12,13 @@ const { } = require('../properties'); import type Texture from '../../render/texture'; +import type Framebuffer from '../../gl/framebuffer'; import type {PaintProps} from './heatmap_style_layer_properties'; class HeatmapStyleLayer extends StyleLayer { heatmapTexture: ?WebGLTexture; - heatmapFbo: ?WebGLFramebuffer; + heatmapFbo: ?Framebuffer; colorRamp: RGBAImage; colorRampTexture: ?Texture; @@ -66,7 +67,7 @@ class HeatmapStyleLayer extends StyleLayer { this.heatmapTexture = null; } if (this.heatmapFbo) { - gl.deleteFramebuffer(this.heatmapFbo); + gl.deleteFramebuffer(this.heatmapFbo.framebuffer); this.heatmapFbo = null; } } diff --git a/src/util/window.js b/src/util/window.js index 65862db169a..0fed6cfd0b3 100644 --- a/src/util/window.js +++ b/src/util/window.js @@ -57,6 +57,7 @@ function restore(): Window { window.ImageData = window.ImageData || function() { return false; }; window.ImageBitmap = window.ImageBitmap || function() { return false; }; + window.WebGLFramebuffer = window.WebGLFramebuffer || Object; util.extend(module.exports, window); return window; From 6669b08310b5b209fad2585fefa7e2cbf05b7083 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Mon, 4 Dec 2017 11:45:04 -0800 Subject: [PATCH 2/7] Tweak naming in draw_hillshade --- src/render/draw_hillshade.js | 14 +++++++------- src/source/tile.js | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index c356e3fe89a..ef466bc6ec4 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -59,7 +59,7 @@ function renderHillshade(painter, tile, layer) { const latRange = getTileLatRange(painter, tile.tileID); context.activeTexture.set(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, tile.texture.colorAttachment.get()); + gl.bindTexture(gl.TEXTURE_2D, tile.fbo.colorAttachment.get()); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, posMatrix); gl.uniform2fv(program.uniforms.u_latrange, latRange); @@ -126,15 +126,15 @@ function prepareHillshade(painter, tile) { } context.activeTexture.set(gl.TEXTURE0); - if (!tile.texture) { - const _texture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); - _texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); + if (!tile.fbo) { + const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); + renderTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - tile.texture = context.createFramebuffer(); - tile.texture.colorAttachment.set(_texture.texture); + tile.fbo = context.createFramebuffer(); + tile.fbo.colorAttachment.set(renderTexture.texture); } - context.bindFramebuffer.set(tile.texture.framebuffer); + context.bindFramebuffer.set(tile.fbo.framebuffer); context.viewport.set([0, 0, tileSize, tileSize]); const matrix = mat4.create(); diff --git a/src/source/tile.js b/src/source/tile.js index 345c5e72087..cedcdd433b9 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -86,6 +86,7 @@ class Tile { needsHillshadePrepare: ?boolean request: any; texture: any; + fbo: any; demTexture: ?Texture; refreshedUponExpiration: boolean; reloadCallback: any; From b8fcd75f67c320c11f1971de968c0a1438c05347 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Mon, 4 Dec 2017 17:29:33 -0800 Subject: [PATCH 3/7] Refactor all offscreen passes, move painter extensions into context, have framebuffers destroy their own attachments --- src/gl/context.js | 23 ++- src/gl/framebuffer.js | 21 ++- src/gl/value.js | 25 ++-- src/render/draw_fill_extrusion.js | 33 ++++- src/render/draw_heatmap.js | 135 +++++++++--------- src/render/draw_hillshade.js | 19 +-- src/render/painter.js | 125 +++++----------- src/source/raster_tile_source.js | 4 +- src/source/tile.js | 5 +- src/style/style_layer.js | 4 +- .../style_layer/fill_extrusion_style_layer.js | 8 +- src/style/style_layer/heatmap_style_layer.js | 13 +- .../style_layer/hillshade_style_layer.js | 4 + 13 files changed, 221 insertions(+), 198 deletions(-) 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; From 98b9169073896e39e94a16fa7bdc3a3d3321f4ed Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Mon, 4 Dec 2017 17:48:13 -0800 Subject: [PATCH 4/7] Use tile framebuffer caching for hillshades --- src/render/draw_hillshade.js | 3 ++- src/source/raster_dem_tile_source.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 2646a537809..58cc244168e 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -127,7 +127,8 @@ function prepareHillshade(painter, tile) { } context.activeTexture.set(gl.TEXTURE0); - let fbo = tile.fbo; + + if (!tile.fbo) tile.fbo = painter.getTileFramebuffer(tile.tileSize); if (!fbo) { const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); diff --git a/src/source/raster_dem_tile_source.js b/src/source/raster_dem_tile_source.js index 8e0ce4e7026..7d31c3ab836 100644 --- a/src/source/raster_dem_tile_source.js +++ b/src/source/raster_dem_tile_source.js @@ -116,6 +116,10 @@ class RasterDEMTileSource extends RasterTileSource implements Source { unloadTile(tile: Tile) { if (tile.demTexture) this.map.painter.saveTileTexture(tile.demTexture); + if (tile.fbo) { + this.map.painter.saveTileFramebuffer(tile.fbo); + tile.fbo = null; + } if (tile.dem) delete tile.dem; delete tile.neighboringTiles; From a87018b774b49d73eca6c0240ef9fa1191a0b822 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Tue, 5 Dec 2017 12:06:42 -0800 Subject: [PATCH 5/7] cleanups --- src/gl/framebuffer.js | 3 --- src/render/draw_hillshade.js | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gl/framebuffer.js b/src/gl/framebuffer.js index 7ed0b36c156..f63569c0843 100644 --- a/src/gl/framebuffer.js +++ b/src/gl/framebuffer.js @@ -33,9 +33,6 @@ class Framebuffer { 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... } } diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 58cc244168e..2b2e562ee77 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -129,6 +129,7 @@ function prepareHillshade(painter, tile) { context.activeTexture.set(gl.TEXTURE0); if (!tile.fbo) tile.fbo = painter.getTileFramebuffer(tile.tileSize); + let fbo = tile.fbo; if (!fbo) { const renderTexture = new Texture(context, {width: tileSize, height: tileSize, data: null}, gl.RGBA); From 6280f133c37a701fdd2ff78e1dbce67909ef1cb7 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Tue, 5 Dec 2017 12:49:56 -0800 Subject: [PATCH 6/7] Update heatmap test --- .../heatmap-radius/antimeridian/expected.png | Bin 10168 -> 10350 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/integration/render-tests/heatmap-radius/antimeridian/expected.png b/test/integration/render-tests/heatmap-radius/antimeridian/expected.png index 3644f9211cf5ef80ec2db6cd0b0651e270170769..4f09bd69d72f8182364883624045888eb7bbff60 100644 GIT binary patch literal 10350 zcmZX4c|26@`@b1OV;@7ZWEqB5d9s!mW8V@HAxmXs9V*$!E=wg_@rklT_Pvl@%VSCQ zEymz^C|kDd+xXrGpU>})-|HO5;F$BiuKRtxujQUdgKL^BjJ%9AG&C&QT55(gG;lb0 zjYrahtG$k3Fb$1Bqqdsz4KLX5PsE2<@~d})c8eP)H;^>mt#RM6y?;rgS#Esk3eCJQ ziVz;@tf3>6ZFcLK?dKJ6wGH;{Qv-HW_fIHI8cFoLDd?~3SoO~A6!AMrX@1|?oG~~d zGPgCowrP5BZSleRD1uX_1-FiJ17ZTfk{!wCV{j7lw(6oM*hvlon-pW&{xba0MTOms!TRO|>n)g-j zYl9<}I}5`uMSh<1a{cu+70FIxmgM&G#iIFN!=8f`tv27>zS%&>u-mYW@QMAvs>8QV z9mttqi_+gx^AUCGu6ynFF4}J6!7A@D#V~@diA@Lssipc_dM0M!T4fJ0E-U^Q z`JQWXEwPB`Nvv_Isaj8VYVaO$5!h)T_2~c4pZ~0n{Jsos1z)A&Sn4?&-!1EXIsfU* z!BQbWJ{cAV8NUJ`i)ve{xW-4XQ@7C0hgr=`DnP+p)8?WWT zU+K4}ikGBsoulQ)X%k}UmE-kMTASF+K%svgLYD9UqXow>tmZNf#W5MT8XK}y>7?|^ z$T_$a5slj0Q<9xr{)|}cER>E}RwmDRZ2WL7+OsMjQ*iN&CKopCsZV$mmp0s(uThZm zCf{&#Yd_QO5S^Q3^e`wCix;YYHvcn#YQ4!Bw`%^VcwyzLpysyMJw-u)dtoZ$`NjrL zX>@RVv)-}6ok*5%deIb zG`~;$h72t+V^{*Gs*dYJ$&ZcTJ5`*D4 zoqKD~d3&7Jwn*zfxH17j%Z){+x`4rDf48~-CB>{8Hm#4HTgyw7RnW+|7%S~z{E8iu zvprQq_2jhLw^MiuLK%(>jWNJ=$J{?4TEHcS|Dx8mZ517iX^v@r(ez>}t90HN!r?S# zOkNmvEh4v1tQ$h7|Hj8UuMum<(M5sEMBa_Tq+(YY>oLdL`u126RrTn5qe*OM} zTf>`C53sUT{3%8$Zr;oUiJ@r@bZY2GK4Ys;5*{rdYtn4>HNlx6a+3yj2Xpcd1DpYh z!m8cyc*MVgXdw(!6*lpPDz8zJkxaIeOHox#1qD!eL86FEfjmq#T=`kvv{(MCEq|(Q z?mrTce-*69nn-^6TkE%o#bXMUg-j-q+=jwj#KssjyO2Lx?ccUbi@(AHc{vS^6{gXP zccHB?r#@;#ZLi-F+|f%(RuB_V6__w!X{AHQ(JY@qp|t)*!~yM)pAj|Iw#?x()}*ws zqwx5isOpy}Js#&d=X!A9S_x-TAe(P5xnJE%olbnhqi2f>tEwS7Fh+5nI}*$N;I1nk`HktD^xE zz+0Sle4Dc^X}2Pa;-N#51lz~m0iCa z7)S$0Lu5XTy@uEI+*oQ2mLPH~FV0cRrp2bZ2`E|}P&7y(c66i2PVIx0fU4zn_1?9$ z?OWTw=I1L*{dS1Xt^qmoWY-jOMV6MbelH!*+iJwBh_)oMI>g0X>uH;LnfcOcHOa8NFRC|_ z?;QpKXE-gyP7Mn@j5i4p1Uhc>90LOZ6ZFivHNoN`P&i&ynJh(+GaOy=h@SH`vmW`S zV@|cT1OLyN_5S&L^W^`GQ4Pqh0Vj7V$A0{Nua;$MM2JXvt4qMMb&DDb(^;dkJLe#q_iveuMF2>n3Q4%5*@B((B5vA(<4a4{@FzGItzG07P2DsOy z%w!uB6=pNn1|D`unLRCd?E7xuRe`#vu9J58TU$ar*ZvqnMeB5`z1GJApO3x4qkqIj zS%A~fEHBQf#YW2}@7IUs56v&Yaj#Rad>$lfii?b^guuq&WuU#jJH+n?G!|^4t`muG zvrTKJ^$OYx^j5_H_6X+EtQM4_8e_GZ&Q}yHOiRe0d+O=d+d%m)6{gzY=y9n{q(J&v zwe*w!(!>7kWOg{aW3@NW_P#T|;3Y14VdA>q$yWJuQ?c$Ri&nH!vq=K87bo4mx}P;} zNw8?)Z*3714+=)Ww08?>o*)l%c!1o$i&`q^xES%FxF}+-PF`Gd3@`%}I)E{DyHlNt#U@w3;&W6R3M`SJ_^ndt%)G)cFKR;!sur3EYAVvb05|ZxqmGNSOO4sOBqc zg-vrn6eDJu2pxuKz7~>!;j5Bx7h6kQ)VDaMp*Y>VvgQ|+?SLnH zzd$Oi^lje(;3c7aG6w{?p?`f{ytgB8RL%7+@!tGfPdAsho~0h|JxXPJIeebTsO!hW z6xG<+X+dg<*t~Pn_19B*Ncm1}I+HkY!TH+CLuvF^yGhrX9gsPXdHd3<^9JTz%>)Jg z>H~_`mPhB7)7{APm&tCB5T~65&dxbsg}qV{DL&MqA?rW~J7P(|_T8Ax#kiO#=T^ut zXB#$$tG+IIemCbU^Y#`V)cArbV*~`JRWnH2UfwGs6>bru--o{re;-zN{WYStcz6ES z$lukkM!zP#mFrG6HChEM&x*jnvBK25uRS3R1l(qpf9gXYo%Jmf?id_I|E%YA5POD; z37>n0ZJ%B{xiC8MW^`gA@1n#UiR4A9-5K%dAL+AM;eBtcnuqB>Q_~~+5i3Cm;U{1* z46UIwN6__EgfNl&VOW$_i$)ni_rheH#m8@XccL(zm`;bzb{WsXdf(r2gZ{-28U{(; zYb5Xd`Jd(CEhr}-*K(z@rCA_8DX(ix<=(s8CBBt=FMFUg7@sEs!dwx8!uZ|4I!>&p z^FLxN9zXsFpSS#1t^unxef#Gajft{@=&{5e}EE*Fd3L2KPB7BMO%A~_A zR0WJuj0C`u(wkx=BR5!)4;3YOpswCw%E)8#iMTp_W&o(T>)rW#^LHuVt0Udy>YkQA zEq_)1H05pfG+I%e5vrX|h)i6uR{2!Xo884Ifcdh?{x3@AHZn$oXd-8>!c_e?xR9Q_ z?h(H{PHU z{h961`_^itID}+btnNeY3$3EY|D4SSvVJJ+3bMY}1}kza>Kw_6E}ora-F9-FE+InT zZTJjEQjHDrGSHTqOP;{*le&j&qQOQ z(~B@w!hGkkuz#P1-qnm0Vn}mQIf~XfWV(I@LD!Zr0r93POd(7$TmfnrK>^_C{0B4) zZu5XRMwEqTd7J=O7p)I~57D~@I;JfPk}td7qZ|f-aDU^KB^uV-FcxW4J@cjnKu-U= zQDe5Fnxed0BHX7U-gmE1LwA}}KB`kpWHnIWv#4R0r~;Jv@_`1|OLS6Fl0ibuk`9v& zy8%kU3xF<<`TbPjmksf72JBIFl@#*~iw!zYXiXs8Z#4$;66e?g`bVe=n7nKZx-3fh zqKr!=Z>6tJrds0k9YB_Djsa;WNJ_es%?^N@CY#BXdIXII9b1BtXpVI?!VjE%PU(0F z?XePCmf+C^l)+bXm9}Z;M^A7-f^52JhXGs+-NmnId2y8#(29LrkCsi)A zf)ZQz0#KJ0I)+I6p%%_TBP)bNUeMLE;iJ}Ns{@8n$LSmXWO zo7Yg7wmK~w5Lb>`r30msQRQmfg$L`ZS zl_;Gd+Ld=n`pMQs;?xD-YEEM;ghevdfbD0$3bTSnA2iWqi-W#iOUB@MnJ8aXjP67A zipeM6dL$i0WR#C+tOLg|5$=dEU{OLr^`_vqp_T;aT%DpjgC$ds+H-l673F-#e(rJU za#st9N`mTx0TfZCSz3`-p-QUmRe9v7`Yvpjb(a+adgVS13&8rTEl_E>mxgtm=2{wz zR!r0M?Y`>LpO)y-Zvp&xoIlA6nwHC!Zh1}i%IEJ!V7N-PBAH_`ppt6| z6cC2;1>(SUORTlDi;xGI>~F*VX$xfY=iRM?%ig98r!R z04d4>u(VYpkOl_45kVJhdK>DOloPd-pXlq4Gn@oZgX@pO6ar5{F>YVaY`WV|b8Q@P z?KX_bv=PqrF@i=Fdp`of8rw)hBT-6faCpmh=y@!L$4m>Z9ie|@8crpU%B(#d{}@Zv z*u>&@v3L{=&K%o_V&DWq%~cY}aVZEo!Gf`bO8h$TM_YjT|NnyaNh-ZTBMtpufpEst zX@SA?boK^t7M(O@2I*8d>@%Cr=9xG8nX2eGG5$C_`g8VSo2HbC5(>~j|H)NFQ^Phn zg?2fG(r943FYqDg2xWR^&ckAkqRhXjf*`o)1%i9Oq zH=#6EX?r?}E_iX8=m#MiTG`uhz7p7}l0Z6cQ#>sbNLIghr33lEL&w2dvi@!i>li;? z8+^ix4m8vsf=xhyoJP;fDTLqbW2)Ac=+W~zc=%1Nc*IeUAJ4G$egUANt4q7tqxa3x zAQ&u(!Lf31b}?EhpYx(CjJX)ZaKKHln2q46_Gw#^<4yXsKZ3x6fx!R~pSd#A2mCqv zATF<<6FJH(vuUieX@Q$BLLZ%~i(AONSq&MMnfyrQXi`Q}LHE2_6?TfX(w3VMi-a?F z;X~we9>WN6CUN z4kWqMw`#kbe>k?7{&38IftiF);i`O1r_|gfAAZdN@3R?wpfrcnSxk{@pc{H1!7e=UR%@8UJm zrj4Ax>SMYvby~%QLV`Fj>zvuS%bj4p9i6eW^&OgJD-CRIayyNc8 zsXq3Hp*R(OjHZ^df_5FeU7wCQEsz$99BFHM(nK~Kc-3Q^wf61kGPrlwnS@XZf!abu zs`9yML?Of_E-(p$=Hw~SR-JamD_UT3su1rEAOpJkfOQ13_Eq8h_=T8`6GzlCXGoLQ z;*YtTfq-LUB;Gta1u@_hrPX}K-J|&Z&VhTke&bRo(VE-K=^d^gCh6*eJJO^!Q39Vd zb3|5|IZny|~UTaxON>U*CnvWWzv4vkZr z2v3MbAnsHL%xAz*f2&14WoDv9K6+B&iqkt^QQ$#9dNIl&>^iMu=p)cR+Co?~T1QnS zkVZQV+))Y3Z7J4{-Kpbs^Q*8d3f2YUZ7A9^KKMHO%ar|R*ir=&k0&*MF@zSMzRkg;Es)`>G^ApK+2B&ueuZgrCJ8Zhv4X}=i{0=b~&0x(TCrcq&9g- z_&1+4CqTD_`*hA;Id!q2cxBHQJfcou(a#?^3v}b46^91y3cbJvsTuX^bXBwvNgGK4 zD-q@rchrOrGGd^?RDsFJ>TJuKRh?ofzZ6PyF0><)*$H-M$~x?}y*!js!G;aAXCt`y zy4W@EI!^U~?3Y7Q#~F<=tn6y(eWdH5yqw*j>hYRhbfI-_t+YoJMR;FKT z`iT1!W?s>bN5Dh%F{#sGE@0MssObLbG&;dOK+$!EGxdgB%~#FexaV|&jiiMM1KYdvH`%pP66Lrzii%z84K`*~H2Apa~Q}&G1W~fitjni_m$}HUPt}}swuA+MC zTisR8Tm~>rpx%A9*nWcULvSaUmxcP!3j*+|cNBO7LVcvykZbn4%Jj1KV zD?k0v>iD-_@pF??Q`*r|@?bX%+EN3|1piwwF*LWebg+>&uMnIgQy>)Q&+2qkHsvK+<`)s^gP8=vZuYS%2d}7LZRbNB(H5=9J&WoY_*MOe$E}n(kzA@(xFvKg;{Ryb1TR1PO1*(0u zezuhjhJj!>+{K6kalk_Z9s2d*!LC#dDj~?3GLxGuO^a!#z0IZz_I)LdS$E?+$`SO()*P*1hK`R3(&GcdG7@*7M>7jiyG1ZHjEJ_E)4f zd$`S4_L9Xq{rUv=)z99-!28tblY-1(J*bTSRR%xaH!(KdqCOEXv|z!|tp3jTv1AFx z99kCh@#D})79`$To?WCn0xSijxGEq@qM}PuKrBpH@29%&&RFkk?&;5w_Uk$~Hmn2} zK`ttucm;NQy?f@aLd6o4*51~DnhfMGC`}wS@ol2Y>=FAKfuWD^=fKXysk#>ngu+$t zb@>#71Tg33pd5B7WJsG?1KDRUmDD#_yh+mSSIB=;SE%>7^twmPH$ zFZvel8WgsSRA}V6)Cj|l5y`qFMpfiny%sgq3-PvPS1-i3J#%=NWO46md@pzrdA(^x z!M8rZa%In{v)Q+BO4TV~ZP+?*Vmc*UMt|+~K$6KzOI!DJml{6Zis6Kk$D$=H^vU`a zTUTV84r`@`Z}_D*#F9_+tteC|)~=F5G&ajPS$^{J$yZ?Fq@e%PV{W7(XQF7>`sIei zs}0|u9#6S%sW&w>P+L5OPlm=PAnE3X1Xth-A?AFAKW4Bha0n;^foGG7#dm_cgg$fi5X!c zy>C=wwI?IE9r|AkJWI*`<6#8USfaRP$M2_yNOMFBR(P{yxmfAXIa;ptOuBB3O10$3 z<6cZ3f;7k3y#l4~2XY|m$rXv4oMHAH!7-6x&KxY)#o7SmR~BN4t+o$eou61yaH`+4 zK`22n!-DGbEm5O%(MA??&P@AU54+j zk`;8ahx>1CL--%4I3oNnR3L?C*5k$*NsOIirbxK3)Ccjkq}Ua%x;J_A1`8V<^IVrJ zo?ML>j~91#-QDD%XU0TubqAd_U?8T6FW-9+6n;pQK$;_OyE5P52)0MQvX7_h7HcB_ z&?g9y=UZ%NNx1US0IMnTAa2axYGr;ZCAu;xdc&%7!+hucj#uuwrTfX*6@yNrx@&7m z3+2Ddf17(G7B)Ga4`KOL$<2HO`+{>wsfbpx7|qHj#~LPeH2ZU;C#xo_X4Z#$_M?3^ zPk+#Rli#k3-*x7{EEKGtFz-@htDEeG`b00(D zmnaqU|MID}gbE?L#SlYc!)nS2t&(P9Z6(!%TV8`cB*5#!shwNx8^wx(t`J2)nF>(^ zK=xahUAf;0J_b-|Ydlo<`$7$QwxWjEnotrd?rf9XU~~8ZiEK$R=kH@>UwDKjkmt`2 zp-nPaHQ6*@`kq(6XHQ%p$~PH2W4~PSyi|QsNO-eJ5L-}zy%m)o=D+JuLs=w7$X1J9 z7puhIilLQ$ra2Ddg~1S);K7R(#1muODyXoSNLfsTY+^A!HL%$EH zpHy268Bjk+s$GLpXsYGTd#8ZB=ZV{w`c_{2`rv40X_81O6UE+XP58}aLUsh$w0_Vh z9}Z?tPy>;gDA=d)ak(MW&v6u@=a7s+md~?@s`Qhq-0U`}SN!axGL{7KI}Yzd`%PXN zOYMZG%I+!Ek5-3DaK?U1$NCLb$3rWS< zvuin9pk(lx+w!uW+bUY~Sp`UOf|h{G)IJ|YD;${ouuA49G#Azuge#Ei3F3LHNHdp_Pz-Td8@4tEc6hnhrR44Pmn#`J-zhv( z_^!yIzszySvVvC|4Bu{*^_!N}l-d#+4+Ss$PV#;H(inG1DRXxP+VdaemCyXSEur6q zLO`I2p1)IGs6p`Mw?30NBP^llI({>>Y(0&H26uI=p8*x7FpN2+GU4^=!wnn&&qMsfL0zvtGH+ zDeeNZ-sa_*<^$#n<{mF^*cu;Dg6L_}n$RdKTe0~+iLQ}Ek?i=KOYnD)$oq0nLXqq+ z_931pEy!TW&3FQ&>-U{Lw&i^*;x+G0%J#Xo_IlR)xjfq-b*g12k$Xd;iqb%M&i-nK zCL05{K?-fU@P&UrW^;>@V4hbtP&V+W(+DVSiuq2F{QAPgfv<-STx+?Lzw^Vmxk%n) ztu((AlH&{c2{+oz+br9xbn|Qre-_#Hm*pFrFd-jM*P8oT>BS=kQ&G;(7+S+i#FWvVL2@B9oUNvV5;O>8&Z$Jh$`{0NTZ0Gq7y9=sQ^I z`38uvWpg^tbIm(%Di^2kpqHCao=}recLd#Bx9D;8{Dpiz>@MuPXH!Mv;bcjPJLF4P z%r2BoOj#Hr*eW*Fe_&0&eCARmdXgPQh8*+B3p&Oug|>w+>wZnk^sjLH{k9=(p}lJ~ zM;9-;Y@DpT^>sd>igI_vg&pW$1Pwe`pG?}@X;}UwSTekz;aD?lZe*@&scR1LV=7%< zf6Y55I>Q%Iu{Yt{m-?sDp}bKj^RB#4rm%bjsPG{*2Fwk`YZ%?hjs>!kx2a*JpN{Zi z$+v$aC9iS>2GN`sVkx_bjF7$^E*eJg)bs$kvaBzsE$?SxLis(%_TS!l9a5D_^TYME z@{87Yohw`B6TEBeMqOgb;(t7zj<^u+p*|`70HIHsU;{!#F-$aW1M4eVm1sD6*i!X}C-{( zetYH$<_*j*#wYhK@5#O#0YAK@ zaOyq00So-k(6AO?ox>mUpI>8tB<{5Hr=%=c1Yun}&>n>HZDKXqc+iY65JsZk30uR4 zE&lqQ`|EcYVR38&`^RU{^$(G}y+I))lUx^v3CSpl#Sl_sPEiYjZcF84eTcz6Kl@m3|Hs~Xj zCyPYh#Ms_-w&n{@!|Q*y%=$VX2u}0IgU|cy?}uq}T&DLI25-IQwflj`Brc#|kgI+5 z^(ALzD)-G&hQH3n$OJ6X5`Bi;nzB2FmM^S0Q)?&Rruiv&2|nxk?aO`u@fA<159oLt4(JI^Er!@=>Cq#J3Dq=TC__ zUFtA^(^QwUR)kknT~@1-ZQ%ULZ7-)*JMT0MgzD)^_}fjtYz2t&#J{}G_8;@EC%w(} z7fHUBxLQFCebZ$#5o((w59bdN59BN3w!56^9--(SW)LaK%KF~Y&|~#6D|6h@)+I^K2c5*|+#(}S_HG>1se5g9L(-P*T2Os|s-f!=wIW9#)j+m3Y?s%$b zJT34N`43bM(S%P{&6N>t>1`>~0iDfy7Rjpx+FjXSt_^(E{4(v-IsJGpbP&@pfI+wb zc+;9+{KpWv{%~;bDwzu{6;BpvE7P_dKzv$FnQnp0I1N>&o8VIR>od#TQHjfI)%(B? zF+Lh`+8ZflZGR+QVBI1ymGr8nHKTmed)WDvyE%Q(GSoDv+M!zW4<4fYd(dDfg%+Tb zN{=#4px$r!H zxcHbifcNyeB& zTQavSw(52I_eUHw%SQ@OkcvG5$4*w-&Ib+*1%A5gTXx@0_H9anFVxk58CjkBxrDu* z8J3`K`d|T58}{7db;LqTcLjUd);SixK6N00z`9?}S*JKpToxVfJz5r{9)sXdo>?{0tcYY~5c_*g?QqHzGz@RmYB0Qk9Mt(bZ*L>`=>+0rRTy?p~uRW z5LiS@Nupk2gfA4TJ!QRi&N$uKVx>i?C1&Z>h62QaAlhsuz}DM}apd;lBb<9m z1a&13sumfY7)fD1A*H_~!+9!kUHuemfBru20&o|rxf|5Z?7vi9ZPT0LTRRC0F;yCt zjA|+QkQuF9y_FVCdlpi%ajWuC!mzlxxJ_m8=gtx)cH+91^7^x_g1zvPc;7lSbm_O| zT1Q%}bCd{(XGU1Z&V}0RQo!~Db&7+HXDIcLTI+;U;`HO`)!Ajt$2LcFED8K%%O&UW#Z$isct zpGVZBZU!CXI%QJym^SKYPRhi(P=ig$`K?yTm%kvf>ZjOLy7{!?pZ$eNUd@DPG^V!X zRTm!*<_)$80Y(WFAky{}M7X#md17`s6k8uAIokIe{XNk~O(j$W`_HPq+w?q)}Kb5jg-Fn3S1v%QED6%BQqO~4l% zJTCLwUJq{iAexp#a~Y0b$f0#MRJx-&C@#0l$<~XqZ0SkF7kr8rci!pW-c16^ZoC?= zwxSaGlqQ%n5izfC9S~SEyxr7S`u(^U|)Ufp@Uj^aB=_KFC}fOooEEU z>p^y(h|pVi1))Lc;VYJ@Fl?N-6bm*^DU|`2Q%WDVIr^sIkutX@2b`hNeI%p;hRwC$ zc#(mUP^@q$GUHP&6?Q*d;qa{7XiD^gndWRxz#&rK_ARXZ?3>pl`})ioi;=X4OA^B0 zhPwCMzwwD;m3x5*JqvR-nAgjKjL=Q$n^>;-5<9hZLyUi586+fpn;}}y1sLq|b1VX$ z%bz^mht|U<#kQ(es75`nrtjA^w(PN4+x?FPo5kxHzrVE?9|1|ePvf@=BsO+7pC_7jn|n$`qg$C22XV|3V0sBWiTe#wj%RzI^Hsw*jL zEd};ok{y`WIs-P}2ogjX0RT5px^ei(mAG>!jSBW#+2||zl4rE7C(X&<;wk05I&O>Z zF0-}0A$>mU7*ZO#XU?p$T-S94-tQC9nU`vGMWwa%*FCn;Z;Wh454u*x2d9ZJ_y#KG z4H3r&zv=4Jw(JB-)+%S9x0{~Twj|#qCiS78z`KAaH@6`>n??Sq_VAJ@EEy`9F{Cxq z4A)?EAQ0XcB>=e*H_^zf1qzAzFUAY_DDNDWxEmtR9F0AiZum`7e@DSEo7?>@E%jVU zZw~Fp+o0+okBYUek8e;N=;CDl#fd7yaBi%+E6B2p8@&JN`|6aa7Jr%L$3t(<_5FqK z_FJ+l`ntRM(;|?%`-NAY&;hCcX2*s;shj}zhTkg?zTj8?y{TtA?U>7fJ~Akb%qR|v z%e|5ZZW34X3i)3$7GbyseZ-JC5fBfJ=ag#N@pB&ge2A?EspHAv|L@^Ywqz^mmV$3B zD(+URucL9^?e>oU%a+Obu+Xc74!~*f=Txp8UVNqhA zOQhJ)9%l3ZF=KH<{5|g>H$d;iaw)bxc-Q0LdlrHzohsAb7tA|p?HF=j{E9>&{|o+? zmOUlIHZ&VrAJBL~$#AR*GIIt7q{1l`$ND|?W|%_5(R{J`$iO9L*F|zI)M|?wv%{oe1l{a475uW!3jfGrWZUgf5)M0gs^zuN=MhnSNm<>z1O& z^EDL4l<(i&^z8cKzkHd6T-BQ1R^?;jX5VS3Cn3&|HgJ#1w&vC40I}qio8M2)<0aQ| zxmElg8v0MK7Bg9t)|U05te+}uGZTG zUM$it%t(H{A?@Y;fhtvd$z<`1EBnySX~FQ18Td(6a67*OG*YShW8yjRK1SZK&*2}~ z3J@zn@CTnQKOG5g5icy6WeD14?eZY(=l*T=x&7Ohe?mj<>lsO0vFu@pL@I_eC2rTM zjUfyhF(4G8-aRc-E5l`9%7;8vS5Udq-|6?%uO^XL#y5Q2i#Mc;u8o*V4Led9>@HEF z%O5P>E4-3bsao9zl1s%f-u#xSQitsDi?ijpDNWNiyJhAd|JF;=_ZnqW?(!*lrPE-^ zu;hP%AIExktz@krCMT@KN~l^ct2N0lTUCDq58cMM>c8?`haN5jR>FX*Yb-usFTXdM#i9j5o1y7eI}{wA_5be=1(vlWTEYEC7jLRw=XE=XyFj5tevm=BlPA z&tkWsC+qbkri*DV9m4b{%Qs&ozocRU#6YtUAa-V8%VdzMi(1LD=Cf_p;B1lS3C_>9 z*Q;gW`)eyaT&%BXX>BQOHPb0=`K?el{FH5{jh@9e!llhiA{J4iD-SP0>~l60H?)Nv zV{_>?bO*AKUjrt%t-cLt--Zo({oD_psci3Bxh(enen}wkAn6H0C>L9ssd9lB!XRe( zOq^jq4i@U&e>Lc-Vt=OW z_nGc9pR8w{)hUfY)L^If&(x;x8Myxza}y@yOS$4_L(?3K{2r!#JLz>8GpPfn_q zsuqdRwGdM5nfzS2fLc@zsg>a!EN zS+Ja$Cij#|*@o=#Mdv-w2A}mhJCL=K1;q1&4uk>{OuFq|-^)CQR+4;u4dV~)m8PSn z-}z5#ypnZ#C7WsMF_7Mxa>f*NS3ge9pZvkNsV)KcrwhL^J1%`p*1FJqO-0w}SvwA- z%9h;1k{J-GyEg{sCHi9#{NrL64yF7EK6PjV?`GbPZ4=j%CN^K_+Hy2b3W(!$d5<8; z$nt?t9}6+syvP7l1{}POoWp76fgNt`c0kXvZ>=8MHdnr|GsJ`a|8m&XCP4mrhuL0O z9(u`^abgmpRJDn`B>J!z{f?e>BmZejLgyjBa2joTzzvLlrb^s9D2W=@su87E-Uhi$7Bhu>$oSMAZ6DRkkz zMjCPl$?2G}9<|Q)E84dVkgsH$;cPM}FLMzZFMK7=_=L68-j{>y)(!%n9|Eu}iERS< z8d%064s|!Ni*UNK*RS@C`yaT$#IeMTDv~>9f;20V1RltC3}{gpY6QI_pAs!QT%G9K zao4%sKu~gzrfI3@l)Jt%Q4LIjk)S-m!49e#L6M?Y;($1yhTPrEs3sl^M52jf2@=Yr ze2In6R}$>jJInY+k4wxPH<*;w_7ROTXSiJo&0C~ouLu~PkP^6e6cl2TMl#Y|!)b|N zMWrdMf_rf(B)}|FVEHO#{hkR|oMoi2lmHhiC*z5LUJ#cN$^!Z9IPO`xoT?e7mNb=>u}Wq-6gNBTX?$#u9R?XGVM zlsTFS!py7M0_9?T(Uc`{14M$Z(ti4^=k#*@d&36<1` zf^(%dLAmw=d)BDJ@twE?;klsCBI=pHb!GREx?h(o!E2x3E5hF7rA7yErrg-=!%jV2ItF>qZ_4@I|DGj;8Jyb+$HWJw z5nL^jEbO~qCEUa$GK9nNWht|W1KVQJir~1xEPn&+T)twx=zP8yqY#Y>>}J<4jQ}cw zT1j%{F>@6e2F>KG$&I>e7VXtMdFM<_9t8BlMpK}XoJdwqfDL{$0;k;#ibAAleblS& zm;|mPO^_o^T)u!e=wC@b*-^&{VVL;GAqbq1Rs>7MxLAFZCWd1%e;;T&K!0|amqjwL3GaaN0QrhVi3V0{{%9M`+q{g>G10jHjl`M6CN zNv@nCozZi|2b3bs*%VgJJ$>Hhf)yDOO+{ANOgN9`Gx4dHMqLEEA=;l04&?(Kw3rKg7upk-+rN!*qgK#UtB44xwwgZa}(9V(2c5SyT(izs7 z%~?1XC{KDApog@IX3QJ?ab#RfP!IiJp^Gw0ngQME15#f|kB_Vmnh;O2 z$5yP3fX2^9Hsn8ZpIdzACP>70Y1$qo<~!YM3;mQebn(UjsnT5$Tj9RAM(Mb?c5#-Z z9a6WEbSBWdXp8Cij&|(A0Mro8riLBW4g~$V-@;>Orhcsil z1-4{EE+lC2A*%eP5|*%rG9}3H?J4O_g`Ykt^%h#XoLz)3BHGdv8$`EXJV&=!%vrNo zG#;c<6dcP#6gN=I6!+J51p<7OZXHjyig9xct7}1J_dheB!z7G5*l3)eX^VOTfv|w6 zQu>b-jsdv=2?}8hayJi95A1NSbFX_-!4VEi1D+9wFdRTQBVd-5kCd8SC<-D50YWJq zDEGx8&0(G4@(^V($Rw;?93?+QIbcBqXzvw_pU@oIUOK35QSBY*j9<0Ra6HOk0|k!r zuXX^dYao(~Q4ZDi)tS9IMYBVS)lnHs?1U7tdVQXnNP?C0BzG|Um{6>BQ`HyE6hQDL zrvIdg9SPtAAW-jN$CItVs!EfsBnnXP2+u052v!WtY>G?F1P04NCd{Kbw4CSX1_89{ zN*cPDJP_tGJ4Xe7IWT0=K`GJ+h^2tIeO5Qi`hIuR9v)=6*(~Q05cDl)At^*YB#2)8 zJQc-QDC5Wmr44j3L4kCTp{s@3Zy{}UcMHY5qJrAJ>(9C(4f_WsJ=6?N-++4v#U9mX ziP4v^w3Y_}2TW9bH`YK!!)#Hbud@j#{>=%f?&&|X6jBOSG0u=lDzWCB z!kQpz`~a2v*p0h*Xe?);m3PBY1BmgOpssPUrdlqD%-xc=8#@6yB(~iSi6r7$fGW^4 zAZboKEdwjC0sFB8B!p&j1!1j8fr;hokQ_FM)r4~L+|%uK>M!i#Z_MJa<=ilmHo1HQ z?crMfGN@OSlM8B+jT$8#bad$DQvcg>@UU6QV;0vg_Ppu!DI(9HGHVZpkpc;s^f~G6 z^`2kEZ)C71qjSBeOrMHTpG1ldwKYxiBQqgo0P_fym|5mva`%?d;eOnCk=kzd>tOmN z=u%i>at><5yj-XHdj!mmbU;Msv#oPCWMkPH-*0;E2vET?tRxZ{LOM{>q=1Rg^h7oU z_=CNkquvQCVr&EDr7M9(KC?*nSxnxbkk4RAy>n)Csk&J|&S*=q-gsL1OO3xP;D^S| zSZu`97NXIvHo6(~i)CQJfPR6-s~c_L8_sOZ6*qtjE_k5Ib?ZfGlD)U@hrx_`n8KQ@ zbAee4jQa=sqf&4YD9K86{=t+4vhfJ+w8`| zIa2IT&mHcG13TWKcd>8~#}fy7k?ui*RIuOcR5S_Oh$1m$)DuSPB`Y|BOFk5zVzSvF zm8QV(=`{-XS$MqqN;R?Rz%AsFk2pi#Qi!35iuX`m&q2x+<^LWTWc?}M zD#QgYagV&?357Q_0dL&_Q6Vgmc*=kj>3WU?9)aUyA`Z&Tc2Q{PbrgX%XWWPaj>Awe zkt_w^Q)%buX7q=&58x7({YGsXnDoW^dG6IW@Sj3FuM_!VTH;Ri06$=S0z1EUkyeW( zDE#_4w7p*N>sQ?%BCyji_vHqdLCl`~`t+lH;76YCa%!bWIq#Uy-_2Q0tJft4&M49a zv*n~goh^CL#0Y|H;dlG(v1$yt4sy^t(Rv0<03N7lSs}q;sT^o&Hz2?pDGMo-Ve-f? zVlRP=A~-CP%eQ};fOuxCVWBn+MFSbCn7jZiv| z_UuE7@#27EW=O>X7-G(ck!&RvNv-K)$)u!aad0Ipm>1DHgU?WWNGgi6gh0HdSUQ_% zOkQz4epsaPQ@*zvE`_ls_iXi{NEzs>w^&QAj4F+MXyD94#3OMSn ztBxxaMYU7j+W*9&!3MiZ3`;DI5|U}aJitH_3{e*!Qp!Vs{pWy-n~cl04D{etKGNSS zmyOOr6B=3J^0P-rXGbr)i~`qJ7(T>s)1Q9?jofXSn7H&AmybuHp33U@P2Mlm5qi_P z^~V*yZ4YTjw7LJ&@~CRv+f<;k7OOn)OR6<4`=6C{>JdVmYT=Z^%KEJY(*%LTvsZc5 zI3;m70cJs{);p-+DAG1XwvhRYelvzue4O{lI8)l#RA_wOduXJmkXlbloBN{^585VG z-5!6FOFsN{Qp4>r;!1kK+58Or%*(osTLh=+hW@#Qk=NK!e64NB+l>r*Mi=th*Xav( z_7Ckm%4psx32YkSRh|BV1_<%wwu$9%GyRo#q=re6yTim$o&56Ww~KC9(+4s0pGp0z z2UJf_&q;@b*^uvLo}N9TssYf)Bd+=iwI!$>5xQWV8L|k{ z(cHrAG3xdLDwEn^yHVVrydH)o)cK6zN&O+_*PHE83F}{Q7CNFi>kh8EQ9~l~cxxn?!dv;FQIF130$eXLU9cJ&cSh;O8{cA8dJdkv%uJSdtfw=H> zx}kVGW3aj_qw59rg5rr`wQvK;zo`9ThuszWF6c9}KbbC_mZD2aNw>EDQ*x{8v#R!n z!q4ARp``TNfnlH9w-3OdNeObQuzYLo%2L*J(~`b^5gFJm=FX-aadmvylU*d@%=l$> z=J#4SCO~w3%OS(|Xi3%{IT35Q5hG+qhnl0@C!+{ijs^Pzq@az9RP_2=iuU4{%8k0Y zpSGki!0#NkZlQd;p^jWJ@;cyEz$@FYVM%svmDP1L?-MYt;Y+{I?WsCbP)fF?(f9%^ z&9mTay8Xk#<2A`TvA2|egc^4apS(Kx>tF1s7~zoW)yXF=69-h;jh5oyi4Td$1rJ}T zlD=lhQ<#V{P477DX^>>Zh{Sw>l+c^3tc{()_mpFx(Pudg6LY})&QaAXn9Ei=^KXoF>y!6c@a&AwYFrN1a>ZWnTM)ILy&-G z_d*!Be>IAdJtO&c1V2Hk1AvHi3$MF?h)HwKV`#m`jjbKZ^lBXt1R-bx`~t;#(IZEC zAe9QtL##X*on&{HYJ$FzsVK@C2b=?g_^lym5c%2NU`2i>%J<kWD-WAG{F{LpP+WKFeV-{tU@(6?2a~GtQgd%F^BhVQpm1 za-FLGEMdTo9FpWTe8mM((`ePOe~q7gSxll&{)fD-9hS?Rt-u?_sr)IM3X|Kq~%6IL$;>=1-JjtZcal)#TcgTDy|k%n;>)u-R{!KS5NL@5(le!jJ(u zI2rcwky>kYy^M#L!pi(VPAq|xGzv6L z!EG7uKp%BjoQcM5mfeC1>|Ans1^<>vy!pvh29TPwP)%&zIqFp0qx-EQl(dv3@aW&O zYVYqeMQ%dkRG^_;cOLuQlaONiddbb#efYiN^H;>;<8`mLk9aBPpzFw^VXiK7)-IVp zv2)Igm2(wD0`P_H2B6Z4ueE-&3{N0}50+Jy*ZOTx22s@8H^iJYIH9i5Ma=j1ALI639GtNFekWr}>WHZ-~>rNmT1_bR%MKiV>0QblrlVRO(_ojV{b)V) zq?~Gu8}AcoDHgTn)MXadAtV#?>ZkO<@BFW-60fJw{G3h&47-#jVf_afXRRe}6kh~c zY+x@Vlu(HvAEScEMBBN*3Mb;?0=j&`{F)0Mv^dJ^7iWCYVzy4{yECT0AX8X z7o0{YeM@c1q8ORqV@Ev)gR zOj~=qfa+CrGVLy9-bPzxM!)a)V^Ie zMqX0uQ0;fqmjkh$9VaKL_VD)A1l!4csRx__@BHOHwcpwYSCtdDQDyIuzVi7qElyQ3 k@>J@c*=>f;H9~$RivHL-@0kmJk;i0eY++Psa4GWt03%K7asU7T From 3a5a0aa833f003aa7039775ba0f22adf13ea2128 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Wed, 6 Dec 2017 17:31:52 -0800 Subject: [PATCH 7/7] Address review comments --- src/render/draw_fill_extrusion.js | 17 +++++++---------- src/render/draw_hillshade.js | 1 - src/render/painter.js | 18 ------------------ src/source/raster_dem_tile_source.js | 4 ++-- src/style/style_layer.js | 4 +--- .../style_layer/fill_extrusion_style_layer.js | 2 ++ 6 files changed, 12 insertions(+), 34 deletions(-) diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index ff762720b44..f52c02244f7 100644 --- a/src/render/draw_fill_extrusion.js +++ b/src/render/draw_fill_extrusion.js @@ -22,15 +22,7 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa } if (painter.renderPass === 'offscreen') { - const context = painter.context; - - setupFramebuffer(painter, layer); - - context.stencilTest.set(false); - context.depthTest.set(true); - - context.clear({ color: Color.transparent }); - context.depthMask.set(true); + drawToExtrusionFramebuffer(painter, layer); for (let i = 0; i < coords.length; i++) { drawExtrusion(painter, source, layer, coords[i]); @@ -40,7 +32,7 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa } } -function setupFramebuffer(painter, layer) { +function drawToExtrusionFramebuffer(painter, layer) { const context = painter.context; const gl = context.gl; @@ -66,6 +58,11 @@ function setupFramebuffer(painter, layer) { painter.depthRboNeedsClear = false; } + context.stencilTest.set(false); + context.depthTest.set(true); + + context.clear({ color: Color.transparent }); + context.depthMask.set(true); } function drawExtrusionTexture(painter, layer) { diff --git a/src/render/draw_hillshade.js b/src/render/draw_hillshade.js index 2b2e562ee77..170fe45bdf4 100644 --- a/src/render/draw_hillshade.js +++ b/src/render/draw_hillshade.js @@ -128,7 +128,6 @@ function prepareHillshade(painter, tile) { context.activeTexture.set(gl.TEXTURE0); - if (!tile.fbo) tile.fbo = painter.getTileFramebuffer(tile.tileSize); let fbo = tile.fbo; if (!fbo) { diff --git a/src/render/painter.js b/src/render/painter.js index af29c8f5837..ea06cc2d1ae 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -40,7 +40,6 @@ 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 = 'offscreen' | 'opaque' | 'translucent'; @@ -62,7 +61,6 @@ class Painter { context: Context; transform: Transform; _tileTextures: { [number]: Array }; - _tileFramebuffers: { [number]: Array }; numSublayers: number; depthEpsilon: number; lineWidthRange: [number, number]; @@ -99,7 +97,6 @@ class Painter { this.context = new Context(gl); this.transform = transform; this._tileTextures = {}; - this._tileFramebuffers = {}; this.setup(); @@ -480,21 +477,6 @@ 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_dem_tile_source.js b/src/source/raster_dem_tile_source.js index 7d31c3ab836..225e73faa4f 100644 --- a/src/source/raster_dem_tile_source.js +++ b/src/source/raster_dem_tile_source.js @@ -117,8 +117,8 @@ class RasterDEMTileSource extends RasterTileSource implements Source { unloadTile(tile: Tile) { if (tile.demTexture) this.map.painter.saveTileTexture(tile.demTexture); if (tile.fbo) { - this.map.painter.saveTileFramebuffer(tile.fbo); - tile.fbo = null; + tile.fbo.destroy(); + delete tile.fbo; } if (tile.dem) delete tile.dem; delete tile.neighboringTiles; diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 40367d8a59d..fa794c325f2 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -15,7 +15,6 @@ const { import type {Bucket} from '../data/bucket'; import type Point from '@mapbox/point-geometry'; -import type Framebuffer from '../gl/framebuffer'; import type {FeatureFilter} from '../style-spec/feature_filter'; import type {TransitionParameters} from './properties'; import type EvaluationParameters from './evaluation_parameters'; @@ -42,7 +41,6 @@ class StyleLayer extends Evented { _transitioningPaint: Transitioning; +paint: mixed; - viewportFrame: ?Framebuffer; _featureFilter: FeatureFilter; +queryRadius: (bucket: Bucket) => number; @@ -201,7 +199,7 @@ class StyleLayer extends Evented { return false; } - resize() { // eslint-disable-line + resize() { // noop } } diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 5d37f516ecc..b31c8ac5b64 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -15,11 +15,13 @@ const { import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; import type {PaintProps} from './fill_extrusion_style_layer_properties'; +import type Framebuffer from '../../gl/framebuffer'; class FillExtrusionStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; _transitioningPaint: Transitioning; paint: PossiblyEvaluated; + viewportFrame: ?Framebuffer; constructor(layer: LayerSpecification) { super(layer, properties);