diff --git a/src/data/bucket.js b/src/data/bucket.js index bb1e03005c7..e467442a1f6 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -7,6 +7,7 @@ import type Style from '../style/style'; import type StyleLayer from '../style/style_layer'; import type FeatureIndex from './feature_index'; import type {Serialized} from '../util/web_worker_transfer'; +import type Context from '../gl/context'; export type BucketParameters = { index: number, @@ -58,7 +59,7 @@ export interface Bucket { populate(features: Array, options: PopulateParameters): void; isEmpty(): boolean; - upload(gl: WebGLRenderingContext): void; + upload(context: Context): void; uploaded: boolean; /** diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index 764e54cf16d..71ad8f5fe01 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -1,8 +1,6 @@ // @flow const {SegmentVector} = require('../segment'); -const VertexBuffer = require('../../gl/vertex_buffer'); -const IndexBuffer = require('../../gl/index_buffer'); const {ProgramConfigurationSet} = require('../program_configuration'); const createVertexArrayType = require('../vertex_array_type'); const {TriangleIndexArray} = require('../index_array_type'); @@ -19,6 +17,9 @@ import type { import type {ProgramInterface} from '../program_configuration'; import type StyleLayer from '../../style/style_layer'; import type {StructArray} from '../../util/struct_array'; +import type Context from '../../gl/context'; +import type IndexBuffer from '../../gl/index_buffer'; +import type VertexBuffer from '../../gl/vertex_buffer'; import type Point from '@mapbox/point-geometry'; const circleInterface = { @@ -100,10 +101,10 @@ class CircleBucket implements Bucket { return this.layoutVertexArray.length === 0; } - upload(gl: WebGLRenderingContext) { - this.layoutVertexBuffer = new VertexBuffer(gl, this.layoutVertexArray); - this.indexBuffer = new IndexBuffer(gl, this.indexArray); - this.programConfigurations.upload(gl); + upload(context: Context) { + this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray); + this.indexBuffer = context.createIndexBuffer(this.indexArray); + this.programConfigurations.upload(context); } destroy() { diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 65bb461a631..d7352939807 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -1,8 +1,6 @@ // @flow const {SegmentVector} = require('../segment'); -const VertexBuffer = require('../../gl/vertex_buffer'); -const IndexBuffer = require('../../gl/index_buffer'); const {ProgramConfigurationSet} = require('../program_configuration'); const createVertexArrayType = require('../vertex_array_type'); const {LineIndexArray, TriangleIndexArray} = require('../index_array_type'); @@ -22,6 +20,9 @@ import type { import type {ProgramInterface} from '../program_configuration'; import type StyleLayer from '../../style/style_layer'; import type {StructArray} from '../../util/struct_array'; +import type Context from '../../gl/context'; +import type IndexBuffer from '../../gl/index_buffer'; +import type VertexBuffer from '../../gl/vertex_buffer'; import type Point from '@mapbox/point-geometry'; const fillInterface = { @@ -92,11 +93,11 @@ class FillBucket implements Bucket { return this.layoutVertexArray.length === 0; } - upload(gl: WebGLRenderingContext) { - this.layoutVertexBuffer = new VertexBuffer(gl, this.layoutVertexArray); - this.indexBuffer = new IndexBuffer(gl, this.indexArray); - this.indexBuffer2 = new IndexBuffer(gl, this.indexArray2); - this.programConfigurations.upload(gl); + upload(context: Context) { + this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray); + this.indexBuffer = context.createIndexBuffer(this.indexArray); + this.indexBuffer2 = context.createIndexBuffer(this.indexArray2); + this.programConfigurations.upload(context); } destroy() { diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index f75a6373932..7483f26681f 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -1,8 +1,6 @@ // @flow const {SegmentVector, MAX_VERTEX_ARRAY_LENGTH} = require('../segment'); -const VertexBuffer = require('../../gl/vertex_buffer'); -const IndexBuffer = require('../../gl/index_buffer'); const {ProgramConfigurationSet} = require('../program_configuration'); const createVertexArrayType = require('../vertex_array_type'); const {TriangleIndexArray} = require('../index_array_type'); @@ -23,6 +21,9 @@ import type { import type {ProgramInterface} from '../program_configuration'; import type StyleLayer from '../../style/style_layer'; import type {StructArray} from '../../util/struct_array'; +import type Context from '../../gl/context'; +import type IndexBuffer from '../../gl/index_buffer'; +import type VertexBuffer from '../../gl/vertex_buffer'; import type Point from '@mapbox/point-geometry'; const fillExtrusionInterface = { @@ -105,10 +106,10 @@ class FillExtrusionBucket implements Bucket { return this.layoutVertexArray.length === 0; } - upload(gl: WebGLRenderingContext) { - this.layoutVertexBuffer = new VertexBuffer(gl, this.layoutVertexArray); - this.indexBuffer = new IndexBuffer(gl, this.indexArray); - this.programConfigurations.upload(gl); + upload(context: Context) { + this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray); + this.indexBuffer = context.createIndexBuffer(this.indexArray); + this.programConfigurations.upload(context); } destroy() { diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 80708b0ae55..7207834ea54 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -1,8 +1,6 @@ // @flow const {SegmentVector} = require('../segment'); -const VertexBuffer = require('../../gl/vertex_buffer'); -const IndexBuffer = require('../../gl/index_buffer'); const {ProgramConfigurationSet} = require('../program_configuration'); const createVertexArrayType = require('../vertex_array_type'); const {TriangleIndexArray} = require('../index_array_type'); @@ -21,6 +19,9 @@ import type {ProgramInterface} from '../program_configuration'; import type LineStyleLayer from '../../style/style_layer/line_style_layer'; import type Point from '@mapbox/point-geometry'; import type {Segment} from '../segment'; +import type Context from '../../gl/context'; +import type IndexBuffer from '../../gl/index_buffer'; +import type VertexBuffer from '../../gl/vertex_buffer'; import type {StructArray} from '../../util/struct_array'; // NOTE ON EXTRUDE SCALE: @@ -149,10 +150,10 @@ class LineBucket implements Bucket { return this.layoutVertexArray.length === 0; } - upload(gl: WebGLRenderingContext) { - this.layoutVertexBuffer = new VertexBuffer(gl, this.layoutVertexArray); - this.indexBuffer = new IndexBuffer(gl, this.indexArray); - this.programConfigurations.upload(gl); + upload(context: Context) { + this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray); + this.indexBuffer = context.createIndexBuffer(this.indexArray); + this.programConfigurations.upload(context); } destroy() { diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index a1e1c516b06..1fdc6cb5d0d 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -2,8 +2,6 @@ const Point = require('@mapbox/point-geometry'); const {SegmentVector} = require('../segment'); -const VertexBuffer = require('../../gl/vertex_buffer'); -const IndexBuffer = require('../../gl/index_buffer'); const {ProgramConfigurationSet} = require('../program_configuration'); const createVertexArrayType = require('../vertex_array_type'); const {TriangleIndexArray, LineIndexArray} = require('../index_array_type'); @@ -30,6 +28,9 @@ import type {ProgramInterface, LayoutAttribute} from '../program_configuration'; import type CollisionBoxArray, {CollisionBox} from '../../symbol/collision_box'; import type { StructArray } from '../../util/struct_array'; import type SymbolStyleLayer from '../../style/style_layer/symbol_style_layer'; +import type Context from '../../gl/context'; +import type IndexBuffer from '../../gl/index_buffer'; +import type VertexBuffer from '../../gl/vertex_buffer'; import type {SymbolQuad} from '../../symbol/quads'; import type {SizeData} from '../../symbol/symbol_size'; import type {PossiblyEvaluatedPropertyValue} from '../../style/properties'; @@ -266,23 +267,23 @@ class SymbolBuffers { } - upload(gl: WebGLRenderingContext, dynamicIndexBuffer) { - this.layoutVertexBuffer = new VertexBuffer(gl, this.layoutVertexArray); - this.indexBuffer = new IndexBuffer(gl, this.indexArray, dynamicIndexBuffer); - this.programConfigurations.upload(gl); + upload(context: Context, dynamicIndexBuffer) { + this.layoutVertexBuffer = context.createVertexBuffer(this.layoutVertexArray); + this.indexBuffer = context.createIndexBuffer(this.indexArray, dynamicIndexBuffer); + this.programConfigurations.upload(context); if (this.dynamicLayoutAttributes) { - this.dynamicLayoutVertexBuffer = new VertexBuffer(gl, this.dynamicLayoutVertexArray, true); + this.dynamicLayoutVertexBuffer = context.createVertexBuffer(this.dynamicLayoutVertexArray, true); } if (this.opacityAttributes) { - this.opacityVertexBuffer = new VertexBuffer(gl, this.opacityVertexArray, true); + this.opacityVertexBuffer = context.createVertexBuffer(this.opacityVertexArray, true); // This is a performance hack so that we can write to opacityVertexArray with uint32s // even though the shaders read uint8s this.opacityVertexBuffer.itemSize = 1; this.opacityVertexBuffer.attributes = shaderOpacityAttributes; } if (this.collisionAttributes) { - this.collisionVertexBuffer = new VertexBuffer(gl, this.collisionVertexArray, true); + this.collisionVertexBuffer = context.createVertexBuffer(this.collisionVertexArray, true); } } @@ -539,11 +540,11 @@ class SymbolBucket implements Bucket { return this.symbolInstances.length === 0; } - upload(gl: WebGLRenderingContext) { - this.text.upload(gl, this.sortFeaturesByY); - this.icon.upload(gl, this.sortFeaturesByY); - this.collisionBox.upload(gl); - this.collisionCircle.upload(gl); + upload(context: Context) { + this.text.upload(context, this.sortFeaturesByY); + this.icon.upload(context, this.sortFeaturesByY); + this.collisionBox.upload(context); + this.collisionCircle.upload(context); } destroy() { diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 697e6bd6f3e..491633479f5 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -4,13 +4,14 @@ import type {GlobalProperties} from "../style-spec/expression/index"; const createVertexArrayType = require('./vertex_array_type'); const packUint8ToFloat = require('../shaders/encode_attribute').packUint8ToFloat; -const VertexBuffer = require('../gl/vertex_buffer'); const Color = require('../style-spec/util/color'); const {deserialize, serialize, register} = require('../util/web_worker_transfer'); +import type Context from '../gl/context'; import type StyleLayer from '../style/style_layer'; import type {Serialized} from '../util/web_worker_transfer'; import type {ViewType, StructArray} from '../util/struct_array'; +import type VertexBuffer from '../gl/vertex_buffer'; import type Program from '../render/program'; import type {Feature, SourceExpression, CompositeExpression} from '../style-spec/expression'; import type {PossiblyEvaluated, PossiblyEvaluatedPropertyValue} from '../style/properties'; @@ -84,7 +85,7 @@ interface Binder { defines(): Array; - setUniforms(gl: WebGLRenderingContext, + setUniforms(context: Context, program: Program, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue): void; @@ -111,11 +112,12 @@ class ConstantBinder implements Binder { populatePaintArray() {} - setUniforms(gl: WebGLRenderingContext, + setUniforms(context: Context, program: Program, globals: GlobalProperties, currentValue: PossiblyEvaluatedPropertyValue) { const value: any = currentValue.constantOr(this.value); + const gl = context.gl; if (this.type === 'color') { gl.uniform4f(program.uniforms[`u_${this.name}`], value.r, value.g, value.b, value.a); } else { @@ -166,8 +168,8 @@ class SourceExpressionBinder implements Binder { } } - setUniforms(gl: WebGLRenderingContext, program: Program) { - gl.uniform1f(program.uniforms[`a_${this.name}_t`], 0); + setUniforms(context: Context, program: Program) { + context.gl.uniform1f(program.uniforms[`a_${this.name}_t`], 0); } } @@ -230,8 +232,8 @@ class CompositeExpressionBinder implements Binder { } } - setUniforms(gl: WebGLRenderingContext, program: Program, globals: GlobalProperties) { - gl.uniform1f(program.uniforms[`a_${this.name}_t`], this.interpolationFactor(globals.zoom)); + setUniforms(context: Context, program: Program, globals: GlobalProperties) { + context.gl.uniform1f(program.uniforms[`a_${this.name}_t`], this.interpolationFactor(globals.zoom)); } } @@ -362,16 +364,16 @@ class ProgramConfiguration { return result; } - setUniforms(gl: WebGLRenderingContext, program: Program, properties: PossiblyEvaluated, globals: GlobalProperties) { + setUniforms(context: Context, program: Program, properties: PossiblyEvaluated, globals: GlobalProperties) { for (const property in this.binders) { const binder = this.binders[property]; - binder.setUniforms(gl, program, globals, properties.get(binder.property)); + binder.setUniforms(context, program, globals, properties.get(binder.property)); } } - upload(gl: WebGLRenderingContext) { + upload(context: Context) { if (this.paintVertexArray) { - this.paintVertexBuffer = new VertexBuffer(gl, this.paintVertexArray); + this.paintVertexBuffer = context.createVertexBuffer(this.paintVertexArray); } } @@ -414,9 +416,9 @@ class ProgramConfigurationSet { return this.programConfigurations[layerId]; } - upload(gl: WebGLRenderingContext) { + upload(context: Context) { for (const layerId in this.programConfigurations) { - this.programConfigurations[layerId].upload(gl); + this.programConfigurations[layerId].upload(context); } } diff --git a/src/gl/context.js b/src/gl/context.js new file mode 100644 index 00000000000..2afe6605a2c --- /dev/null +++ b/src/gl/context.js @@ -0,0 +1,158 @@ +// @flow +const IndexBuffer = require('./index_buffer'); +const VertexBuffer = require('./vertex_buffer'); +const State = require('./state'); +const { + ClearColor, + ClearDepth, + ClearStencil, + ColorMask, + DepthMask, + StencilMask, + StencilFunc, + StencilOp, + StencilTest, + DepthRange, + DepthTest, + DepthFunc, + Blend, + BlendFunc, + BlendColor, + Program, + LineWidth, + ActiveTextureUnit, + Viewport, + BindFramebuffer, + BindRenderbuffer, + BindTexture, + BindVertexBuffer, + BindElementBuffer, + BindVertexArrayOES, + PixelStoreUnpack, + PixelStoreUnpackPremultiplyAlpha, +} = require('./value'); + + +import type {TriangleIndexArray, LineIndexArray} from '../data/index_array_type'; +import type {StructArray} from '../util/struct_array'; +import type { + BlendFuncType, + ColorMaskType, + DepthRangeType, + StencilFuncType, + DepthFuncType, + StencilOpType, + TextureUnitType, + ViewportType, +} from './types'; +import type Color from '../style-spec/util/color'; + +type ClearArgs = { + color?: Color, + depth?: number, + stencil?: number +}; + + +class Context { + gl: WebGLRenderingContext; + extVertexArrayObject: any; + currentNumAttributes: ?number; + + clearColor: State; + clearDepth: State; + clearStencil: State; + colorMask: State; + depthMask: State; + stencilMask: State; + stencilFunc: State; + stencilOp: State; + stencilTest: State; + depthRange: State; + depthTest: State; + depthFunc: State; + blend: State; + blendFunc: State; + blendColor: State; + program: State; + lineWidth: State; + activeTexture: State; + viewport: State; + bindFramebuffer: State; + bindRenderbuffer: State + bindTexture: State; + bindVertexBuffer: State; + bindElementBuffer: State; + bindVertexArrayOES: State; + pixelStoreUnpack: State; + pixelStoreUnpackPremultiplyAlpha: State; + + constructor(gl: WebGLRenderingContext) { + this.gl = gl; + this.extVertexArrayObject = this.gl.getExtension('OES_vertex_array_object'); + + this.clearColor = new State(new ClearColor(this)); + this.clearDepth = new State(new ClearDepth(this)); + this.clearStencil = new State(new ClearStencil(this)); + this.colorMask = new State(new ColorMask(this)); + this.depthMask = new State(new DepthMask(this)); + this.stencilMask = new State(new StencilMask(this)); + this.stencilFunc = new State(new StencilFunc(this)); + this.stencilOp = new State(new StencilOp(this)); + this.stencilTest = new State(new StencilTest(this)); + this.depthRange = new State(new DepthRange(this)); + this.depthTest = new State(new DepthTest(this)); + this.depthFunc = new State(new DepthFunc(this)); + this.blend = new State(new Blend(this)); + this.blendFunc = new State(new BlendFunc(this)); + this.blendColor = new State(new BlendColor(this)); + this.program = new State(new Program(this)); + this.lineWidth = new State(new LineWidth(this)); + this.activeTexture = new State(new ActiveTextureUnit(this)); + this.viewport = new State(new Viewport(this)); + this.bindFramebuffer = new State(new BindFramebuffer(this)); + this.bindRenderbuffer = new State(new BindRenderbuffer(this)); + this.bindTexture = new State(new BindTexture(this)); + this.bindVertexBuffer = new State(new BindVertexBuffer(this)); + this.bindElementBuffer = new State(new BindElementBuffer(this)); + this.bindVertexArrayOES = this.extVertexArrayObject && new State(new BindVertexArrayOES(this)); + this.pixelStoreUnpack = new State(new PixelStoreUnpack(this)); + this.pixelStoreUnpackPremultiplyAlpha = new State(new PixelStoreUnpackPremultiplyAlpha(this)); + } + + createIndexBuffer(array: TriangleIndexArray | LineIndexArray, dynamicDraw?: boolean) { + return new IndexBuffer(this, array, dynamicDraw); + } + + createVertexBuffer(array: StructArray, dynamicDraw?: boolean) { + return new VertexBuffer(this, array, dynamicDraw); + } + + clear({color, depth}: ClearArgs) { + const gl = this.gl; + let mask = 0; + + if (color) { + mask |= gl.COLOR_BUFFER_BIT; + this.clearColor.set(color); + this.colorMask.set([true, true, true, true]); + } + + if (typeof depth !== 'undefined') { + mask |= gl.DEPTH_BUFFER_BIT; + this.clearDepth.set(depth); + this.depthMask.set(true); + } + + // See note in Painter#clearStencil: implement this the easy way once GPU bug/workaround is fixed upstream + // if (typeof stencil !== 'undefined') { + // mask |= gl.STENCIL_BUFFER_BIT; + // this.clearStencil.set(stencil); + // this.stencilMask.set(0xFF); + // } + + gl.clear(mask); + } +} + +module.exports = Context; diff --git a/src/gl/index_buffer.js b/src/gl/index_buffer.js index d81f53c1a32..42fc7e1fede 100644 --- a/src/gl/index_buffer.js +++ b/src/gl/index_buffer.js @@ -3,21 +3,23 @@ const assert = require('assert'); import type {StructArray} from '../util/struct_array'; import type {TriangleIndexArray, LineIndexArray} from '../data/index_array_type'; +import type Context from '../gl/context'; class IndexBuffer { - gl: WebGLRenderingContext; + context: Context; buffer: WebGLBuffer; dynamicDraw: boolean; - constructor(gl: WebGLRenderingContext, array: TriangleIndexArray | LineIndexArray, dynamicDraw?: boolean) { - this.gl = gl; + constructor(context: Context, array: TriangleIndexArray | LineIndexArray, dynamicDraw?: boolean) { + this.context = context; + const gl = context.gl; this.buffer = gl.createBuffer(); this.dynamicDraw = Boolean(dynamicDraw); this.unbindVAO(); - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffer); + context.bindElementBuffer.set(this.buffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); if (!this.dynamicDraw) { @@ -29,30 +31,29 @@ class IndexBuffer { // The bound index buffer is part of vertex array object state. We don't want to // modify whatever VAO happens to be currently bound, so make sure the default // vertex array provided by the context is bound instead. - if (this.gl.extVertexArrayObject === undefined) { - (this.gl: any).extVertexArrayObject = this.gl.getExtension("OES_vertex_array_object"); - } - if (this.gl.extVertexArrayObject) { - (this.gl: any).extVertexArrayObject.bindVertexArrayOES(null); + if (this.context.extVertexArrayObject) { + this.context.bindVertexArrayOES.set(null); } } bind() { - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffer); + this.context.bindElementBuffer.set(this.buffer); } updateData(array: StructArray) { + const gl = this.context.gl; assert(this.dynamicDraw); // The right VAO will get this buffer re-bound later in VertexArrayObject#bind // See https://github.com/mapbox/mapbox-gl-js/issues/5620 this.unbindVAO(); this.bind(); - this.gl.bufferSubData(this.gl.ELEMENT_ARRAY_BUFFER, 0, array.arrayBuffer); + gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, array.arrayBuffer); } destroy() { + const gl = this.context.gl; if (this.buffer) { - this.gl.deleteBuffer(this.buffer); + gl.deleteBuffer(this.buffer); delete this.buffer; } } diff --git a/src/gl/state.js b/src/gl/state.js new file mode 100644 index 00000000000..cd591991c79 --- /dev/null +++ b/src/gl/state.js @@ -0,0 +1,27 @@ +// @flow + +import type {Value} from './value'; + +class State { + value: Value; + current: T; + dirty: boolean; + + constructor(v: Value) { + this.value = v; + this.current = v.constructor.default(v.context); + } + + get(): T { + return this.current; + } + + set(t: T) { + if (!this.value.constructor.equal(this.current, t)) { + this.current = t; + this.value.set(t); + } + } +} + +module.exports = State; diff --git a/src/gl/types.js b/src/gl/types.js new file mode 100644 index 00000000000..b393825e9ee --- /dev/null +++ b/src/gl/types.js @@ -0,0 +1,58 @@ +// @flow + +type BlendFuncConstant = + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType; + +export type BlendFuncType = [BlendFuncConstant, BlendFuncConstant]; + +export type ColorMaskType = [boolean, boolean, boolean, boolean]; + +export type DepthRangeType = [number, number]; + +export type CompareFuncType = + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType; + +export type StencilFuncType = { + func: CompareFuncType, + ref: number, + mask: number +}; + +export type DepthFuncType = CompareFuncType; + +type StencilOpConstant = + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType + | $PropertyType; + +export type StencilOpType = [StencilOpConstant, StencilOpConstant, StencilOpConstant]; + +export type TextureUnitType = number; + +export type ViewportType = [number, number, number, number]; diff --git a/src/gl/value.js b/src/gl/value.js new file mode 100644 index 00000000000..fb15aad3843 --- /dev/null +++ b/src/gl/value.js @@ -0,0 +1,355 @@ +// @flow + +const Color = require('../style-spec/util/color'); +const util = require('../util/util'); + +import type Context from './context'; +import type { + BlendFuncType, + ColorMaskType, + DepthRangeType, + StencilFuncType, + StencilOpType, + DepthFuncType, + TextureUnitType, + ViewportType, +} from './types'; + + +export interface Value { + context: Context; + static default(context?: Context): T; + set(value: T): void; + static equal(a: T, b: T): boolean; +} + +class ContextValue { + context: Context; + + constructor(context: Context) { + this.context = context; + } + + static equal(a, b): boolean { + return util.deepEqual(a, b); + } +} + +class ClearColor extends ContextValue implements Value { + static default() { return Color.transparent; } + + set(v: Color): void { + this.context.gl.clearColor(v.r, v.g, v.b, v.a); + } +} + +class ClearDepth extends ContextValue implements Value { + static default() { return 1; } + + set(v: number): void { + this.context.gl.clearDepth(v); + } +} + +class ClearStencil extends ContextValue implements Value { + static default() { return 0; } + + set(v: number): void { + this.context.gl.clearStencil(v); + } +} + +class ColorMask extends ContextValue implements Value { + static default() { return [true, true, true, true]; } + + set(v: ColorMaskType): void { + this.context.gl.colorMask(v[0], v[1], v[2], v[3]); + } +} + +class DepthMask extends ContextValue implements Value { + static default() { return true; } + + set(v: boolean): void { + this.context.gl.depthMask(v); + } +} + +class StencilMask extends ContextValue implements Value { + static default() { return 0xFF; } + + set(v: number): void { + this.context.gl.stencilMask(v); + } +} + +class StencilFunc extends ContextValue implements Value { + static default(context: Context) { + return { + func: context.gl.ALWAYS, + ref: 0, + mask: 0xFF + }; + } + + set(v: StencilFuncType): void { + this.context.gl.stencilFunc(v.func, v.ref, v.mask); + } +} + +class StencilOp extends ContextValue implements Value { + static default(context: Context) { + const gl = context.gl; + return [gl.KEEP, gl.KEEP, gl.KEEP]; + } + + set(v: StencilOpType): void { + this.context.gl.stencilOp(v[0], v[1], v[2]); + } +} + +class StencilTest extends ContextValue implements Value { + static default() { return false; } + + set(v: boolean): void { + const gl = this.context.gl; + if (v) { + gl.enable(gl.STENCIL_TEST); + } else { + gl.disable(gl.STENCIL_TEST); + } + } +} + +class DepthRange extends ContextValue implements Value { + static default() { return [0, 1]; } + + set(v: DepthRangeType): void { + this.context.gl.depthRange(v[0], v[1]); + } +} + +class DepthTest extends ContextValue implements Value { + static default() { return false; } + + set(v: boolean): void { + const gl = this.context.gl; + if (v) { + gl.enable(gl.DEPTH_TEST); + } else { + gl.disable(gl.DEPTH_TEST); + } + } +} + +class DepthFunc extends ContextValue implements Value { + static default(context: Context) { + return context.gl.LESS; + } + + set(v: DepthFuncType): void { + this.context.gl.depthFunc(v); + } +} + +class Blend extends ContextValue implements Value { + static default() { return false; } + + set(v: boolean): void { + const gl = this.context.gl; + if (v) { + gl.enable(gl.BLEND); + } else { + gl.disable(gl.BLEND); + } + } +} + +class BlendFunc extends ContextValue implements Value { + static default(context: Context) { + const gl = context.gl; + return [gl.ONE, gl.ZERO]; + } + + set(v: BlendFuncType): void { + this.context.gl.blendFunc(v[0], v[1]); + } +} + +class BlendColor extends ContextValue implements Value { + static default() { return Color.transparent; } + + set(v: Color): void { + this.context.gl.blendColor(v.r, v.g, v.b, v.a); + } +} + +class Program extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLProgram): void { + this.context.gl.useProgram(v); + } + + static equal(a: ?WebGLProgram, b: ?WebGLProgram): boolean { + return a === b; + } +} + +class LineWidth extends ContextValue implements Value { + static default() { return 1; } + + set(v: number): void { + this.context.gl.lineWidth(v); + } +} + +class ActiveTextureUnit extends ContextValue implements Value { + static default(context: Context) { + return context.gl.TEXTURE0; + } + + set(v: TextureUnitType): void { + this.context.gl.activeTexture(v); + } +} + +class Viewport extends ContextValue implements Value { + static default(context: Context) { + const gl = context.gl; + return [0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight]; + } + + set(v: ViewportType): void { + this.context.gl.viewport(v[0], v[1], v[2], v[3]); + } +} + +class BindFramebuffer extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLFramebuffer): void { + const gl = this.context.gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, v); + } + + static equal(a: ?WebGLFramebuffer, b: ?WebGLFramebuffer): boolean { + return a === b; + } +} + +class BindRenderbuffer extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLRenderbuffer): void { + const gl = this.context.gl; + gl.bindRenderbuffer(gl.RENDERBUFFER, v); + } + + static equal(a: ?WebGLRenderbuffer, b: ?WebGLRenderbuffer): boolean { + return a === b; + } +} + +class BindTexture extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLTexture): void { + const gl = this.context.gl; + gl.bindTexture(gl.TEXTURE_2D, v); + } + + static equal(a: ?WebGLTexture, b: ?WebGLTexture): boolean { + return a === b; + } +} + +class BindVertexBuffer extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLBuffer): void { + const gl = this.context.gl; + gl.bindBuffer(gl.ARRAY_BUFFER, v); + } + + static equal(a: ?WebGLBuffer, b: ?WebGLBuffer): boolean { + return a === b; + } +} + +class BindElementBuffer extends ContextValue implements Value { + static default() { return null; } + + set(v: ?WebGLBuffer): void { + const gl = this.context.gl; + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, v); + } + + static equal(): boolean { + // Always rebind: + return false; + } +} + +class BindVertexArrayOES extends ContextValue implements Value { + static default() { return null; } + + set(v: ?any): void { + const context = this.context; + if (context.extVertexArrayObject) { + context.extVertexArrayObject.bindVertexArrayOES(v); + } + } + + static equal(a, b): boolean { + return a === b; + } +} + +class PixelStoreUnpack extends ContextValue implements Value { + static default() { return 4; } + + set(v: number): void { + const gl = this.context.gl; + gl.pixelStorei(gl.UNPACK_ALIGNMENT, v); + } +} + +class PixelStoreUnpackPremultiplyAlpha extends ContextValue implements Value { + static default() { return false; } + + set(v: boolean): void { + const gl = this.context.gl; + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, (v: any)); + } +} + +module.exports = { + ClearColor, + ClearDepth, + ClearStencil, + ColorMask, + DepthMask, + StencilMask, + StencilFunc, + StencilOp, + StencilTest, + DepthRange, + DepthTest, + DepthFunc, + Blend, + BlendFunc, + BlendColor, + Program, + LineWidth, + ActiveTextureUnit, + Viewport, + BindFramebuffer, + BindRenderbuffer, + BindTexture, + BindVertexBuffer, + BindElementBuffer, + BindVertexArrayOES, + PixelStoreUnpack, + PixelStoreUnpackPremultiplyAlpha, +}; diff --git a/src/gl/vertex_buffer.js b/src/gl/vertex_buffer.js index 066a0819627..643ae932400 100644 --- a/src/gl/vertex_buffer.js +++ b/src/gl/vertex_buffer.js @@ -6,6 +6,7 @@ import type { } from '../util/struct_array'; import type Program from '../render/program'; +import type Context from '../gl/context'; /** * @enum {string} AttributeType @@ -32,22 +33,23 @@ class VertexBuffer { attributes: Array; itemSize: number; dynamicDraw: ?boolean; - gl: WebGLRenderingContext; + context: Context; buffer: WebGLBuffer; /** * @param dynamicDraw Whether this buffer will be repeatedly updated. */ - constructor(gl: WebGLRenderingContext, array: StructArray, dynamicDraw?: boolean) { + constructor(context: Context, array: StructArray, dynamicDraw?: boolean) { this.length = array.length; this.attributes = array.members; this.itemSize = array.bytesPerElement; this.dynamicDraw = dynamicDraw; - this.gl = gl; + this.context = context; + const gl = context.gl; this.buffer = gl.createBuffer(); - this.gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer); - this.gl.bufferData(gl.ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); + context.bindVertexBuffer.set(this.buffer); + gl.bufferData(gl.ARRAY_BUFFER, array.arrayBuffer, this.dynamicDraw ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW); if (!this.dynamicDraw) { delete array.arrayBuffer; @@ -55,12 +57,13 @@ class VertexBuffer { } bind() { - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer); + this.context.bindVertexBuffer.set(this.buffer); } updateData(array: StructArray) { + const gl = this.context.gl; this.bind(); - this.gl.bufferSubData(this.gl.ARRAY_BUFFER, 0, array.arrayBuffer); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, array.arrayBuffer); } enableAttributes(gl: WebGLRenderingContext, program: Program) { @@ -101,8 +104,9 @@ class VertexBuffer { * Destroy the GL buffer bound to the given WebGL context */ destroy() { + const gl = this.context.gl; if (this.buffer) { - this.gl.deleteBuffer(this.buffer); + gl.deleteBuffer(this.buffer); delete this.buffer; } } diff --git a/src/render/draw_background.js b/src/render/draw_background.js index fbd06f4f076..af2a4d3bb2d 100644 --- a/src/render/draw_background.js +++ b/src/render/draw_background.js @@ -17,7 +17,8 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Backg if (opacity === 0) return; - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const transform = painter.transform; const tileSize = transform.tileSize; const image = layer.paint.get('background-pattern'); @@ -26,7 +27,7 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Backg const pass = (!image && color.a === 1 && opacity === 1) ? 'opaque' : 'translucent'; if (painter.renderPass !== pass) return; - gl.disable(gl.STENCIL_TEST); + context.stencilTest.set(false); painter.setDepthSublayer(0); @@ -42,14 +43,14 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Backg if (pattern.isPatternMissing(image, painter)) return; const configuration = ProgramConfiguration.forBackgroundPattern(opacity); program = painter.useProgram('fillPattern', configuration); - configuration.setUniforms(gl, program, properties, globals); + configuration.setUniforms(context, program, properties, globals); pattern.prepare(image, painter, program); - painter.tileExtentPatternVAO.bind(gl, program, painter.tileExtentBuffer); + painter.tileExtentPatternVAO.bind(context, program, painter.tileExtentBuffer); } else { const configuration = ProgramConfiguration.forBackgroundColor(color, opacity); program = painter.useProgram('fill', configuration); - configuration.setUniforms(gl, program, properties, globals); - painter.tileExtentVAO.bind(gl, program, painter.tileExtentBuffer); + configuration.setUniforms(context, program, properties, globals); + painter.tileExtentVAO.bind(context, program, painter.tileExtentBuffer); } const coords = transform.coveringTiles({tileSize}); diff --git a/src/render/draw_circle.js b/src/render/draw_circle.js index 28379470009..487bbd9822c 100644 --- a/src/render/draw_circle.js +++ b/src/render/draw_circle.js @@ -21,14 +21,15 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt return; } - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; painter.setDepthSublayer(0); - painter.depthMask(false); + context.depthMask.set(false); // Allow circles to be drawn across boundaries, so that // large circles are not clipped to tiles - gl.disable(gl.STENCIL_TEST); + context.stencilTest.set(false); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; @@ -39,7 +40,7 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram('circle', programConfiguration); - programConfiguration.setUniforms(gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(context, program, layer.paint, {zoom: painter.transform.zoom}); gl.uniform1f(program.uniforms.u_camera_to_center_distance, painter.transform.cameraToCenterDistance); gl.uniform1i(program.uniforms.u_scale_with_map, layer.paint.get('circle-pitch-scale') === 'map' ? 1 : 0); @@ -60,7 +61,7 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt )); program.draw( - gl, + context, gl.TRIANGLES, layer.id, bucket.layoutVertexBuffer, diff --git a/src/render/draw_collision_debug.js b/src/render/draw_collision_debug.js index fdf1903cc97..1cf8639908e 100644 --- a/src/render/draw_collision_debug.js +++ b/src/render/draw_collision_debug.js @@ -10,7 +10,8 @@ const pixelsToTileUnits = require('../source/pixels_to_tile_units'); module.exports = drawCollisionDebug; function drawCollisionDebugGeometry(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array, drawCircles: boolean) { - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const program = drawCircles ? painter.useProgram('collisionCircle') : painter.useProgram('collisionBox'); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; @@ -35,7 +36,7 @@ function drawCollisionDebugGeometry(painter: Painter, sourceCache: SourceCache, painter.transform.pixelsToGLUnits[1] / (pixelRatio * scale)); program.draw( - gl, + context, drawCircles ? gl.TRIANGLES : gl.LINES, layer.id, buffers.layoutVertexBuffer, diff --git a/src/render/draw_debug.js b/src/render/draw_debug.js index 9b3b50ed183..1fb7bed27ac 100644 --- a/src/render/draw_debug.js +++ b/src/render/draw_debug.js @@ -3,7 +3,6 @@ const browser = require('../util/browser'); const mat4 = require('@mapbox/gl-matrix').mat4; const EXTENT = require('../data/extent'); -const VertexBuffer = require('../gl/vertex_buffer'); const VertexArrayObject = require('./vertex_array_object'); const PosArray = require('../data/pos_array'); @@ -20,9 +19,10 @@ function drawDebug(painter: Painter, sourceCache: SourceCache, coords: Array, painter, programCon if (!pat) { program = painter.useProgram(programId, programConfiguration); if (firstTile || program !== prevProgram) { - programConfiguration.setUniforms(painter.gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom: painter.transform.zoom}); } } else { program = painter.useProgram(`${programId}Pattern`, programConfiguration); if (firstTile || program !== prevProgram) { - programConfiguration.setUniforms(painter.gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom: painter.transform.zoom}); pattern.prepare(pat, painter, program); } pattern.setTile(tile, painter, program); } - painter.gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix( + painter.context.gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix( coord.posMatrix, tile, layer.paint.get('fill-translate'), layer.paint.get('fill-translate-anchor') diff --git a/src/render/draw_fill_extrusion.js b/src/render/draw_fill_extrusion.js index 2c763034ee6..34e195669a7 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 Color = require('../style-spec/util/color'); const mat3 = glMatrix.mat3; const mat4 = glMatrix.mat4; const vec3 = glMatrix.vec3; @@ -20,13 +21,13 @@ function draw(painter: Painter, source: SourceCache, layer: FillExtrusionStyleLa } if (painter.renderPass === '3d') { - const gl = painter.gl; + const context = painter.context; - gl.disable(gl.STENCIL_TEST); - gl.enable(gl.DEPTH_TEST); + context.stencilTest.set(false); + context.depthTest.set(true); - painter.clearColor(); - painter.depthMask(true); + context.clear({ color: Color.transparent }); + context.depthMask.set(true); for (let i = 0; i < coords.length; i++) { drawExtrusion(painter, source, layer, coords[i]); @@ -40,11 +41,12 @@ function drawExtrusionTexture(painter, layer) { const renderedTexture = layer.viewportFrame; if (!renderedTexture) return; - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const program = painter.useProgram('extrusionTexture'); - gl.disable(gl.STENCIL_TEST); - gl.disable(gl.DEPTH_TEST); + context.stencilTest.set(false); + context.depthTest.set(false); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, renderedTexture.texture); @@ -58,7 +60,7 @@ function drawExtrusionTexture(painter, layer) { gl.uniform2f(program.uniforms.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - painter.viewportVAO.bind(gl, program, painter.viewportBuffer); + painter.viewportVAO.bind(context, program, painter.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } @@ -67,13 +69,14 @@ function drawExtrusion(painter, source, layer, coord) { const bucket: ?FillExtrusionBucket = (tile.getBucket(layer): any); if (!bucket) return; - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const image = layer.paint.get('fill-extrusion-pattern'); const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram(image ? 'fillExtrusionPattern' : 'fillExtrusion', programConfiguration); - programConfiguration.setUniforms(gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(context, program, layer.paint, {zoom: painter.transform.zoom}); if (image) { if (pattern.isPatternMissing(image, painter)) return; @@ -82,7 +85,7 @@ function drawExtrusion(painter, source, layer, coord) { gl.uniform1f(program.uniforms.u_height_factor, -Math.pow(2, coord.z) / tile.tileSize / 8); } - painter.gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix( + painter.context.gl.uniformMatrix4fv(program.uniforms.u_matrix, false, painter.translatePosMatrix( coord.posMatrix, tile, layer.paint.get('fill-extrusion-translate'), @@ -92,7 +95,7 @@ function drawExtrusion(painter, source, layer, coord) { setLight(program, painter); program.draw( - gl, + context, gl.TRIANGLES, layer.id, bucket.layoutVertexBuffer, @@ -102,7 +105,7 @@ function drawExtrusion(painter, source, layer, coord) { } function setLight(program, painter) { - const gl = painter.gl; + const gl = painter.context.gl; const light = painter.style.light; const _lp = light.properties.get('position'); diff --git a/src/render/draw_heatmap.js b/src/render/draw_heatmap.js index aab25f197de..7f073e38ed8 100644 --- a/src/render/draw_heatmap.js +++ b/src/render/draw_heatmap.js @@ -3,6 +3,7 @@ const mat4 = require('@mapbox/gl-matrix').mat4; const Texture = require('./texture'); const pixelsToTileUnits = require('../source/pixels_to_tile_units'); +const Color = require('../style-spec/util/color'); import type Painter from './painter'; import type SourceCache from '../source/source_cache'; @@ -18,22 +19,22 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS return; } - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; painter.setDepthSublayer(0); - painter.depthMask(false); + context.depthMask.set(false); // Allow kernels to be drawn across boundaries, so that // large kernels are not clipped to tiles - gl.disable(gl.STENCIL_TEST); + context.stencilTest.set(false); - renderToTexture(gl, painter, layer); + renderToTexture(context, painter, layer); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); + context.clear({ color: Color.transparent }); // Turn on additive blending for kernels, which is a key aspect of kernel density estimation formula - gl.blendFunc(gl.ONE, gl.ONE); + context.blendFunc.set([gl.ONE, gl.ONE]); for (let i = 0; i < coords.length; i++) { const coord = coords[i]; @@ -50,7 +51,7 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram('heatmap', programConfiguration); const {zoom} = painter.transform; - programConfiguration.setUniforms(gl, program, layer.paint, {zoom}); + 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)); @@ -59,7 +60,7 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS gl.uniformMatrix4fv(program.uniforms.u_matrix, false, coord.posMatrix); program.draw( - gl, + context, gl.TRIANGLES, layer.id, bucket.layoutVertexBuffer, @@ -68,14 +69,15 @@ function drawHeatmap(painter: Painter, sourceCache: SourceCache, layer: HeatmapS programConfiguration); } - renderTextureToMap(gl, painter, layer); + renderTextureToMap(context, painter, layer); } -function renderToTexture(gl, painter, layer) { - gl.activeTexture(gl.TEXTURE1); +function renderToTexture(context, painter, layer) { + const gl = context.gl; + context.activeTexture.set(gl.TEXTURE1); // Use a 4x downscaled screen texture for better performance - gl.viewport(0, 0, painter.width / 4, painter.height / 4); + context.viewport.set([0, 0, painter.width / 4, painter.height / 4]); let texture = layer.heatmapTexture; let fbo = layer.heatmapFbo; @@ -90,46 +92,48 @@ function renderToTexture(gl, painter, layer) { fbo = layer.heatmapFbo = gl.createFramebuffer(); - bindTextureFramebuffer(gl, painter, texture, fbo); + bindTextureFramebuffer(context, painter, texture, fbo); } else { gl.bindTexture(gl.TEXTURE_2D, texture); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + context.bindFramebuffer.set(fbo); } } -function bindTextureFramebuffer(gl, painter, texture, fbo) { +function bindTextureFramebuffer(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); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + context.bindFramebuffer.set(fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // 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; - bindTextureFramebuffer(gl, painter, texture, fbo); + bindTextureFramebuffer(context, painter, texture, fbo); } } -function renderTextureToMap(gl, painter, layer) { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); +function renderTextureToMap(context, painter, layer) { + const gl = context.gl; + context.bindFramebuffer.set(null); - gl.activeTexture(gl.TEXTURE2); + context.activeTexture.set(gl.TEXTURE2); let colorRampTexture = layer.colorRampTexture; if (!colorRampTexture) { - colorRampTexture = layer.colorRampTexture = new Texture(gl, layer.colorRamp, gl.RGBA); + colorRampTexture = layer.colorRampTexture = new Texture(context, layer.colorRamp, gl.RGBA); } colorRampTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + context.blendFunc.set(painter._showOverdrawInspector ? [gl.CONSTANT_COLOR, gl.ONE] : [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); const program = painter.useProgram('heatmapTexture'); - gl.viewport(0, 0, painter.width, painter.height); + context.viewport.set([0, 0, painter.width, painter.height]); - gl.activeTexture(gl.TEXTURE0); + context.activeTexture.set(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, layer.heatmapTexture); const opacity = layer.paint.get('heatmap-opacity'); @@ -141,12 +145,12 @@ function renderTextureToMap(gl, painter, layer) { mat4.ortho(matrix, 0, painter.width, painter.height, 0, 0, 1); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, matrix); - gl.disable(gl.DEPTH_TEST); + context.depthTest.set(false); gl.uniform2f(program.uniforms.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - painter.viewportVAO.bind(gl, program, painter.viewportBuffer); + painter.viewportVAO.bind(painter.context, program, painter.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl.enable(gl.DEPTH_TEST); + context.depthTest.set(true); } diff --git a/src/render/draw_line.js b/src/render/draw_line.js index 0e066df3058..92d32cb955d 100644 --- a/src/render/draw_line.js +++ b/src/render/draw_line.js @@ -15,11 +15,12 @@ module.exports = function drawLine(painter: Painter, sourceCache: SourceCache, l const opacity = layer.paint.get('line-opacity'); if (opacity.constantOr(1) === 0) return; + const context = painter.context; + painter.setDepthSublayer(0); - painter.depthMask(false); + context.depthMask.set(false); - const gl = painter.gl; - gl.enable(gl.STENCIL_TEST); + context.stencilTest.set(true); const programId = layer.paint.get('line-dasharray') ? 'lineSDF' : @@ -40,7 +41,7 @@ module.exports = function drawLine(painter: Painter, sourceCache: SourceCache, l const tileRatioChanged = prevTileZoom !== tile.coord.z; if (programChanged) { - programConfiguration.setUniforms(painter.gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom: painter.transform.zoom}); } drawLineTile(program, painter, tile, bucket, layer, coord, programConfiguration, programChanged, tileRatioChanged); prevTileZoom = tile.coord.z; @@ -49,7 +50,8 @@ module.exports = function drawLine(painter: Painter, sourceCache: SourceCache, l }; function drawLineTile(program, painter, tile, bucket, layer, coord, programConfiguration, programChanged, tileRatioChanged) { - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const dasharray = layer.paint.get('line-dasharray'); const image = layer.paint.get('line-pattern'); @@ -88,8 +90,8 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi if (dasharray) { gl.uniform1i(program.uniforms.u_image, 0); - gl.activeTexture(gl.TEXTURE0); - painter.lineAtlas.bind(gl); + context.activeTexture.set(gl.TEXTURE0); + painter.lineAtlas.bind(context); gl.uniform1f(program.uniforms.u_tex_y_a, (posA: any).y); gl.uniform1f(program.uniforms.u_tex_y_b, (posB: any).y); @@ -97,8 +99,8 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi } else if (image) { gl.uniform1i(program.uniforms.u_image, 0); - gl.activeTexture(gl.TEXTURE0); - painter.imageManager.bind(gl); + context.activeTexture.set(gl.TEXTURE0); + painter.imageManager.bind(context); gl.uniform2fv(program.uniforms.u_pattern_tl_a, (imagePosA: any).tl); gl.uniform2fv(program.uniforms.u_pattern_br_a, (imagePosA: any).br); @@ -116,7 +118,7 @@ function drawLineTile(program, painter, tile, bucket, layer, coord, programConfi gl.uniform1f(program.uniforms.u_ratio, 1 / pixelsToTileUnits(tile, 1, painter.transform.zoom)); program.draw( - gl, + context, gl.TRIANGLES, layer.id, bucket.layoutVertexBuffer, diff --git a/src/render/draw_raster.js b/src/render/draw_raster.js index bd7b5e31a82..bc4fb239f74 100644 --- a/src/render/draw_raster.js +++ b/src/render/draw_raster.js @@ -15,16 +15,17 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty if (painter.renderPass !== 'translucent') return; if (layer.paint.get('raster-opacity') === 0) return; - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const source = sourceCache.getSource(); const program = painter.useProgram('raster'); - gl.enable(gl.DEPTH_TEST); - painter.depthMask(layer.paint.get('raster-opacity') === 1); + context.depthTest.set(true); + context.depthMask.set(layer.paint.get('raster-opacity') === 1); // Change depth function to prevent double drawing in areas where tiles overlap. - gl.depthFunc(gl.LESS); + context.depthFunc.set(gl.LESS); - gl.disable(gl.STENCIL_TEST); + context.stencilTest.set(false); // Constant parameters. gl.uniform1f(program.uniforms.u_brightness_low, layer.paint.get('raster-brightness-min')); @@ -54,10 +55,10 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty let parentScaleBy, parentTL; - gl.activeTexture(gl.TEXTURE0); + context.activeTexture.set(gl.TEXTURE0); tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); - gl.activeTexture(gl.TEXTURE1); + context.activeTexture.set(gl.TEXTURE1); if (parentTile) { parentTile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); @@ -78,11 +79,11 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty if (source instanceof ImageSource) { const buffer = source.boundsBuffer; const vao = source.boundsVAO; - vao.bind(gl, program, buffer); + vao.bind(context, program, buffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.length); } else if (tile.maskedBoundsBuffer && tile.maskedIndexBuffer && tile.segments) { program.draw( - gl, + context, gl.TRIANGLES, layer.id, tile.maskedBoundsBuffer, @@ -92,12 +93,12 @@ function drawRaster(painter: Painter, sourceCache: SourceCache, layer: RasterSty } else { const buffer = painter.rasterBoundsBuffer; const vao = painter.rasterBoundsVAO; - vao.bind(gl, program, buffer); + vao.bind(context, program, buffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, buffer.length); } } - gl.depthFunc(gl.LEQUAL); + context.depthFunc.set(gl.LEQUAL); } function spinWeights(angle) { diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index 92d8acab832..2d3e3289382 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -20,13 +20,13 @@ module.exports = drawSymbols; function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolStyleLayer, coords: Array) { if (painter.renderPass !== 'translucent') return; - const gl = painter.gl; + const context = painter.context; // Disable the stencil test so that labels aren't clipped to tile boundaries. - gl.disable(gl.STENCIL_TEST); + context.stencilTest.set(false); painter.setDepthSublayer(0); - painter.depthMask(false); + context.depthMask.set(false); if (layer.paint.get('icon-opacity').constantOr(1) !== 0) { drawLayerSymbols(painter, sourceCache, layer, coords, false, @@ -56,7 +56,8 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate, translateAnchor, rotationAlignment, pitchAlignment, keepUpright) { - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const tr = painter.transform; const rotateWithMap = rotationAlignment === 'map'; @@ -69,11 +70,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate const depthOn = pitchWithMap; - if (depthOn) { - gl.enable(gl.DEPTH_TEST); - } else { - gl.disable(gl.DEPTH_TEST); - } + context.depthTest.set(depthOn); let program; @@ -91,12 +88,12 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate if (!program) { program = painter.useProgram(isSDF ? 'symbolSDF' : 'symbolIcon', programConfiguration); - programConfiguration.setUniforms(gl, program, layer.paint, {zoom: painter.transform.zoom}); + programConfiguration.setUniforms(painter.context, program, layer.paint, {zoom: painter.transform.zoom}); setSymbolDrawState(program, painter, layer, isText, rotateInShader, pitchWithMap, sizeData); } - gl.activeTexture(gl.TEXTURE0); + context.activeTexture.set(gl.TEXTURE0); gl.uniform1i(program.uniforms.u_texture, 0); if (isText) { @@ -132,12 +129,12 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap); } - if (!depthOn) gl.enable(gl.DEPTH_TEST); + if (!depthOn) context.depthTest.set(true); } function setSymbolDrawState(program, painter, layer, isText, rotateInShader, pitchWithMap, sizeData) { - const gl = painter.gl; + const gl = painter.context.gl; const tr = painter.transform; gl.uniform1i(program.uniforms.u_pitch_with_map, pitchWithMap ? 1 : 0); @@ -163,7 +160,8 @@ function setSymbolDrawState(program, painter, layer, isText, rotateInShader, pit function drawTileSymbols(program, programConfiguration, painter, layer, tile, buffers, isText, isSDF, pitchWithMap) { - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const tr = painter.transform; if (isSDF) { @@ -173,19 +171,19 @@ function drawTileSymbols(program, programConfiguration, painter, layer, tile, bu if (hasHalo) { // Draw halo underneath the text. gl.uniform1f(program.uniforms.u_is_halo, 1); - drawSymbolElements(buffers, layer, gl, program); + drawSymbolElements(buffers, layer, context, program); } gl.uniform1f(program.uniforms.u_is_halo, 0); } - drawSymbolElements(buffers, layer, gl, program); + drawSymbolElements(buffers, layer, context, program); } -function drawSymbolElements(buffers, layer, gl, program) { +function drawSymbolElements(buffers, layer, context, program) { program.draw( - gl, - gl.TRIANGLES, + context, + context.gl.TRIANGLES, layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, diff --git a/src/render/image_manager.js b/src/render/image_manager.js index c5fbd954ca8..d5e0c3ea728 100644 --- a/src/render/image_manager.js +++ b/src/render/image_manager.js @@ -7,6 +7,7 @@ const Texture = require('./texture'); const assert = require('assert'); import type {StyleImage} from '../style/style_image'; +import type Context from '../gl/context'; import type {ImagePosition} from './image_atlas'; import type {Bin} from '@mapbox/shelf-pack'; import type {Callback} from '../types/callback'; @@ -179,9 +180,10 @@ class ImageManager { return position; } - bind(gl: WebGLRenderingContext) { + bind(context: Context) { + const gl = context.gl; if (!this.atlasTexture) { - this.atlasTexture = new Texture(gl, this.atlasImage, gl.RGBA); + this.atlasTexture = new Texture(context, this.atlasImage, gl.RGBA); } else if (this.dirty) { this.atlasTexture.update(this.atlasImage); this.dirty = false; diff --git a/src/render/line_atlas.js b/src/render/line_atlas.js index f80d71b8b48..2d41b9b2c39 100644 --- a/src/render/line_atlas.js +++ b/src/render/line_atlas.js @@ -2,6 +2,8 @@ const util = require('../util/util'); +import type Context from '../gl/context'; + /** * A LineAtlas lets us reuse rendered dashed lines * by writing many of them to a texture and then fetching their positions @@ -128,7 +130,8 @@ class LineAtlas { return pos; } - bind(gl: WebGLRenderingContext) { + bind(context: Context) { + const gl = context.gl; if (!this.texture) { this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this.texture); diff --git a/src/render/painter.js b/src/render/painter.js index afc350dc71f..d48eab10085 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -6,7 +6,6 @@ const SourceCache = require('../source/source_cache'); const EXTENT = require('../data/extent'); const pixelsToTileUnits = require('../source/pixels_to_tile_units'); const util = require('../util/util'); -const VertexBuffer = require('../gl/vertex_buffer'); const VertexArrayObject = require('./vertex_array_object'); const RasterBoundsArray = require('../data/raster_bounds_array'); const PosArray = require('../data/pos_array'); @@ -14,8 +13,10 @@ const {ProgramConfiguration} = require('../data/program_configuration'); 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 updateTileMasks = require('./tile_mask'); +const Color = require('../style-spec/util/color'); const draw = { symbol: require('./draw_symbol'), @@ -38,6 +39,7 @@ 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'; export type RenderPass = '3d' | 'opaque' | 'translucent'; @@ -56,7 +58,7 @@ type PainterOptions = { * @private */ class Painter { - gl: WebGLRenderingContext; + context: Context; transform: Transform; _tileTextures: { [number]: Array }; numSublayers: number; @@ -67,7 +69,6 @@ class Painter { height: number; depthRbo: WebGLRenderbuffer; depthRboAttached: boolean; - _depthMask: boolean; tileExtentBuffer: VertexBuffer; tileExtentVAO: VertexArrayObject; tileExtentPatternVAO: VertexArrayObject; @@ -96,7 +97,7 @@ class Painter { crossTileSymbolIndex: CrossTileSymbolIndex; constructor(gl: WebGLRenderingContext, transform: Transform) { - this.gl = gl; + this.context = new Context(gl); this.transform = transform; this._tileTextures = {}; @@ -119,11 +120,11 @@ class Painter { * for a new width and height value. */ resize(width: number, height: number) { - const gl = this.gl; + const gl = this.context.gl; this.width = width * browser.devicePixelRatio; this.height = height * browser.devicePixelRatio; - gl.viewport(0, 0, this.width, this.height); + this.context.viewport.set([0, 0, this.width, this.height]); if (this.style) { for (const layerId of this.style._order) { @@ -132,33 +133,33 @@ class Painter { } if (this.depthRbo) { - this.gl.deleteRenderbuffer(this.depthRbo); + gl.deleteRenderbuffer(this.depthRbo); this.depthRbo = null; } } setup() { - const gl = this.gl; + const context = this.context; + const gl = this.context.gl; // We are blending the new pixels *behind* the existing pixels. That way we can // draw front-to-back and use then stencil buffer to cull opaque pixels early. - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + context.blend.set(true); + context.blendFunc.set([gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); - gl.enable(gl.STENCIL_TEST); + context.stencilTest.set(true); - gl.enable(gl.DEPTH_TEST); - gl.depthFunc(gl.LEQUAL); + context.depthTest.set(true); + context.depthFunc.set(gl.LEQUAL); - this._depthMask = false; - gl.depthMask(false); + context.depthMask.set(false); const tileExtentArray = new PosArray(); tileExtentArray.emplaceBack(0, 0); tileExtentArray.emplaceBack(EXTENT, 0); tileExtentArray.emplaceBack(0, EXTENT); tileExtentArray.emplaceBack(EXTENT, EXTENT); - this.tileExtentBuffer = new VertexBuffer(gl, tileExtentArray); + this.tileExtentBuffer = context.createVertexBuffer(tileExtentArray); this.tileExtentVAO = new VertexArrayObject(); this.tileExtentPatternVAO = new VertexArrayObject(); @@ -168,7 +169,7 @@ class Painter { debugArray.emplaceBack(EXTENT, EXTENT); debugArray.emplaceBack(0, EXTENT); debugArray.emplaceBack(0, 0); - this.debugBuffer = new VertexBuffer(gl, debugArray); + this.debugBuffer = context.createVertexBuffer(debugArray); this.debugVAO = new VertexArrayObject(); const rasterBoundsArray = new RasterBoundsArray(); @@ -176,7 +177,7 @@ class Painter { rasterBoundsArray.emplaceBack(EXTENT, 0, EXTENT, 0); rasterBoundsArray.emplaceBack(0, EXTENT, 0, EXTENT); rasterBoundsArray.emplaceBack(EXTENT, EXTENT, EXTENT, EXTENT); - this.rasterBoundsBuffer = new VertexBuffer(gl, rasterBoundsArray); + this.rasterBoundsBuffer = context.createVertexBuffer(rasterBoundsArray); this.rasterBoundsVAO = new VertexArrayObject(); const viewportArray = new PosArray(); @@ -184,7 +185,7 @@ class Painter { viewportArray.emplaceBack(1, 0); viewportArray.emplaceBack(0, 1); viewportArray.emplaceBack(1, 1); - this.viewportBuffer = new VertexBuffer(gl, viewportArray); + this.viewportBuffer = context.createVertexBuffer(viewportArray); this.viewportVAO = new VertexArrayObject(); this.extTextureFilterAnisotropic = ( @@ -202,40 +203,28 @@ class Painter { } } - /* - * Reset the color buffers of the drawing canvas. - */ - clearColor() { - const gl = this.gl; - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - } - /* * Reset the drawing canvas by clearing the stencil buffer so that we can draw * new tiles at the same location, while retaining previously drawn pixels. */ clearStencil() { - const gl = this.gl; + const context = this.context; + const gl = context.gl; // As a temporary workaround for https://github.com/mapbox/mapbox-gl-js/issues/5490, // pending an upstream fix, we draw a fullscreen stencil=0 clipping mask here, - // effectively clearing the stencil buffer: restore this code for native - // performance and readability once an upstream patch lands. + // effectively clearing the stencil buffer: once an upstream patch lands, remove + // this function in favor of context.clear({ stencil: 0x0 }) - // gl.clearStencil(0x0); - // gl.stencilMask(0xFF); - // gl.clear(gl.STENCIL_BUFFER_BIT); + context.colorMask.set([false, false, false, false]); + context.depthMask.set(false); + context.depthTest.set(false); + context.stencilTest.set(true); - gl.colorMask(false, false, false, false); - this.depthMask(false); - gl.disable(gl.DEPTH_TEST); - gl.enable(gl.STENCIL_TEST); + context.stencilMask.set(0xFF); + context.stencilOp.set([gl.ZERO, gl.ZERO, gl.ZERO]); - gl.stencilMask(0xFF); - gl.stencilOp(gl.ZERO, gl.ZERO, gl.ZERO); - - gl.stencilFunc(gl.ALWAYS, 0x0, 0xFF); + context.stencilFunc.set({ func: gl.ALWAYS, ref: 0x0, mask: 0xFF }); const matrix = mat4.create(); mat4.ortho(matrix, 0, this.width, this.height, 0, 0, 1); @@ -244,32 +233,26 @@ class Painter { const program = this.useProgram('fill', ProgramConfiguration.forTileClippingMask()); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, matrix); - this.viewportVAO.bind(gl, program, this.viewportBuffer); + this.viewportVAO.bind(context, program, this.viewportBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); - gl.stencilMask(0x00); - gl.colorMask(true, true, true, true); - this.depthMask(true); - gl.enable(gl.DEPTH_TEST); - } - - clearDepth() { - const gl = this.gl; - gl.clearDepth(1); - this.depthMask(true); - gl.clear(gl.DEPTH_BUFFER_BIT); + context.stencilMask.set(0x00); + context.colorMask.set([true, true, true, true]); + context.depthMask.set(true); + context.depthTest.set(true); } _renderTileClippingMasks(coords: Array) { - const gl = this.gl; - gl.colorMask(false, false, false, false); - this.depthMask(false); - gl.disable(gl.DEPTH_TEST); - gl.enable(gl.STENCIL_TEST); - - gl.stencilMask(0xFF); + const context = this.context; + const gl = context.gl; + context.colorMask.set([false, false, false, false]); + context.depthMask.set(false); + context.depthTest.set(false); + context.stencilTest.set(true); + + context.stencilMask.set(0xFF); // Tests will always pass, and ref value will be written to stencil buffer. - gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + context.stencilOp.set([gl.KEEP, gl.KEEP, gl.REPLACE]); let idNext = 1; this._tileClippingMaskIDs = {}; @@ -278,25 +261,26 @@ class Painter { for (const coord of coords) { const id = this._tileClippingMaskIDs[coord.id] = idNext++; - gl.stencilFunc(gl.ALWAYS, id, 0xFF); + context.stencilFunc.set({ func: gl.ALWAYS, ref: id, mask: 0xFF }); const program = this.useProgram('fill', programConfiguration); gl.uniformMatrix4fv(program.uniforms.u_matrix, false, coord.posMatrix); // Draw the clipping mask - this.tileExtentVAO.bind(gl, program, this.tileExtentBuffer); + this.tileExtentVAO.bind(this.context, program, this.tileExtentBuffer); gl.drawArrays(gl.TRIANGLE_STRIP, 0, this.tileExtentBuffer.length); } - gl.stencilMask(0x00); - gl.colorMask(true, true, true, true); - this.depthMask(true); - gl.enable(gl.DEPTH_TEST); + context.stencilMask.set(0x00); + context.colorMask.set([true, true, true, true]); + context.depthMask.set(true); + context.depthTest.set(true); } enableTileClippingMask(coord: TileCoord) { - const gl = this.gl; - gl.stencilFunc(gl.EQUAL, this._tileClippingMaskIDs[coord.id], 0xFF); + const context = this.context; + const gl = context.gl; + context.stencilFunc.set({ func: gl.EQUAL, ref: this._tileClippingMaskIDs[coord.id], mask: 0xFF }); } render(style: Style, options: PainterOptions) { @@ -307,10 +291,12 @@ class Painter { this.imageManager = style.imageManager; this.glyphManager = style.glyphManager; + const context = this.context; + for (const id in style.sourceCaches) { const sourceCache = this.style.sourceCaches[id]; if (sourceCache.used) { - sourceCache.prepare(this.gl); + sourceCache.prepare(this.context); } } @@ -321,7 +307,7 @@ class Painter { const sourceCache = rasterSources[key]; const coords = sourceCache.getVisibleCoordinates(); const visibleTiles = coords.map((c)=>{ return sourceCache.getTile(c); }); - updateTileMasks(visibleTiles, this.gl); + updateTileMasks(visibleTiles, this.context); } // 3D pass @@ -369,7 +355,7 @@ class Painter { renderTarget.bindWithDepth(this.depthRbo); if (first) { - this.clearDepth(); + context.clear({ depth: 1 }); first = false; } @@ -380,8 +366,7 @@ class Painter { } // Clear buffers in preparation for drawing to the main framebuffer - this.clearColor(); - this.clearDepth(); + context.clear({ color: Color.transparent, depth: 1 }); this.showOverdrawInspector(options.showOverdrawInspector); @@ -397,7 +382,7 @@ class Painter { this.currentLayer = layerIds.length - 1; if (!this._showOverdrawInspector) { - this.gl.disable(this.gl.BLEND); + this.context.blend.set(false); } for (this.currentLayer; this.currentLayer >= 0; this.currentLayer--) { @@ -427,7 +412,7 @@ class Painter { let sourceCache; let coords = []; - this.gl.enable(this.gl.BLEND); + this.context.blend.set(true); this.currentLayer = 0; @@ -462,25 +447,20 @@ class Painter { } _setup3DRenderbuffer() { + const context = this.context; // All of the 3D textures will use the same depth renderbuffer. if (!this.depthRbo) { - const gl = this.gl; + const gl = this.context.gl; this.depthRbo = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthRbo); + + context.bindRenderbuffer.set(this.depthRbo); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); - gl.bindRenderbuffer(gl.RENDERBUFFER, null); + context.bindRenderbuffer.set(null); } this.depthRboAttached = true; } - depthMask(mask: boolean) { - if (mask !== this._depthMask) { - this._depthMask = mask; - this.gl.depthMask(mask); - } - } - renderLayer(painter: Painter, sourceCache: SourceCache, layer: StyleLayer, coords: Array) { if (layer.isHidden(this.transform.zoom)) return; if (layer.type !== 'background' && !coords.length) return; @@ -492,7 +472,7 @@ class Painter { setDepthSublayer(n: number) { const farDepth = 1 - ((1 + this.currentLayer) * this.numSublayers + n) * this.depthEpsilon; const nearDepth = farDepth - 1 + this.depthRange; - this.gl.depthRange(nearDepth, farDepth); + this.context.depthRange.set([nearDepth, farDepth]); } /** @@ -542,23 +522,23 @@ class Painter { } lineWidth(width: number) { - this.gl.lineWidth(util.clamp(width, this.lineWidthRange[0], this.lineWidthRange[1])); + this.context.lineWidth.set(util.clamp(width, this.lineWidthRange[0], this.lineWidthRange[1])); } showOverdrawInspector(enabled: boolean) { if (!enabled && !this._showOverdrawInspector) return; this._showOverdrawInspector = enabled; - const gl = this.gl; + const context = this.context; + const gl = context.gl; if (enabled) { - gl.blendFunc(gl.CONSTANT_COLOR, gl.ONE); + context.blendFunc.set([gl.CONSTANT_COLOR, gl.ONE]); const numOverdrawSteps = 8; const a = 1 / numOverdrawSteps; - gl.blendColor(a, a, a, 0); - gl.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT); + context.blendColor.set(new Color(a, a, a, 0)); + context.clear({ color: Color.black }); } else { - gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + context.blendFunc.set([gl.ONE, gl.ONE_MINUS_SRC_ALPHA]); } } @@ -566,19 +546,15 @@ class Painter { this.cache = this.cache || {}; const key = `${name}${programConfiguration.cacheKey || ''}${this._showOverdrawInspector ? '/overdraw' : ''}`; if (!this.cache[key]) { - this.cache[key] = new Program(this.gl, shaders[name], programConfiguration, this._showOverdrawInspector); + this.cache[key] = new Program(this.context, shaders[name], programConfiguration, this._showOverdrawInspector); } return this.cache[key]; } useProgram(name: string, programConfiguration?: ProgramConfiguration): Program { - const gl = this.gl; const nextProgram = this._createProgramCached(name, programConfiguration || this.emptyProgramConfiguration); - if (this.currentProgram !== nextProgram) { - gl.useProgram(nextProgram.program); - this.currentProgram = nextProgram; - } + this.context.program.set(nextProgram.program); return nextProgram; } diff --git a/src/render/pattern.js b/src/render/pattern.js index 13a1a91cdbb..b5b4552a362 100644 --- a/src/render/pattern.js +++ b/src/render/pattern.js @@ -21,7 +21,8 @@ exports.isPatternMissing = function(image: ?CrossFaded, painter: Painter }; exports.prepare = function (image: CrossFaded, painter: Painter, program: Program) { - const gl = painter.gl; + const context = painter.context; + const gl = context.gl; const imagePosA = painter.imageManager.getPattern(image.from); const imagePosB = painter.imageManager.getPattern(image.to); @@ -40,12 +41,12 @@ exports.prepare = function (image: CrossFaded, painter: Painter, program gl.uniform1f(program.uniforms.u_scale_a, image.fromScale); gl.uniform1f(program.uniforms.u_scale_b, image.toScale); - gl.activeTexture(gl.TEXTURE0); - painter.imageManager.bind(gl); + context.activeTexture.set(gl.TEXTURE0); + painter.imageManager.bind(painter.context); }; exports.setTile = function (tile: {coord: TileCoord, tileSize: number}, painter: Painter, program: Program) { - const gl = painter.gl; + const gl = painter.context.gl; gl.uniform1f(program.uniforms.u_tile_units_to_pixels, 1 / pixelsToTileUnits(tile, 1, painter.transform.tileZoom)); diff --git a/src/render/program.js b/src/render/program.js index 176c614dcdb..f6dbfe5e023 100644 --- a/src/render/program.js +++ b/src/render/program.js @@ -5,6 +5,7 @@ const shaders = require('../shaders'); const assert = require('assert'); const {ProgramConfiguration} = require('../data/program_configuration'); const VertexArrayObject = require('./vertex_array_object'); +const Context = require('../gl/context'); import type {SegmentVector} from '../data/segment'; import type VertexBuffer from '../gl/vertex_buffer'; @@ -15,17 +16,16 @@ export type DrawMode = | $PropertyType; class Program { - gl: WebGLRenderingContext; program: WebGLProgram; uniforms: {[string]: WebGLUniformLocation}; attributes: {[string]: number}; numAttributes: number; - constructor(gl: WebGLRenderingContext, + constructor(context: Context, source: {fragmentSource: string, vertexSource: string}, configuration: ProgramConfiguration, showOverdrawInspector: boolean) { - this.gl = gl; + const gl = context.gl; this.program = gl.createProgram(); const defines = configuration.defines().concat( @@ -82,7 +82,7 @@ class Program { } } - draw(gl: WebGLRenderingContext, + draw(context: Context, drawMode: DrawMode, layerID: string, layoutVertexBuffer: VertexBuffer, @@ -92,6 +92,8 @@ class Program { dynamicLayoutBuffer: ?VertexBuffer, dynamicLayoutBuffer2: ?VertexBuffer) { + const gl = context.gl; + const primitiveSize = { [gl.LINES]: 2, [gl.TRIANGLES]: 3 @@ -102,7 +104,7 @@ class Program { const vao = vaos[layerID] || (vaos[layerID] = new VertexArrayObject()); vao.bind( - gl, + context, this, layoutVertexBuffer, indexBuffer, diff --git a/src/render/render_texture.js b/src/render/render_texture.js index bd2a8376a6a..db7ec875bcd 100644 --- a/src/render/render_texture.js +++ b/src/render/render_texture.js @@ -1,12 +1,13 @@ // @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 { - gl: WebGLRenderingContext; + context: Context; texture: WebGLTexture; fbo: WebGLFramebuffer; buffer: VertexBuffer; @@ -14,7 +15,8 @@ class RenderTexture { attachedRbo: ?WebGLRenderbuffer; constructor(painter: Painter) { - const gl = this.gl = painter.gl; + const context = this.context = painter.context; + const gl = context.gl; const texture = this.texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); @@ -27,13 +29,14 @@ class RenderTexture { gl.bindTexture(gl.TEXTURE_2D, null); const fbo = this.fbo = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); + context.bindFramebuffer.set(fbo); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); } bindWithDepth(depthRbo: WebGLRenderbuffer) { - const gl = this.gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo); + 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; @@ -41,8 +44,7 @@ class RenderTexture { } unbind() { - const gl = this.gl; - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + this.context.bindFramebuffer.set(null); } } diff --git a/src/render/texture.js b/src/render/texture.js index 390ff14c2c2..ce79625ce80 100644 --- a/src/render/texture.js +++ b/src/render/texture.js @@ -2,6 +2,7 @@ const {HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageData} = require('../util/window'); +import type Context from '../gl/context'; import type {RGBAImage, AlphaImage} from '../util/image'; import type {ImageTextureSource} from '../source/image_source'; @@ -23,21 +24,21 @@ export type TextureImage = | ImageTextureSource; class Texture { - gl: WebGLRenderingContext; + context: Context; size: Array; texture: WebGLTexture; format: TextureFormat; filter: ?TextureFilter; wrap: ?TextureWrap; - constructor(gl: WebGLRenderingContext, image: TextureImage, format: TextureFormat) { - this.gl = gl; + constructor(context: Context, image: TextureImage, format: TextureFormat) { + this.context = context; const {width, height} = image; this.size = [width, height]; this.format = format; - this.texture = gl.createTexture(); + this.texture = context.gl.createTexture(); this.update(image); } @@ -45,12 +46,13 @@ class Texture { const {width, height} = image; this.size = [width, height]; - const {gl} = this; + const {context} = this; + const {gl} = context; gl.bindTexture(gl.TEXTURE_2D, this.texture); - gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); + context.pixelStoreUnpack.set(1); if (this.format === gl.RGBA) { - gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, (true: any)); + context.pixelStoreUnpackPremultiplyAlpha.set(true); } if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData) { @@ -61,7 +63,8 @@ class Texture { } bind(filter: TextureFilter, wrap: TextureWrap, minFilter: ?TextureFilter) { - const {gl} = this; + const {context} = this; + const {gl} = context; gl.bindTexture(gl.TEXTURE_2D, this.texture); if (filter !== this.filter) { @@ -78,7 +81,7 @@ class Texture { } destroy() { - const {gl} = this; + const {gl} = this.context; gl.deleteTexture(this.texture); this.texture = (null: any); } diff --git a/src/render/tile_mask.js b/src/render/tile_mask.js index be3c8381e37..62ddab6ad7d 100644 --- a/src/render/tile_mask.js +++ b/src/render/tile_mask.js @@ -3,6 +3,7 @@ const TileCoord = require('../source/tile_coord'); import type Tile from './../source/tile'; +import type Context from '../gl/context'; export type Mask = { [number]: boolean @@ -59,7 +60,7 @@ export type Mask = { // 2/1/3, since it is not a descendant of it. -module.exports = function(renderableTiles: Array, gl: WebGLRenderingContext) { +module.exports = function(renderableTiles: Array, context: Context) { const sortedRenderables = renderableTiles.sort((a, b) => { return a.coord.isLessThan(b.coord) ? -1 : b.coord.isLessThan(a.coord) ? 1 : 0; }); for (let i = 0; i < sortedRenderables.length; i++) { @@ -72,7 +73,7 @@ module.exports = function(renderableTiles: Array, gl: WebGLRenderingContex // can never be children of the current wrap. computeTileMasks(tile.coord.wrapped(), tile.coord, childArray, new TileCoord(0, 0, 0, tile.coord.w + 1), mask); - tile.setMask(mask, gl); + tile.setMask(mask, context); } }; diff --git a/src/render/vertex_array_object.js b/src/render/vertex_array_object.js index 89ec18eec50..8336bb61739 100644 --- a/src/render/vertex_array_object.js +++ b/src/render/vertex_array_object.js @@ -5,8 +5,10 @@ const assert = require('assert'); import type Program from './program'; import type VertexBuffer from '../gl/vertex_buffer'; import type IndexBuffer from '../gl/index_buffer'; +import type Context from '../gl/context'; class VertexArrayObject { + context: Context; boundProgram: ?Program; boundVertexBuffer: ?VertexBuffer; boundVertexBuffer2: ?VertexBuffer; @@ -15,7 +17,6 @@ class VertexArrayObject { boundDynamicVertexBuffer: ?VertexBuffer; boundDynamicVertexBuffer2: ?VertexBuffer; vao: any; - gl: WebGLRenderingContext; constructor() { this.boundProgram = null; @@ -27,7 +28,7 @@ class VertexArrayObject { this.vao = null; } - bind(gl: WebGLRenderingContext, + bind(context: Context, program: Program, layoutVertexBuffer: VertexBuffer, indexBuffer: ?IndexBuffer, @@ -36,9 +37,7 @@ class VertexArrayObject { dynamicVertexBuffer: ?VertexBuffer, dynamicVertexBuffer2: ?VertexBuffer) { - if (gl.extVertexArrayObject === undefined) { - (gl: any).extVertexArrayObject = gl.getExtension("OES_vertex_array_object"); - } + this.context = context; const isFreshBindRequired = ( !this.vao || @@ -51,11 +50,10 @@ class VertexArrayObject { this.boundDynamicVertexBuffer2 !== dynamicVertexBuffer2 ); - if (!gl.extVertexArrayObject || isFreshBindRequired) { - this.freshBind(gl, program, layoutVertexBuffer, indexBuffer, vertexBuffer2, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2); - this.gl = gl; + if (!context.extVertexArrayObject || isFreshBindRequired) { + this.freshBind(program, layoutVertexBuffer, indexBuffer, vertexBuffer2, vertexOffset, dynamicVertexBuffer, dynamicVertexBuffer2); } else { - (gl: any).extVertexArrayObject.bindVertexArrayOES(this.vao); + context.bindVertexArrayOES.set(this.vao); if (dynamicVertexBuffer) { // The buffer may have been updated. Rebind to upload data. @@ -72,8 +70,7 @@ class VertexArrayObject { } } - freshBind(gl: WebGLRenderingContext, - program: Program, + freshBind(program: Program, layoutVertexBuffer: VertexBuffer, indexBuffer: ?IndexBuffer, vertexBuffer2: ?VertexBuffer, @@ -83,10 +80,13 @@ class VertexArrayObject { let numPrevAttributes; const numNextAttributes = program.numAttributes; - if (gl.extVertexArrayObject) { + const context = this.context; + const gl = context.gl; + + if (context.extVertexArrayObject) { if (this.vao) this.destroy(); - this.vao = (gl: any).extVertexArrayObject.createVertexArrayOES(); - (gl: any).extVertexArrayObject.bindVertexArrayOES(this.vao); + this.vao = context.extVertexArrayObject.createVertexArrayOES(); + context.bindVertexArrayOES.set(this.vao); numPrevAttributes = 0; // store the arguments so that we can verify them when the vao is bound again @@ -99,7 +99,7 @@ class VertexArrayObject { this.boundDynamicVertexBuffer2 = dynamicVertexBuffer2; } else { - numPrevAttributes = (gl: any).currentNumAttributes || 0; + numPrevAttributes = context.currentNumAttributes || 0; // Disable all attributes from the previous program that aren't used in // the new program. Note: attribute indices are *not* program specific! @@ -140,12 +140,12 @@ class VertexArrayObject { dynamicVertexBuffer2.setVertexAttribPointers(gl, program, vertexOffset); } - (gl: any).currentNumAttributes = numNextAttributes; + context.currentNumAttributes = numNextAttributes; } destroy() { if (this.vao) { - (this.gl: any).extVertexArrayObject.deleteVertexArrayOES(this.vao); + this.context.extVertexArrayObject.deleteVertexArrayOES(this.vao); this.vao = null; } } diff --git a/src/source/canvas_source.js b/src/source/canvas_source.js index da24acd55a7..794000414e7 100644 --- a/src/source/canvas_source.js +++ b/src/source/canvas_source.js @@ -135,7 +135,7 @@ class CanvasSource extends ImageSource { if (Object.keys(this.tiles).length === 0) return; // not enough data for current position - this._prepareImage(this.map.painter.gl, this.canvas, resize); + this._prepareImage(this.map.painter.context, this.canvas, resize); } serialize(): Object { diff --git a/src/source/image_source.js b/src/source/image_source.js index c1646e4eb1b..3d09b2cacfb 100644 --- a/src/source/image_source.js +++ b/src/source/image_source.js @@ -10,7 +10,6 @@ const ajax = require('../util/ajax'); const browser = require('../util/browser'); const EXTENT = require('../data/extent'); const RasterBoundsArray = require('../data/raster_bounds_array'); -const VertexBuffer = require('../gl/vertex_buffer'); const VertexArrayObject = require('../render/vertex_array_object'); const Texture = require('../render/texture'); @@ -20,6 +19,8 @@ import type Dispatcher from '../util/dispatcher'; import type Tile from './tile'; import type Coordinate from '../geo/coordinate'; import type {Callback} from '../types/callback'; +import type Context from '../gl/context'; +import type VertexBuffer from '../gl/vertex_buffer'; export type ImageTextureSource = ImageData | @@ -187,12 +188,13 @@ class ImageSource extends Evented implements Source { prepare() { if (Object.keys(this.tiles).length === 0 || !this.image) return; - this._prepareImage(this.map.painter.gl, this.image); + this._prepareImage(this.map.painter.context, this.image); } - _prepareImage(gl: WebGLRenderingContext, image: ImageTextureSource, resize?: boolean) { + _prepareImage(context: Context, image: ImageTextureSource, resize?: boolean) { + const gl = context.gl; if (!this.boundsBuffer) { - this.boundsBuffer = new VertexBuffer(gl, this._boundsArray); + this.boundsBuffer = context.createVertexBuffer(this._boundsArray); } if (!this.boundsVAO) { @@ -201,7 +203,7 @@ class ImageSource extends Evented implements Source { if (!this.textureLoaded) { this.textureLoaded = true; - this.texture = new Texture(gl, image, gl.RGBA); + this.texture = new Texture(context, image, gl.RGBA); this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } else if (resize) { this.texture.update(image); diff --git a/src/source/raster_tile_source.js b/src/source/raster_tile_source.js index a310f2ce9e4..215b5e296dc 100644 --- a/src/source/raster_tile_source.js +++ b/src/source/raster_tile_source.js @@ -99,13 +99,14 @@ class RasterTileSource extends Evented implements Source { delete (img: any).cacheControl; delete (img: any).expires; - const gl = this.map.painter.gl; + const context = this.map.painter.context; + const gl = context.gl; tile.texture = this.map.painter.getTileTexture(img.width); if (tile.texture) { tile.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE, gl.LINEAR_MIPMAP_NEAREST); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, img); } else { - tile.texture = new Texture(gl, img, gl.RGBA); + 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) { diff --git a/src/source/source_cache.js b/src/source/source_cache.js index 9e7b6a322b2..40170751063 100644 --- a/src/source/source_cache.js +++ b/src/source/source_cache.js @@ -8,6 +8,7 @@ const Cache = require('../util/lru_cache'); const Coordinate = require('../geo/coordinate'); const util = require('../util/util'); const EXTENT = require('../data/extent'); +const Context = require('../gl/context'); const Point = require('@mapbox/point-geometry'); const browser = require('../util/browser'); @@ -162,13 +163,13 @@ class SourceCache extends Evented { return this._source.serialize(); } - prepare(gl: WebGLRenderingContext) { + prepare(context: Context) { if (this._source.prepare) { this._source.prepare(); } for (const i in this._tiles) { - this._tiles[i].upload(gl); + this._tiles[i].upload(context); } } diff --git a/src/source/tile.js b/src/source/tile.js index 9badaaa10f9..4e4d69bc318 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -14,8 +14,6 @@ const RasterBoundsArray = require('../data/raster_bounds_array'); const TileCoord = require('./tile_coord'); const EXTENT = require('../data/extent'); const Point = require('@mapbox/point-geometry'); -const VertexBuffer = require('../gl/vertex_buffer'); -const IndexBuffer = require('../gl/index_buffer'); const Texture = require('../render/texture'); const {SegmentVector} = require('../data/segment'); const {TriangleIndexArray} = require('../data/index_array_type'); @@ -33,6 +31,9 @@ import type {WorkerTileResult} from './worker_source'; import type {RGBAImage, AlphaImage} from '../util/image'; import type Mask from '../render/tile_mask'; import type CrossTileSymbolIndex from '../symbol/cross_tile_symbol_index'; +import type Context from '../gl/context'; +import type IndexBuffer from '../gl/index_buffer'; +import type VertexBuffer from '../gl/vertex_buffer'; export type TileState = | 'loading' // Tile data is in the process of loading. @@ -251,22 +252,24 @@ class Tile { return this.buckets[layer.id]; } - upload(gl: WebGLRenderingContext) { + upload(context: Context) { for (const id in this.buckets) { const bucket = this.buckets[id]; if (!bucket.uploaded) { - bucket.upload(gl); + bucket.upload(context); bucket.uploaded = true; } } + const gl = context.gl; + if (this.iconAtlasImage) { - this.iconAtlasTexture = new Texture(gl, this.iconAtlasImage, gl.RGBA); + this.iconAtlasTexture = new Texture(context, this.iconAtlasImage, gl.RGBA); this.iconAtlasImage = null; } if (this.glyphAtlasImage) { - this.glyphAtlasTexture = new Texture(gl, this.glyphAtlasImage, gl.ALPHA); + this.glyphAtlasTexture = new Texture(context, this.glyphAtlasImage, gl.ALPHA); this.glyphAtlasImage = null; } } @@ -342,7 +345,7 @@ class Tile { } } - setMask(mask: Mask, gl: WebGLRenderingContext) { + setMask(mask: Mask, context: Context) { // don't redo buffer work if the mask is the same; if (util.deepEqual(this.mask, mask)) return; @@ -387,8 +390,8 @@ class Tile { segment.primitiveLength += 2; } - this.maskedBoundsBuffer = new VertexBuffer(gl, maskedBoundsArray); - this.maskedIndexBuffer = new IndexBuffer(gl, indexArray); + this.maskedBoundsBuffer = context.createVertexBuffer(maskedBoundsArray); + this.maskedIndexBuffer = context.createIndexBuffer(indexArray); } hasData() { diff --git a/src/source/video_source.js b/src/source/video_source.js index d046f5d099d..726070c2fae 100644 --- a/src/source/video_source.js +++ b/src/source/video_source.js @@ -113,7 +113,7 @@ class VideoSource extends ImageSource { prepare() { if (Object.keys(this.tiles).length === 0 || this.video.readyState < 2) return; // not enough data for current position - this._prepareImage(this.map.painter.gl, this.video); + this._prepareImage(this.map.painter.context, this.video); } serialize() { diff --git a/src/ui/map.js b/src/ui/map.js index a4fc68d0465..9e36fa15917 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -1512,7 +1512,7 @@ class Map extends Camera { window.removeEventListener('resize', this._onWindowResize, false); window.removeEventListener('online', this._onWindowOnline, false); } - const extension = this.painter.gl.getExtension('WEBGL_lose_context'); + const extension = this.painter.context.gl.getExtension('WEBGL_lose_context'); if (extension) extension.loseContext(); removeNode(this._canvasContainer); removeNode(this._controlContainer); diff --git a/test/suite_implementation.js b/test/suite_implementation.js index 5e3f42d6b66..1ca5a62dbe5 100644 --- a/test/suite_implementation.js +++ b/test/suite_implementation.js @@ -52,7 +52,7 @@ module.exports = function(style, options, _callback) { if (options.debug) map.showTileBoundaries = true; if (options.showOverdrawInspector) map.showOverdrawInspector = true; - const gl = map.painter.gl; + const gl = map.painter.context.gl; map.once('load', () => { if (options.collisionDebug) { @@ -90,7 +90,7 @@ module.exports = function(style, options, _callback) { map.remove(); gl.getExtension('STACKGL_destroy_context').destroy(); - delete map.painter.gl; + delete map.painter.context.gl; callback(null, data, results.map((feature) => { feature = feature.toJSON(); diff --git a/test/unit/data/vertex_buffer.test.js b/test/unit/data/vertex_buffer.test.js index 3980970e34d..f4419f0ffbf 100644 --- a/test/unit/data/vertex_buffer.test.js +++ b/test/unit/data/vertex_buffer.test.js @@ -3,6 +3,7 @@ const test = require('mapbox-gl-js-test').test; const VertexBuffer = require('../../../src/gl/vertex_buffer'); const StructArrayType = require('../../../src/util/struct_array'); +const Context = require('../../../src/gl/context'); test('VertexBuffer', (t) => { @@ -16,13 +17,13 @@ test('VertexBuffer', (t) => { t.test('constructs itself', (t) => { - const gl = require('gl')(10, 10); + const context = new Context(require('gl')(10, 10)); const array = new TestArray(); array.emplaceBack(1, 1, 1); array.emplaceBack(1, 1, 1); array.emplaceBack(1, 1, 1); - const buffer = new VertexBuffer(gl, array); + const buffer = new VertexBuffer(context, array); t.deepEqual(buffer.attributes, [ { diff --git a/test/unit/gl/state.test.js b/test/unit/gl/state.test.js new file mode 100644 index 00000000000..d9969d6b038 --- /dev/null +++ b/test/unit/gl/state.test.js @@ -0,0 +1,176 @@ +'use strict'; + +const test = require('mapbox-gl-js-test').test; +const State = require('../../../src/gl/state'); +const { + ClearColor, + ClearDepth, + ClearStencil, + ColorMask, + DepthMask, + StencilMask, + StencilFunc, + StencilOp, + StencilTest, + DepthRange, + DepthTest, + DepthFunc, + Blend, + BlendFunc, + BlendColor, + Program, + LineWidth, + ActiveTextureUnit, + Viewport, + BindFramebuffer, + BindRenderbuffer, + BindTexture, + BindVertexBuffer, + BindElementBuffer, + BindVertexArrayOES, + PixelStoreUnpack, + PixelStoreUnpackPremultiplyAlpha +} = require('../../../src/gl/value'); +const Context = require('../../../src/gl/context'); +const Color = require('../../../src/style-spec/util/color'); + +const context = new Context(require('gl')(10, 10)); + +function ValueTest(Constructor, options, t) { + t.test('#constructor', (t) => { + const v = new State(new Constructor(context)); + t.ok(v); + const currentV = v.get(); + const defaultV = v.value.constructor.default(context); + t.notEqual(typeof currentV, 'undefined'); + t.notEqual(typeof defaultV, 'undefined'); + t.ok(v.value.constructor.equal(currentV, defaultV) || + // special case for BindElementBuffer, where equal always returns false + currentV === defaultV); + t.end(); + }); + + t.test('#set', (t) => { + const v = new State(new Constructor(context)); + v.set(options.setValue); + t.ok(v.value.constructor.equal(v.get(), options.setValue) || + // special case for BindElementBuffer, where equal always returns false + v.get() === options.setValue); + t.end(); + }); + + t.end(); +} + +test('ClearColor', ValueTest.bind(ValueTest, ClearColor, { + setValue: new Color(1, 1, 0, 1) +})); + + +test('ClearDepth', ValueTest.bind(ValueTest, ClearDepth, { + setValue: 0.5 +})); + +test('ClearStencil', ValueTest.bind(ValueTest, ClearStencil, { + setValue: 0.5 +})); + +test('ColorMask', ValueTest.bind(ValueTest, ColorMask, { + setValue: [false, false, true, true] +})); + +test('DepthMask', ValueTest.bind(ValueTest, DepthMask, { + setValue: false +})); + +test('StencilMask', ValueTest.bind(ValueTest, StencilMask, { + setValue: [0x00, 4] +})); + +test('StencilFunc', ValueTest.bind(ValueTest, StencilFunc, { + setValue: { + func: context.gl.LEQUAL, + ref: 1, + mask: 0xFF + } +})); + +test('StencilOp', ValueTest.bind(ValueTest, StencilOp, { + setValue: [context.gl.KEEP, context.gl.REPLACE, context.gl.REPLACE] +})); + +test('StencilTest', ValueTest.bind(ValueTest, StencilTest, { + setValue: true +})); + +test('DepthRange', ValueTest.bind(ValueTest, DepthRange, { + setValue: [0, 0.1] +})); + +test('DepthTest', ValueTest.bind(ValueTest, DepthTest, { + setValue: true +})); + +test('DepthFunc', ValueTest.bind(ValueTest, DepthFunc, { + setValue: context.gl.EQUAL +})); + +test('Blend', ValueTest.bind(ValueTest, Blend, { + setValue: false +})); + +test('BlendFunc', ValueTest.bind(ValueTest, BlendFunc, { + setValue: [context.gl.SRC_ALPHA, context.gl.SRC_ALPHA] +})); + +test('BlendColor', ValueTest.bind(ValueTest, BlendColor, { + setValue: Color.white +})); + +test('Program', ValueTest.bind(ValueTest, Program, { + setValue: context.gl.createProgram() +})); + +test('LineWidth', ValueTest.bind(ValueTest, LineWidth, { + setValue: 0.5 +})); + +test('ActiveTextureUnit', ValueTest.bind(ValueTest, ActiveTextureUnit, { + setValue: context.gl.TEXTURE1 +})); + +test('Viewport', ValueTest.bind(ValueTest, Viewport, { + setValue: [0, 0, 1, 1] +})); + +test('BindFramebuffer', ValueTest.bind(ValueTest, BindFramebuffer, { + setValue: context.gl.createFramebuffer() +})); + +test('BindRenderbuffer', ValueTest.bind(ValueTest, BindRenderbuffer, { + setValue: context.gl.createRenderbuffer() +})); + +test('BindTexture', ValueTest.bind(ValueTest, BindTexture, { + setValue: context.gl.createTexture() +})); + +test('BindVertexBuffer', ValueTest.bind(ValueTest, BindVertexBuffer, { + setValue: context.gl.createBuffer() +})); + +test('BindElementBuffer', ValueTest.bind(ValueTest, BindElementBuffer, { + setValue: context.gl.createBuffer() +})); + +test('BindVertexArrayOES', ValueTest.bind(ValueTest, BindVertexArrayOES, { + setValue: context.extVertexArrayObject +})); + +test('PixelStoreUnpack', ValueTest.bind(ValueTest, PixelStoreUnpack, { + setValue: 8 +})); + +test('PixelStoreUnpackPremultiplyAlpha', ValueTest.bind(ValueTest, PixelStoreUnpackPremultiplyAlpha, { + setValue: true +})); diff --git a/test/unit/source/tile.test.js b/test/unit/source/tile.test.js index f74e86acace..07a7242612e 100644 --- a/test/unit/source/tile.test.js +++ b/test/unit/source/tile.test.js @@ -12,6 +12,7 @@ const CollisionIndex = require('../../../src/symbol/collision_index'); const Transform = require('../../../src/geo/transform'); const CollisionBoxArray = require('../../../src/symbol/collision_box'); const util = require('../../../src/util/util'); +const Context = require('../../../src/gl/context'); const {serialize} = require('../../../src/util/web_worker_transfer'); test('querySourceFeatures', (t) => { @@ -137,17 +138,17 @@ test('Tile#setMask', (t) => { t.test('simple mask', (t)=>{ const tile = new Tile(0, 0, 0); - const gl = require('gl')(10, 10); - tile.setMask([new TileCoord(1, 0, 0).id, new TileCoord(1, 1, 1).id], gl); + const context = new Context(require('gl')(10, 10)); + tile.setMask([new TileCoord(1, 0, 0).id, new TileCoord(1, 1, 1).id], context); t.deepEqual(tile.mask, [new TileCoord(1, 0, 0).id, new TileCoord(1, 1, 1).id]); t.end(); }); t.test('complex mask', (t) => { const tile = new Tile(0, 0, 0); - const gl = require('gl')(10, 10); + const context = new Context(require('gl')(10, 10)); tile.setMask([new TileCoord(1, 0, 1).id, new TileCoord(1, 1, 0).id, new TileCoord(2, 2, 3).id, - new TileCoord(2, 3, 2).id, new TileCoord(3, 6, 7).id, new TileCoord(3, 7, 6).id], gl); + new TileCoord(2, 3, 2).id, new TileCoord(3, 6, 7).id, new TileCoord(3, 7, 6).id], context); t.deepEqual(tile.mask, [new TileCoord(1, 0, 1).id, new TileCoord(1, 1, 0).id, new TileCoord(2, 2, 3).id, new TileCoord(2, 3, 2).id, new TileCoord(3, 6, 7).id, new TileCoord(3, 7, 6).id]); t.end(); diff --git a/yarn.lock b/yarn.lock index 112f8fe2cfb..f37e9f45407 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2448,6 +2448,14 @@ concat-stream@~1.5.0, concat-stream@~1.5.1: readable-stream "~2.0.0" typedarray "~0.0.5" +concat-stream@~1.5.0, concat-stream@~1.5.1: + version "1.5.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" + dependencies: + inherits "~2.0.1" + readable-stream "~2.0.0" + typedarray "~0.0.5" + concat-with-sourcemaps@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.0.4.tgz#f55b3be2aeb47601b10a2d5259ccfb70fd2f1dd6"