From 9baad74bf38f398ca3588fcea365c3a34f236760 Mon Sep 17 00:00:00 2001 From: Tarek Sherif Date: Thu, 17 Oct 2019 17:45:26 -0400 Subject: [PATCH] Optimizations (#1283) --- .eslintrc.js | 1 + docs/api-reference/webgl/program.md | 1 + docs/upgrade-guide.md | 1 + examples/performance/stress-test/app.js | 7 +- modules/core/src/lib/model.js | 31 +- modules/debug/src/index.js | 5 - .../parameter-definitions.js | 555 ------------------ modules/webgl/src/classes/program.js | 116 ++-- modules/webgl/src/classes/uniforms.js | 243 +++++--- modules/webgl/src/classes/vertex-array.js | 5 +- .../test/classes/uniform-cache-test-cases.js | 123 ++++ modules/webgl/test/classes/uniforms.spec.js | 190 ++---- 12 files changed, 434 insertions(+), 844 deletions(-) delete mode 100644 modules/debug/src/webgl-api-tracing/parameter-definitions.js create mode 100644 modules/webgl/test/classes/uniform-cache-test-cases.js diff --git a/.eslintrc.js b/.eslintrc.js index 08a99ed6ad..765c76ca54 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,6 +9,7 @@ module.exports = { 'no-inline-comments': 0, camelcase: 0, 'max-statements': 0, + 'max-depth': 0, 'luma-gl-custom-rules/check-log-call': 1, 'import/no-unresolved': ['error'], 'import/no-extraneous-dependencies': [ diff --git a/docs/api-reference/webgl/program.md b/docs/api-reference/webgl/program.md index b35b4d218c..68625bf4ca 100644 --- a/docs/api-reference/webgl/program.md +++ b/docs/api-reference/webgl/program.md @@ -159,6 +159,7 @@ Notes: * Indexed rendering uses the element buffer (`GL.ELEMENT_ARRAY_BUFFER`), make sure your attributes or `VertexArray` contains one. * If a `TransformFeedback` object is supplied, `transformFeedback.begin()` and `transformFeedback.end()` will be called before and after the draw call. * A `Sampler` will only be bound if there is a matching Texture with the same key in the supplied `uniforms` object. +* Once a uniform is set, it's size should not be changed. This is only a concern for array uniforms. The following WebGL APIs are called in this function: diff --git a/docs/upgrade-guide.md b/docs/upgrade-guide.md index 8e2c68e8a3..03f36558fb 100644 --- a/docs/upgrade-guide.md +++ b/docs/upgrade-guide.md @@ -6,6 +6,7 @@ - `BaseModel` and `Model` have been consolidated in `Model`. `Model` be used as a substitute for `BaseModel` where necessary. - `AmbientLight`, `DirectionalLight`, `PointLight`, `PhongMaterial`, `PBRMaterial`, `CameraNode` have been removed from @luma.gl/core. These were either empty classes or simple data objects and so can be replaced by plain JavaScript objects in most cases. - `ShaderCache` has been removed and superseded by `ProgramManager`. +- `VertexArray.getDrawParams` no longer takes overrides as an argument. The calling function can manually override values as needed. - @luma.gl/glfx has been renamed to @luma.gl/effects. - @luma.gl/main has been removed. Use individual modules instead. - `Multipass` classes have been removed. diff --git a/examples/performance/stress-test/app.js b/examples/performance/stress-test/app.js index f0e369a902..b7bdaf8005 100644 --- a/examples/performance/stress-test/app.js +++ b/examples/performance/stress-test/app.js @@ -165,11 +165,16 @@ export default class AppAnimationLoop extends AnimationLoop { opaqueCubes, transparentCubes, angle, - statsWidget + statsWidget, + tick } = props; statsWidget.update(); + if (tick % 600 === 0) { + this.stats.reset(); + } + const camX = Math.cos(angle); const camZ = Math.sin(angle); const camRadius = 800; diff --git a/modules/core/src/lib/model.js b/modules/core/src/lib/model.js index e00e0cd3e7..8112d5a99c 100644 --- a/modules/core/src/lib/model.js +++ b/modules/core/src/lib/model.js @@ -17,6 +17,9 @@ const LOG_DRAW_TIMEOUT = 10000; const ERR_MODEL_PARAMS = 'Model needs drawMode and vertexCount'; +const NOOP = () => {}; +const DRAW_PARAMS = {}; + export default class Model { constructor(gl, props = {}) { // Deduce a helpful id @@ -25,6 +28,7 @@ export default class Model { this.id = id; this.gl = gl; this.id = props.id || uid('Model'); + this.debug = props.debug || false; this.lastLogTime = 0; // TODO - move to probe.gl this.initialize(props); } @@ -236,25 +240,34 @@ export default class Model { this.updateModuleSettings(moduleSettings); this.setUniforms(uniforms); - const logPriority = this._logDrawCallStart(2); + let logPriority; + + if (this.debug) { + logPriority = this._logDrawCallStart(2); + } + + const drawParams = this.vertexArray.getDrawParams(); + const { + isIndexed = drawParams.isIndexed, + indexType = drawParams.indexType, + indexOffset = drawParams.indexOffset, + vertexArrayInstanced = drawParams.isInstanced + } = this.props; - const drawParams = this.vertexArray.getDrawParams(this.props); - if (drawParams.isInstanced && !this.isInstanced) { + if (vertexArrayInstanced && !this.isInstanced) { log.warn('Found instanced attributes on non-instanced model', this.id)(); } - const {isIndexed, indexType, indexOffset} = drawParams; const {isInstanced, instanceCount} = this; - const noop = () => {}; - const {onBeforeRender = noop, onAfterRender = noop} = this.props; + const {onBeforeRender = NOOP, onAfterRender = NOOP} = this.props; onBeforeRender(); this.program.setUniforms(this.uniforms); const didDraw = this.program.draw( - Object.assign({}, opts, { + Object.assign(DRAW_PARAMS, opts, { logPriority, uniforms: null, // Already set (may contain "function values" not understood by Program) framebuffer, @@ -273,7 +286,9 @@ export default class Model { onAfterRender(); - this._logDrawCallEnd(logPriority, vertexArray, framebuffer); + if (this.debug) { + this._logDrawCallEnd(logPriority, vertexArray, framebuffer); + } return didDraw; } diff --git a/modules/debug/src/index.js b/modules/debug/src/index.js index c7b785b948..c4e7fea298 100644 --- a/modules/debug/src/index.js +++ b/modules/debug/src/index.js @@ -15,11 +15,6 @@ export {makeDebugContext} from './webgl-api-tracing/webgl-debug-context'; import {makeDebugContext} from './webgl-api-tracing/webgl-debug-context'; global.makeDebugContext = makeDebugContext; -// Install additional parameter definitions on luma.gl classes -// TODO: This needs a bit more plumbing -// import {installParameterDefinitions} from './webgl-api-tracing/parameter-definitions'; -// installParameterDefinitions(); - // Since debug support has been explicitly installed, no qualms about printing to console // TODO - That said: We could import probe.gl from luma.gl log.info('@luma.gl/debug: WebGL debug support installed'); // eslint-disable-line diff --git a/modules/debug/src/webgl-api-tracing/parameter-definitions.js b/modules/debug/src/webgl-api-tracing/parameter-definitions.js deleted file mode 100644 index 87a6b5ea44..0000000000 --- a/modules/debug/src/webgl-api-tracing/parameter-definitions.js +++ /dev/null @@ -1,555 +0,0 @@ -// Parameter support. -// Installs definitions that enable querying an object for all its parameters -// with resource.getParameters(). This is mainly useful during debugging. -// Note: Kept separate to avoid bundling in production applications - -import GL from '@luma.gl/constants'; - -// WebGL specification 'types' -export const GLenum = 'GLenum'; -export const GLfloat = 'GLfloat'; -export const GLint = 'GLint'; -export const GLuint = 'GLint'; -export const GLboolean = 'GLboolean'; - -/* - TODO - will most likely remove some of these fields from the main struct - but they can be useful for debugging/seer integration, so keep them here for now -export const DBG_PARAMETERS = { - blend: { - type: GLboolean, - params: GL.BLEND, - value: false, - setter: (gl, value) => value ? gl.enable(GL.BLEND) : gl.disable(GL.BLEND) - }, - - blendColor: { - type: new Float32Array(4), - value: new Float32Array([0, 0, 0, 0]), - params: GL.BLEND_COLOR, - setter: (gl, value) => gl.blendColor(...value) - }, - - blendEquation: { - type: [GLenum, GLenum], - object: ['rgb', 'alpha'], - alias: 'blendEquationSeparate', - value: [GL.FUNC_ADD, GL.FUNC_ADD], - params: [GL.BLEND_EQUATION_RGB, GL.BLEND_EQUATION_ALPHA], - setter: (gl, value) => gl.blendEquationSeparate(...value), - normalizeArgs: args => isArray(args) ? args : [args, args] - }, - - // blend func - blendFunc: { - type: [GLenum, GLenum, GLenum, GLenum], - object: ['srcRgb', 'dstRgb', 'srcAlpha', 'dstAlpha'], - value: [GL.ONE, GL.ZERO, GL.ONE, GL.ZERO], - params: [GL.BLEND_SRC_RGB, GL.BLEND_DST_RGB, GL.BLEND_SRC_ALPHA, GL.BLEND_DST_ALPHA], - setter: (gl, value) => gl.blendFuncSeparate(...value), - normalizeArgs: args => isArray(args) && args.length === 3 ? [...args, ...args] : args - }, - - clearColor: { - type: new Float32Array(4), - params: GL.COLOR_CLEAR_VALUE, - value: new Float32Array([0, 0, 0, 0]), // TBD - setter: (gl, value) => gl.clearColor(...value) - }, - - colorMask: { - type: [GLboolean, GLboolean, GLboolean, GLboolean], - params: GL.COLOR_WRITEMASK, - value: [true, true, true, true], - setter: (gl, value) => gl.colorMask(...value) - }, - - // TODO - We have a name clash here - cullFace: { - type: GLboolean, - params: GL.CULL_FACE, - value: false, - setter: (gl, value) => value ? gl.enable(GL.CULL_FACE) : gl.disable(GL.CULL_FACE) - }, - - cullFaceMode: { - type: GLenum, - params: GL.CULL_FACE_MODE, - value: GL.BACK, - setter: (gl, value) => gl.cullFace(value) - }, - - depthTest: { - type: GLboolean, - params: GL.DEPTH_TEST, - value: false, - setter: (gl, value) => value ? gl.enable(GL.DEPTH_TEST) : gl.disable(GL.DEPTH_TEST) - }, - - depthClearValue: { - type: GLfloat, - params: GL.DEPTH_CLEAR_VALUE, - value: 1, - setter: (gl, value) => gl.clearDepth(value) - }, - - depthFunc: { - type: GLenum, - params: GL.DEPTH_FUNC, - value: GL.LESS, - setter: (gl, value) => gl.depthFunc(value) - }, - - depthRange: { - type: new Float32Array(2), - object: ['min', 'max'], - params: GL.DEPTH_RANGE, - value: new Float32Array([0, 1]), // TBD - setter: (gl, value) => gl.depthRange(...value) - }, - - depthWritemask: { - type: GLboolean, - params: GL.DEPTH_WRITEMASK, - value: true, - setter: (gl, value) => gl.depthMask(value) - }, - - dither: { - type: GLboolean, - params: GL.DITHER, - value: true, - setter: (gl, value) => value ? gl.enable(GL.DITHER) : gl.disable(GL.DITHER) - }, - - fragmentShaderDerivativeHint: { - type: GLenum, - params: GL.FRAGMENT_SHADER_DERIVATIVE_HINT, - value: GL.DONT_CARE, - setter: (gl, value) => gl.hint(GL.FRAGMENT_SHADER_DERIVATIVE_HINT, value), - gl1: 'OES_standard_derivatives' - }, - - frontFace: { - type: GLenum, - params: GL.FRONT_FACE, - value: GL.CCW, - setter: (gl, value) => gl.frontFace(value) - }, - - // Hint for quality of images generated with glGenerateMipmap - generateMipmapHint: { - type: GLenum, - params: GL.GENERATE_MIPMAP_HINT, - value: GL.DONT_CARE, - setter: (gl, value) => gl.hint(GL.GENERATE_MIPMAP_HINT, value) - }, - - lineWidth: { - type: GLfloat, - params: GL.LINE_WIDTH, - value: 1, - setter: (gl, value) => gl.lineWidth(value) - }, - - polygonOffsetFill: { - type: GLboolean, - params: GL.POLYGON_OFFSET_FILL, - value: false, - setter: (gl, value) => - value ? gl.enable(GL.POLYGON_OFFSET_FILL) : gl.disable(GL.POLYGON_OFFSET_FILL) - }, - - // Add small offset to fragment depth values (by factor × DZ + r × units) - // Useful for rendering hidden-line images, for applying decals to surfaces, - // and for rendering solids with highlighted edges. - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glPolygonOffset.xml - polygonOffset: { - type: [GLfloat, GLfloat], - object: ['factor', 'units'], - params: [GL.POLYGON_OFFSET_FACTOR, GL.POLYGON_OFFSET_UNITS], - value: [0, 0], - setter: (gl, value) => gl.polygonOffset(...value) - }, - - // TODO - enabling multisampling - // glIsEnabled with argument GL_SAMPLE_ALPHA_TO_COVERAGE - // glIsEnabled with argument GL_SAMPLE_COVERAGE - - // specify multisample coverage parameters - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glSampleCoverage.xml - sampleCoverage: { - type: [GLfloat, GLboolean], - object: ['value', 'invert'], - params: [GL.SAMPLE_COVERAGE_VALUE, GL.SAMPLE_COVERAGE_INVERT], - value: [1.0, false], - setter: (gl, value) => gl.sampleCoverage(...value) - }, - - scissorTest: { - type: GLboolean, - params: GL.SCISSOR_TEST, - value: false, - setter: (gl, value) => value ? gl.enable(GL.SCISSOR_TEST) : gl.disable(GL.SCISSOR_TEST) - }, - - scissorBox: { - type: new Int32Array(4), - object: ['x', 'y', 'width', 'height'], - // When scissor test enabled we expect users to set correct scissor box, - // otherwise we default to following value array. - params: GL.SCISSOR_BOX, - value: new Int32Array([0, 0, 1024, 1024]), - setter: (gl, value) => gl.scissor(...value) - }, - - stencilTest: { - type: GLboolean, - params: GL.STENCIL_TEST, - value: false, - setter: (gl, value) => value ? gl.enable(GL.STENCIL_TEST) : gl.disable(GL.STENCIL_TEST) - }, - - // Sets index used when stencil buffer is cleared. - stencilClearValue: { - type: GLint, - params: GL.STENCIL_CLEAR_VALUE, - value: 0, - setter: (gl, value) => gl.clearStencil(value) - }, - - // Sets bit mask enabling writing of individual bits in the stencil planes - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glStencilMaskSeparate.xml - stencilMask: { - type: [GLuint, GLuint], - object: ['mask', 'backMask'], - value: [0xFFFFFFFF, 0xFFFFFFFF], - params: [GL.STENCIL_WRITEMASK, GL.STENCIL_BACK_WRITEMASK], - setter: (gl, value) => { - value = isArray(value) ? value : [value, value]; - const [mask, backMask] = value; - gl.stencilMaskSeparate(GL.FRONT, mask); - gl.stencilMaskSeparate(GL.BACK, backMask); - } - }, - - // Set stencil testing function, reference value and mask for front and back - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glStencilFuncSeparate.xml - stencilFunc: { - type: [GLenum, GLint, GLuint, GLenum, GLint, GLuint], - object: [ - 'func', 'ref', 'valueMask', 'backFunc', 'backRef', 'backValueMask' - ], - value: [GL.ALWAYS, 0, 0xFFFFFFFF, GL.ALWAYS, 0, 0xFFFFFFFF], - params: [ - // front - GL.STENCIL_FUNC, - GL.STENCIL_REF, - GL.STENCIL_VALUE_MASK, - // back - GL.STENCIL_BACK_FUNC, - GL.STENCIL_BACK_REF, - GL.STENCIL_BACK_VALUE_MASK - ], - setter: (gl, value) => { - const [func, ref, mask, backFunc, backRef, backMask] = value; - gl.stencilFuncSeparate(GL.FRONT, func, ref, mask); - gl.stencilFuncSeparate(GL.BACK, backFunc, backRef, backMask); - } - }, - - // Specifies the action to take when the stencil test fails, front and back. - // Stencil test fail action, depth test fail action, pass action - // GL.KEEP, GL.ZERO, GL.REPLACE, GL.INCR, GL.INCR_WRAP, GL.DECR, GL.DECR_WRAP, - // and GL.INVERT - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glStencilOpSeparate.xml - stencilOp: { - type: [GLenum, GLenum, GLenum, GLenum, GLenum, GLenum], - object: [ - 'fail', 'passDepthFail', 'passDepthPass', - 'backFail', 'backPassDepthFail', 'backPassDepthPass' - ], - params: [ - // front - GL.STENCIL_FAIL, - GL.STENCIL_PASS_DEPTH_FAIL, - GL.STENCIL_PASS_DEPTH_PASS, - // back - GL.STENCIL_BACK_FAIL, - GL.STENCIL_BACK_PASS_DEPTH_FAIL, - GL.STENCIL_BACK_PASS_DEPTH_PASS - ], - value: [GL.KEEP, GL.KEEP, GL.KEEP, GL.KEEP, GL.KEEP, GL.KEEP], - setter: (gl, value) => { - const [sfail, dpfail, dppass, backSfail, backDpfail, backDppass] = value; - gl.stencilOpSeparate(GL.FRONT, sfail, dpfail, dppass); - gl.stencilOpSeparate(GL.BACK, backSfail, backDpfail, backDppass); - } - }, - - viewport: { - type: new Int32Array(4), - object: ['x', 'y', 'width', 'height'], - // We use [0, 0, 1024, 1024] as default, but usually this is updated in each frame. - params: GL.VIEWPORT, - value: new Int32Array([0, 0, 1024, 1024]), - setter: (gl, value) => gl.viewport(...value) - }, - - // WEBGL1 PIXEL PACK/UNPACK MODES - - // Packing of pixel data in memory (1,2,4,8) - [GL.PACK_ALIGNMENT]: { - type: GLint, - params: GL.PACK_ALIGNMENT, - value: 4, - setter: (gl, value) => gl.pixelStorei(GL.PACK_ALIGNMENT, value) - }, - // Unpacking pixel data from memory(1,2,4,8) - [GL.UNPACK_ALIGNMENT]: { - type: GLint, - params: GL.UNPACK_ALIGNMENT, - value: 4, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_ALIGNMENT, value) - }, - // Flip source data along its vertical axis - [GL.UNPACK_FLIP_Y_WEBGL]: { - type: GLboolean, - params: GL.UNPACK_FLIP_Y_WEBGL, - value: false, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, value) - }, - // Multiplies the alpha channel into the other color channels - [GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL]: { - type: GLboolean, - params: GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, - value: false, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, value) - }, - // Default color space conversion or no color space conversion. - [GL.UNPACK_COLORSPACE_CONVERSION_WEBGL]: { - type: GLenum, - params: GL.UNPACK_COLORSPACE_CONVERSION_WEBGL, - value: GL.BROWSER_DEFAULT_WEBGL, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_COLORSPACE_CONVERSION_WEBGL, value) - }, - - // WEBGL2 PIXEL PACK/UNPACK MODES - - // Number of pixels in a row. - [GL.PACK_ROW_LENGTH]: { - type: GLint, - params: GL.PACK_ROW_LENGTH, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.PACK_ROW_LENGTH, value), - webgl2: true - }, - // Number of pixels skipped before the first pixel is written into memory. - [GL.PACK_SKIP_PIXELS]: { - type: GLint, - params: GL.PACK_SKIP_PIXELS, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.PACK_SKIP_PIXELS, value), - webgl2: true - }, - // Number of rows of pixels skipped before first pixel is written to memory. - [GL.PACK_SKIP_ROWS]: { - type: GLint, - params: GL.PACK_SKIP_ROWS, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.PACK_SKIP_ROWS, value), - webgl2: true - }, - // Number of pixels in a row. - [GL.UNPACK_ROW_LENGTH]: { - type: GLint, - params: GL.UNPACK_ROW_LENGTH, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_ROW_LENGTH, value), - webgl2: true - }, - // Image height used for reading pixel data from memory - [GL.UNPACK_IMAGE_HEIGHT]: { - type: GLint, - params: GL.UNPACK_IMAGE_HEIGHT, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_IMAGE_HEIGHT, value), - webgl2: true - }, - // Number of pixel images skipped before first pixel is read from memory - [GL.UNPACK_SKIP_PIXELS]: { - type: GLint, - params: GL.UNPACK_SKIP_PIXELS, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_SKIP_PIXELS, value), - webgl2: true - }, - // Number of rows of pixels skipped before first pixel is read from memory - [GL.UNPACK_SKIP_ROWS]: { - type: GLint, - params: GL.UNPACK_SKIP_ROWS, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_SKIP_ROWS, value), - webgl2: true - }, - // Number of pixel images skipped before first pixel is read from memory - [GL.UNPACK_SKIP_IMAGES]: { - type: GLint, - params: GL.UNPACK_SKIP_IMAGES, - value: 0, - setter: (gl, value) => gl.pixelStorei(GL.UNPACK_SKIP_IMAGES, value), - webgl2: true - } -}; -*/ - -export const BUFFER_PARAMETERS = { - [GL.BUFFER_SIZE]: {webgl1: 0}, // GLint indicating the size of the buffer in bytes. - [GL.BUFFER_USAGE]: {webgl1: 0} // GLenum indicating the usage pattern of the buffer. -}; - -export const FENCE_SYNC_PARAMETERS = [ - GL.OBJECT_TYPE, // GLenum, type of sync object (always GL.SYNC_FENCE). - GL.SYNC_STATUS, // GLenum, status of sync object (GL.SIGNALED/GL.UNSIGNALED) - GL.SYNC_CONDITION, // GLenum. object condition (always GL.SYNC_GPU_COMMANDS_COMPLETE). - GL.SYNC_FLAGS // GLenum, flags sync object was created with (always 0) -]; - -export const FRAMEBUFFER_ATTACHMENT_PARAMETERS = [ - GL.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, // WebGLRenderbuffer or WebGLTexture - GL.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, // GL.RENDERBUFFER, GL.TEXTURE, GL.NONE - GL.FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, // GL.TEXTURE_CUBE_MAP_POSITIVE_X, etc. - GL.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, // GLint - // EXT_sRGB or WebGL2 - GL.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, // GL.LINEAR, GL.SRBG - // WebGL2 - GL.FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, // GLint - GL.FRAMEBUFFER_ATTACHMENT_RED_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, // GLint - GL.FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE - // GL.FLOAT, GL.INT, GL.UNSIGNED_INT, GL.SIGNED_NORMALIZED, OR GL.UNSIGNED_NORMALIZED. -]; - -export const FRAMEBUFFER_STATUS = { - [GL.FRAMEBUFFER_COMPLETE]: 'Success. Framebuffer is correctly set up', - [GL.FRAMEBUFFER_INCOMPLETE_ATTACHMENT]: - 'Framebuffer attachment types mismatched or some attachment point not attachment complete', - [GL.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT]: 'Framebuffer has no attachment', - [GL.FRAMEBUFFER_INCOMPLETE_DIMENSIONS]: 'Framebuffer attachments do not have the same size', - [GL.FRAMEBUFFER_UNSUPPORTED]: - 'Framebuffer attachment format not supported or depth and stencil attachments are not same', - // When using a WebGL 2 context, the following values can be returned - [GL.FRAMEBUFFER_INCOMPLETE_MULTISAMPLE]: - 'Framebuffer attachement SAMPLES differs among renderbuffers, or are mixed with textures' -}; - -export const PROGRAM_PARAMETERS = { - [GL.DELETE_STATUS]: {webgl1: 0}, // GLboolean - [GL.LINK_STATUS]: {webgl1: 0}, // GLboolean - [GL.VALIDATE_STATUS]: {webgl1: 0}, // GLboolean - [GL.ATTACHED_SHADERS]: {webgl1: 0}, // GLint - [GL.ACTIVE_ATTRIBUTES]: {webgl1: 0}, // GLint - [GL.ACTIVE_UNIFORMS]: {webgl1: 0}, // GLint - [GL.TRANSFORM_FEEDBACK_BUFFER_MODE]: {webgl2: 0}, // SEPARATE_ATTRIBS/INTERLEAVED_ATTRIBS - [GL.TRANSFORM_FEEDBACK_VARYINGS]: {webgl2: 0}, // GLint - [GL.ACTIVE_UNIFORM_BLOCKS]: {webgl2: 0} // GLint -}; - -// parameters -export const RENDERBUFFER_PARAMETERS = { - // WebGL1 parameters - [GL.RENDERBUFFER_WIDTH]: {webgl1: 0}, // {GLint} - height of the image of renderbuffer. - [GL.RENDERBUFFER_HEIGHT]: {webgl1: 0}, // {GLint} - height of the image of renderbuffer. - - // Internal format of the currently bound renderbuffer. - // The default is GL.RGBA4. Possible return values: - // GL.RGBA4: 4 red bits, 4 green bits, 4 blue bits 4 alpha bits. - // GL.RGB565: 5 red bits, 6 green bits, 5 blue bits. - // GL.RGB5_A1: 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit. - // GL.DEPTH_COMPONENT16: 16 depth bits. - // GL.STENCIL_INDEX8: 8 stencil bits. - [GL.RENDERBUFFER_INTERNAL_FORMAT]: {type: 'GLenum', webgl1: GL.RGBA4}, - - [GL.RENDERBUFFER_GREEN_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of green color - [GL.RENDERBUFFER_BLUE_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of blue color - [GL.RENDERBUFFER_RED_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of red color - [GL.RENDERBUFFER_ALPHA_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of alpha component - [GL.RENDERBUFFER_DEPTH_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of depth component - [GL.RENDERBUFFER_STENCIL_SIZE]: {webgl1: 0}, // {GLint} - resolution (bits) of stencil component - - // When using a WebGL 2 context, the following value is available - [GL.RENDERBUFFER_SAMPLES]: {webgl2: 1} -}; - -export const SAMPLER_PARAMETERS = { - [GL.TEXTURE_MAG_FILTER]: {webgl2: true}, // texture magnification filter - [GL.TEXTURE_MIN_FILTER]: {webgl2: true}, // texture minification filter - [GL.TEXTURE_WRAP_S]: {webgl2: true}, // texture wrapping function for texture coordinate s - [GL.TEXTURE_WRAP_T]: {webgl2: true}, // texture wrapping function for texture coordinate t - [GL.TEXTURE_WRAP_R]: {webgl2: true}, // texture wrapping function for texture coordinate r - [GL.TEXTURE_BASE_LEVEL]: {webgl2: true}, // Texture mipmap level - [GL.TEXTURE_MAX_LEVEL]: {webgl2: true}, // Maximum texture mipmap array level - [GL.TEXTURE_COMPARE_FUNC]: {webgl2: true}, // texture comparison function - [GL.TEXTURE_COMPARE_MODE]: {webgl2: true}, // texture comparison mode - [GL.TEXTURE_MIN_LOD]: {webgl2: true}, // minimum level-of-detail value - [GL.TEXTURE_MAX_LOD]: {webgl2: true} // maximum level-of-detail value - - // [GL.TEXTURE_MAX_ANISOTROPY_EXT]: {webgl2: 'EXT_texture_filter_anisotropic'} -}; - -// const SHADER_PARAMETERS = [ -// GL.DELETE_STATUS, // GLboolean - whether shader is flagged for deletion. -// GL.COMPILE_STATUS, // GLboolean - was last shader compilation successful. -// GL.SHADER_TYPE // GLenum - GL.VERTEX_SHADER or GL.FRAGMENT_SHADER. -// ]; - -export const TEXTURE_PARAMETERS = { - // WEBGL1 - [GL.TEXTURE_MAG_FILTER]: {type: 'GLenum', webgl1: GL.LINEAR}, // texture magnification filter - [GL.TEXTURE_MIN_FILTER]: {type: 'GLenum', webgl1: GL.NEAREST_MIPMAP_LINEAR}, // minification filt. - [GL.TEXTURE_WRAP_S]: {type: 'GLenum', webgl1: GL.REPEAT}, // texture wrapping for coordinate s - [GL.TEXTURE_WRAP_T]: {type: 'GLenum', webgl1: GL.REPEAT}, // texture wrapping for texture t - - // WEBGL2 - [GL.TEXTURE_WRAP_R]: {type: 'GLenum', webgl2: GL.REPEAT}, // texture wrapping for texture r - [GL.TEXTURE_BASE_LEVEL]: {webgl2: 0}, // Texture mipmap level - [GL.TEXTURE_MAX_LEVEL]: {webgl2: 1000}, // Maximum texture mipmap array level - [GL.TEXTURE_COMPARE_FUNC]: {type: 'GLenum', webgl2: GL.LEQUAL}, // texture comparison function - [GL.TEXTURE_COMPARE_MODE]: {type: 'GLenum', webgl2: GL.NONE}, // texture comparison mode - [GL.TEXTURE_MIN_LOD]: {webgl2: -1000}, // minimum level-of-detail value - [GL.TEXTURE_MAX_LOD]: {webgl2: 1000}, // maximum level-of-detail value - - // WebGL Extensions - // [GL.TEXTURE_MAX_ANISOTROPY_EXT]: {webgl1: 1.0, extension: 'EXT_texture_filter_anisotropic'}, - - // Emulated parameters - These OpenGL parameters are not supported by OpenGL ES - [GL.TEXTURE_WIDTH]: {webgl1: 0}, - [GL.TEXTURE_HEIGHT]: {webgl1: 0} -}; - -// TODO - separate install parameter definitions function from api metadata -/* -import Buffer from '../buffer'; -import Framebuffer from '../framebuffer'; -import Program from '../program'; -import Renderbuffer from '../renderbuffer'; -import Sampler from '../sampler'; -// import Shader from './shader'; -import Texture from '../texture'; -*/ - -export function installParameterDefinitions() { - // Buffer.PARAMETERS = BUFFER_PARAMETERS; - // FenceSync.PARAMETERS = FENCE_SYNC_PARAMETERS; - // Framebuffer.ATTACHMENT_PARAMETERS = FRAMEBUFFER_ATTACHMENT_PARAMETERS; - // Framebuffer.STATUS = FRAMEBUFFER_STATUS; - // Program.PARAMETERS = PROGRAM_PARAMETERS; - // Renderbuffer.PARAMETERS = RENDERBUFFER_PARAMETERS; - // Sampler.PARAMETERS = SAMPLER_PARAMETERS; - // // Shader.PARAMETERS = SHADER_PARAMETERS; - // Texture.PARAMETERS = TEXTURE_PARAMETERS; -} diff --git a/modules/webgl/src/classes/program.js b/modules/webgl/src/classes/program.js index 0534964fa4..0480a1e8b8 100644 --- a/modules/webgl/src/classes/program.js +++ b/modules/webgl/src/classes/program.js @@ -6,7 +6,7 @@ import Framebuffer from './framebuffer'; import {parseUniformName, getUniformSetter} from './uniforms'; import {VertexShader, FragmentShader} from './shader'; import ProgramConfiguration from './program-configuration'; -import {checkUniformValues, areUniformsEqual, getUniformCopy} from './uniforms'; +import {copyUniform, checkUniformValues} from './uniforms'; import {withParameters} from '../context'; import {assertWebGL2Context, isWebGL2, getKey} from '../webgl-utils'; @@ -54,9 +54,10 @@ export default class Program extends Resource { } initialize(props = {}) { - const {hash, vs, fs, varyings, bufferMode = GL_SEPARATE_ATTRIBS} = props; + const {hash, vs, fs, varyings, bufferMode = GL_SEPARATE_ATTRIBS, debug = false} = props; this.hash = hash || ''; // Used by ProgramManager + this.debug = debug; // Create shaders if needed this.vs = @@ -69,6 +70,8 @@ export default class Program extends Resource { // uniforms this.uniforms = {}; + this._texturesRenderable = true; + // Setup varyings if supplied if (varyings && varyings.length > 0) { assertWebGL2Context(this.gl); @@ -128,7 +131,7 @@ export default class Program extends Resource { this.setUniforms(uniforms || {}); } - if (logPriority !== undefined) { + if (this.debug && logPriority !== undefined) { const fb = framebuffer ? framebuffer.id : 'default'; const message = `mode=${getKey(this.gl, drawMode)} verts=${vertexCount} ` + @@ -185,22 +188,51 @@ export default class Program extends Resource { return true; } - setUniforms(uniforms = {}, _onChangeCallback = () => {}) { - // Simple change detection - if all uniforms are unchanged, do nothing - let somethingChanged = false; - const changedUniforms = {}; - for (const key in uniforms) { - if (!areUniformsEqual(this.uniforms[key], uniforms[key])) { - somethingChanged = true; - changedUniforms[key] = uniforms[key]; - this.uniforms[key] = getUniformCopy(uniforms[key]); - } + setUniforms(uniforms = {}) { + if (this.debug) { + checkUniformValues(uniforms, this.id, this._uniformSetters); } - if (somethingChanged) { - _onChangeCallback(); - checkUniformValues(changedUniforms, this.id, this._uniformSetters); - this._setUniforms(changedUniforms); + this.gl.useProgram(this.handle); + + for (const uniformName in uniforms) { + const uniform = uniforms[uniformName]; + const uniformSetter = this._uniformSetters[uniformName]; + + if (uniformSetter) { + let value = uniform; + let textureUpdate = false; + if (value instanceof Framebuffer) { + value = value.texture; + } + if (value instanceof Texture) { + textureUpdate = this.uniforms[uniformName] !== uniform; + + if (textureUpdate) { + // eslint-disable-next-line max-depth + if (uniformSetter.textureIndex === undefined) { + uniformSetter.textureIndex = this._textureIndexCounter++; + } + + // Bind texture to index + const texture = value; + const {textureIndex} = uniformSetter; + + texture.bind(textureIndex); + value = textureIndex; + + if (!texture.loaded) { + this._texturesRenderable = false; + } + } + } + + // NOTE(Tarek): uniformSetter returns whether + // value had to be updated or not. + if (uniformSetter(value) || textureUpdate) { + copyUniform(this.uniforms, uniformName, uniform); + } + } } return this; @@ -208,13 +240,14 @@ export default class Program extends Resource { // PRIVATE METHODS - // stub for shader chache, should reset uniforms to default valiues - reset() {} - // Checks if all texture-values uniforms are renderable (i.e. loaded) // Note: This is currently done before every draw call _areTexturesRenderable() { - let texturesRenderable = true; + if (this._texturesRenderable) { + return true; + } + + this._texturesRenderable = true; for (const uniformName in this.uniforms) { const uniformSetter = this._uniformSetters[uniformName]; @@ -230,12 +263,12 @@ export default class Program extends Resource { if (uniform instanceof Texture) { const texture = uniform; // Check that texture is loaded - texturesRenderable = texturesRenderable && texture.loaded; + this._texturesRenderable = this._texturesRenderable && texture.loaded; } } } - return texturesRenderable; + return this._texturesRenderable; } // Binds textures @@ -259,43 +292,6 @@ export default class Program extends Resource { } } - // Apply a set of uniform values to a program - // Only uniforms actually present in the linked program will be updated. - _setUniforms(uniforms) { - this.gl.useProgram(this.handle); - - for (const uniformName in uniforms) { - let uniform = uniforms[uniformName]; - const uniformSetter = this._uniformSetters[uniformName]; - - if (uniformSetter) { - if (uniform instanceof Framebuffer) { - uniform = uniform.texture; - } - if (uniform instanceof Texture) { - // eslint-disable-next-line max-depth - if (uniformSetter.textureIndex === undefined) { - uniformSetter.textureIndex = this._textureIndexCounter++; - } - - // Bind texture to index - const texture = uniform; - const {textureIndex} = uniformSetter; - - texture.bind(textureIndex); - - // Set the uniform sampler to the texture index - uniformSetter(textureIndex); - } else { - // Just set the value - uniformSetter(uniform); - } - } - } - - return this; - } - // RESOURCE METHODS _createHandle() { diff --git a/modules/webgl/src/classes/uniforms.js b/modules/webgl/src/classes/uniforms.js index 081534da90..10f5ee421b 100644 --- a/modules/webgl/src/classes/uniforms.js +++ b/modules/webgl/src/classes/uniforms.js @@ -2,72 +2,123 @@ import GL from '@luma.gl/constants'; import Framebuffer from './framebuffer'; import Renderbuffer from './renderbuffer'; import Texture from './texture'; -import {log} from '../utils'; +import {log, assert} from '../utils'; const UNIFORM_SETTERS = { // WEBGL1 /* eslint-disable max-len */ - [GL.FLOAT]: (gl, location, value) => gl.uniform1fv(location, toFloatArray(value, 1)), - [GL.FLOAT_VEC2]: (gl, location, value) => gl.uniform2fv(location, toFloatArray(value, 2)), - [GL.FLOAT_VEC3]: (gl, location, value) => gl.uniform3fv(location, toFloatArray(value, 3)), - [GL.FLOAT_VEC4]: (gl, location, value) => gl.uniform4fv(location, toFloatArray(value, 4)), + [GL.FLOAT]: getArraySetter.bind(null, 'uniform1fv', toFloatArray, 1, setVectorUniform), + [GL.FLOAT_VEC2]: getArraySetter.bind(null, 'uniform2fv', toFloatArray, 2, setVectorUniform), + [GL.FLOAT_VEC3]: getArraySetter.bind(null, 'uniform3fv', toFloatArray, 3, setVectorUniform), + [GL.FLOAT_VEC4]: getArraySetter.bind(null, 'uniform4fv', toFloatArray, 4, setVectorUniform), - [GL.INT]: (gl, location, value) => gl.uniform1iv(location, toIntArray(value, 1)), - [GL.INT_VEC2]: (gl, location, value) => gl.uniform2iv(location, toIntArray(value, 2)), - [GL.INT_VEC3]: (gl, location, value) => gl.uniform3iv(location, toIntArray(value, 3)), - [GL.INT_VEC4]: (gl, location, value) => gl.uniform4iv(location, toIntArray(value, 4)), + [GL.INT]: getArraySetter.bind(null, 'uniform1iv', toIntArray, 1, setVectorUniform), + [GL.INT_VEC2]: getArraySetter.bind(null, 'uniform2iv', toIntArray, 2, setVectorUniform), + [GL.INT_VEC3]: getArraySetter.bind(null, 'uniform3iv', toIntArray, 3, setVectorUniform), + [GL.INT_VEC4]: getArraySetter.bind(null, 'uniform4iv', toIntArray, 4, setVectorUniform), - [GL.BOOL]: (gl, location, value) => gl.uniform1iv(location, toIntArray(value, 1)), - [GL.BOOL_VEC2]: (gl, location, value) => gl.uniform2iv(location, toIntArray(value, 2)), - [GL.BOOL_VEC3]: (gl, location, value) => gl.uniform3iv(location, toIntArray(value, 3)), - [GL.BOOL_VEC4]: (gl, location, value) => gl.uniform4iv(location, toIntArray(value, 4)), + [GL.BOOL]: getArraySetter.bind(null, 'uniform1iv', toIntArray, 1, setVectorUniform), + [GL.BOOL_VEC2]: getArraySetter.bind(null, 'uniform2iv', toIntArray, 2, setVectorUniform), + [GL.BOOL_VEC3]: getArraySetter.bind(null, 'uniform3iv', toIntArray, 3, setVectorUniform), + [GL.BOOL_VEC4]: getArraySetter.bind(null, 'uniform4iv', toIntArray, 4, setVectorUniform), // uniformMatrix(false): don't transpose the matrix - [GL.FLOAT_MAT2]: (gl, location, value) => - gl.uniformMatrix2fv(location, false, toFloatArray(value, 4)), - [GL.FLOAT_MAT3]: (gl, location, value) => - gl.uniformMatrix3fv(location, false, toFloatArray(value, 9)), - [GL.FLOAT_MAT4]: (gl, location, value) => - gl.uniformMatrix4fv(location, false, toFloatArray(value, 16)), + [GL.FLOAT_MAT2]: getArraySetter.bind(null, 'uniformMatrix2fv', toFloatArray, 4, setMatrixUniform), + [GL.FLOAT_MAT3]: getArraySetter.bind(null, 'uniformMatrix3fv', toFloatArray, 9, setMatrixUniform), + [GL.FLOAT_MAT4]: getArraySetter.bind( + null, + 'uniformMatrix4fv', + toFloatArray, + 16, + setMatrixUniform + ), - [GL.SAMPLER_2D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.SAMPLER_CUBE]: (gl, location, value) => gl.uniform1i(location, value), + [GL.SAMPLER_2D]: getSamplerSetter, + [GL.SAMPLER_CUBE]: getSamplerSetter, // WEBGL2 - unsigned integers, irregular matrices, additional texture samplers - [GL.UNSIGNED_INT]: (gl, location, value) => gl.uniform1uiv(location, toUIntArray(value, 1)), - [GL.UNSIGNED_INT_VEC2]: (gl, location, value) => gl.uniform2uiv(location, toUIntArray(value, 2)), - [GL.UNSIGNED_INT_VEC3]: (gl, location, value) => gl.uniform3uiv(location, toUIntArray(value, 3)), - [GL.UNSIGNED_INT_VEC4]: (gl, location, value) => gl.uniform4uiv(location, toUIntArray(value, 4)), + [GL.UNSIGNED_INT]: getArraySetter.bind(null, 'uniform1uiv', toUIntArray, 1, setVectorUniform), + [GL.UNSIGNED_INT_VEC2]: getArraySetter.bind( + null, + 'uniform2uiv', + toUIntArray, + 2, + setVectorUniform + ), + [GL.UNSIGNED_INT_VEC3]: getArraySetter.bind( + null, + 'uniform3uiv', + toUIntArray, + 3, + setVectorUniform + ), + [GL.UNSIGNED_INT_VEC4]: getArraySetter.bind( + null, + 'uniform4uiv', + toUIntArray, + 4, + setVectorUniform + ), // uniformMatrix(false): don't transpose the matrix - [GL.FLOAT_MAT2x3]: (gl, location, value) => - gl.uniformMatrix2x3fv(location, false, toFloatArray(value, 6)), - [GL.FLOAT_MAT2x4]: (gl, location, value) => - gl.uniformMatrix2x4fv(location, false, toFloatArray(value, 8)), - [GL.FLOAT_MAT3x2]: (gl, location, value) => - gl.uniformMatrix3x2fv(location, false, toFloatArray(value, 6)), - [GL.FLOAT_MAT3x4]: (gl, location, value) => - gl.uniformMatrix3x4fv(location, false, toFloatArray(value, 12)), - [GL.FLOAT_MAT4x2]: (gl, location, value) => - gl.uniformMatrix4x2fv(location, false, toFloatArray(value, 8)), - [GL.FLOAT_MAT4x3]: (gl, location, value) => - gl.uniformMatrix4x3fv(location, false, toFloatArray(value, 12)), - - [GL.SAMPLER_3D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.SAMPLER_2D_SHADOW]: (gl, location, value) => gl.uniform1i(location, value), - [GL.SAMPLER_2D_ARRAY]: (gl, location, value) => gl.uniform1i(location, value), - [GL.SAMPLER_2D_ARRAY_SHADOW]: (gl, location, value) => gl.uniform1i(location, value), - [GL.SAMPLER_CUBE_SHADOW]: (gl, location, value) => gl.uniform1i(location, value), - [GL.INT_SAMPLER_2D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.INT_SAMPLER_3D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.INT_SAMPLER_CUBE]: (gl, location, value) => gl.uniform1i(location, value), - [GL.INT_SAMPLER_2D_ARRAY]: (gl, location, value) => gl.uniform1i(location, value), - [GL.UNSIGNED_INT_SAMPLER_2D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.UNSIGNED_INT_SAMPLER_3D]: (gl, location, value) => gl.uniform1i(location, value), - [GL.UNSIGNED_INT_SAMPLER_CUBE]: (gl, location, value) => gl.uniform1i(location, value), - [GL.UNSIGNED_INT_SAMPLER_2D_ARRAY]: (gl, location, value) => gl.uniform1i(location, value) + [GL.FLOAT_MAT2x3]: getArraySetter.bind( + null, + 'uniformMatrix2x3fv', + toFloatArray, + 6, + setMatrixUniform + ), + [GL.FLOAT_MAT2x4]: getArraySetter.bind( + null, + 'uniformMatrix2x4fv', + toFloatArray, + 8, + setMatrixUniform + ), + [GL.FLOAT_MAT3x2]: getArraySetter.bind( + null, + 'uniformMatrix3x2fv', + toFloatArray, + 6, + setMatrixUniform + ), + [GL.FLOAT_MAT3x4]: getArraySetter.bind( + null, + 'uniformMatrix3x4fv', + toFloatArray, + 12, + setMatrixUniform + ), + [GL.FLOAT_MAT4x2]: getArraySetter.bind( + null, + 'uniformMatrix4x2fv', + toFloatArray, + 8, + setMatrixUniform + ), + [GL.FLOAT_MAT4x3]: getArraySetter.bind( + null, + 'uniformMatrix4x3fv', + toFloatArray, + 12, + setMatrixUniform + ), + + [GL.SAMPLER_3D]: getSamplerSetter, + [GL.SAMPLER_2D_SHADOW]: getSamplerSetter, + [GL.SAMPLER_2D_ARRAY]: getSamplerSetter, + [GL.SAMPLER_2D_ARRAY_SHADOW]: getSamplerSetter, + [GL.SAMPLER_CUBE_SHADOW]: getSamplerSetter, + [GL.INT_SAMPLER_2D]: getSamplerSetter, + [GL.INT_SAMPLER_3D]: getSamplerSetter, + [GL.INT_SAMPLER_CUBE]: getSamplerSetter, + [GL.INT_SAMPLER_2D_ARRAY]: getSamplerSetter, + [GL.UNSIGNED_INT_SAMPLER_2D]: getSamplerSetter, + [GL.UNSIGNED_INT_SAMPLER_3D]: getSamplerSetter, + [GL.UNSIGNED_INT_SAMPLER_CUBE]: getSamplerSetter, + [GL.UNSIGNED_INT_SAMPLER_2D_ARRAY]: getSamplerSetter /* eslint-enable max-len */ }; @@ -79,7 +130,8 @@ const UINT_ARRAY = {}; const array1 = [0]; // Functions to ensure the type of uniform values -// TODO - Why is this necessary? The uniform*v funtions can consume Arrays +// This is done because uniform*v functions +// are extremely slow when consuming JS arrays directly. function toTypedArray(value, uniformLength, Type, cache) { // convert boolean uniforms to Number if (uniformLength === 1 && typeof value === 'boolean') { @@ -151,7 +203,10 @@ export function getUniformSetter(gl, location, info) { if (!setter) { throw new Error(`Unknown GLSL uniform type ${info.type}`); } - return setter.bind(null, gl, location); + + // NOTE(Tarek): This construction is the ensure + // separate caches for all setters. + return setter().bind(null, gl, location); } // Basic checks of uniform values (with or without knowledge of program) @@ -211,33 +266,71 @@ function checkUniformArray(value) { } /** - * Given two values of a uniform, returns `true` if they are equal + * Creates a copy of the uniform */ -export function areUniformsEqual(uniform1, uniform2) { - if (Array.isArray(uniform1) || ArrayBuffer.isView(uniform1)) { - if (!uniform2) { - return false; +export function copyUniform(uniforms, key, value) { + if (Array.isArray(value) || ArrayBuffer.isView(value)) { + if (uniforms[key]) { + const dest = uniforms[key]; + for (let i = 0, len = value.length; i < len; ++i) { + dest[i] = value[i]; + } + } else { + uniforms[key] = value.slice(); } - const len = uniform1.length; - if (uniform2.length !== len) { - return false; + } else { + uniforms[key] = value; + } +} +// NOTE(Tarek): Setters maintain a cache +// of the previously set value, and +// avoid resetting it if it's the same. +function getSamplerSetter() { + let cache = null; + return (gl, location, value) => { + const update = cache !== value; + if (update) { + gl.uniform1i(location, value); + cache = value; } - for (let i = 0; i < len; i++) { - if (uniform1[i] !== uniform2[i]) { - return false; + + return update; + }; +} + +function getArraySetter(functionName, toArray, size, uniformSetter) { + let cache = null; + let cacheLength = null; + return (gl, location, value) => { + const arrayValue = toArray(value, size); + const length = arrayValue.length; + let update = false; + if (cache === null) { + cache = new Float32Array(length); + cacheLength = length; + update = true; + } else { + assert(cacheLength === length, 'Uniform length cannot change.'); + for (let i = 0; i < length; ++i) { + if (arrayValue[i] !== cache[i]) { + update = true; + break; + } } } - return true; - } - return uniform1 === uniform2; + if (update) { + uniformSetter(gl, functionName, location, arrayValue); + cache.set(arrayValue); + } + + return update; + }; } -/** - * Creates a copy of the uniform - */ -export function getUniformCopy(uniform) { - if (Array.isArray(uniform) || ArrayBuffer.isView(uniform)) { - return uniform.slice(); - } - return uniform; +function setVectorUniform(gl, functionName, location, value) { + gl[functionName](location, value); +} + +function setMatrixUniform(gl, functionName, location, value) { + gl[functionName](location, false, value); } diff --git a/modules/webgl/src/classes/vertex-array.js b/modules/webgl/src/classes/vertex-array.js index 8be7bf1da1..4d3ec804e4 100644 --- a/modules/webgl/src/classes/vertex-array.js +++ b/modules/webgl/src/classes/vertex-array.js @@ -109,12 +109,11 @@ export default class VertexArray { this.drawParams = null; } - getDrawParams(appParameters) { + getDrawParams() { // Auto deduced draw parameters this.drawParams = this.drawParams || this._updateDrawParams(); - // Override with any application supplied draw parameters - return Object.assign({}, this.drawParams, appParameters); + return this.drawParams; } // Set (bind) an array or map of vertex array buffers, either in numbered or named locations. diff --git a/modules/webgl/test/classes/uniform-cache-test-cases.js b/modules/webgl/test/classes/uniform-cache-test-cases.js new file mode 100644 index 0000000000..35656e86fe --- /dev/null +++ b/modules/webgl/test/classes/uniform-cache-test-cases.js @@ -0,0 +1,123 @@ +import GL from '@luma.gl/constants'; + +export const CACHING_TEST_CASES = [ + { + function: 'uniform1fv', + arrayType: Float32Array, + glType: GL.FLOAT, + data1: 0, + data2: 1 + }, + { + function: 'uniform2fv', + arrayType: Float32Array, + glType: GL.FLOAT, + data1: [0, 1], + data2: [1, 1] + }, + { + function: 'uniform3fv', + arrayType: Float32Array, + glType: GL.FLOAT, + data1: [0, 1, 2], + data2: [1, 1, 2] + }, + { + function: 'uniform4fv', + arrayType: Float32Array, + glType: GL.FLOAT, + data1: [0, 1, 2, 3], + data2: [1, 1, 2, 3] + }, + { + function: 'uniform1iv', + arrayType: Int32Array, + glType: GL.INT, + data1: 0, + data2: 1 + }, + { + function: 'uniform2iv', + arrayType: Int32Array, + glType: GL.INT, + data1: [0, 1], + data2: [1, 1] + }, + { + function: 'uniform3iv', + arrayType: Int32Array, + glType: GL.INT, + data1: [0, 1, 2], + data2: [1, 1, 2] + }, + { + function: 'uniform4iv', + arrayType: Int32Array, + glType: GL.INT, + data1: [0, 1, 2, 3], + data2: [1, 1, 2, 3] + }, + { + function: 'uniform1uiv', + arrayType: Uint32Array, + glType: GL.UNSIGNED_INT, + data1: 0, + data2: 1 + }, + { + function: 'uniform2uiv', + arrayType: Uint32Array, + glType: GL.UNSIGNED_INT, + data1: [0, 1], + data2: [1, 1] + }, + { + function: 'uniform3uiv', + arrayType: Uint32Array, + glType: GL.UNSIGNED_INT, + data1: [0, 1, 2], + data2: [1, 1, 2] + }, + { + function: 'uniform4uiv', + arrayType: Uint32Array, + glType: GL.UNSIGNED_INT, + data1: [0, 1, 2, 3], + data2: [1, 1, 2, 3] + }, + { + function: 'uniformMatrix2fv', + arrayType: Float32Array, + glType: GL.FLOAT_MAT2, + data1: [0, 1, 2, 3], + data2: [1, 1, 2, 3] + }, + { + function: 'uniformMatrix3fv', + arrayType: Float32Array, + glType: GL.FLOAT_MAT3, + data1: [0, 1, 2, 3, 4, 5, 6, 7, 8], + data2: [1, 1, 2, 3, 4, 5, 6, 7, 8] + }, + { + function: 'uniformMatrix4fv', + arrayType: Float32Array, + glType: GL.FLOAT_MAT4, + data1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + data2: [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + }, + { + function: 'uniform1i', + arrayType: null, + glType: GL.SAMPLER_2D, + data1: 1, + data2: 2 + }, + { + function: 'uniform1i', + arrayType: null, + glType: GL.SAMPLER_CUBE, + data1: 1, + data2: 2 + } +]; diff --git a/modules/webgl/test/classes/uniforms.spec.js b/modules/webgl/test/classes/uniforms.spec.js index 7ad55ddd9e..a584aace89 100644 --- a/modules/webgl/test/classes/uniforms.spec.js +++ b/modules/webgl/test/classes/uniforms.spec.js @@ -5,12 +5,14 @@ import {isBrowser} from '@luma.gl/webgl/utils'; import {equals} from 'math.gl'; import { checkUniformValues, - areUniformsEqual, - parseUniformName + parseUniformName, + getUniformSetter } from '@luma.gl/webgl/classes/uniforms'; import {fixture} from 'test/setup'; +import {CACHING_TEST_CASES} from './uniform-cache-test-cases'; + const MATRIX_2 = [1, 0, 0, 1]; const MATRIX_3 = [1, 0, 0, 0, 1, 0, 0, 0, 1]; @@ -133,7 +135,6 @@ const WEBGL1_GOOD_UNIFORMS = { m4: new Float32Array(MATRIX_4), // FLOAT_MAT4 0x8B5C s2d: new Texture2D(fixture.gl) // SAMPLER_2D 0x8B5E - // sCube: new TextureCube(gl) // SAMPLER_CUBE 0x8B60 }; const ARRAY_UNIFORM_SIZE = { @@ -145,74 +146,9 @@ const WEBGL1_GOOD_UNIFORMS_ARRAY = { i: new Int32Array([40, -103, 34, 87, 26]), b: new Int32Array([false, false, true, true, false]) }; - -// const WEBGL1_ARRAYS_FRAGMENT_SHADER = ` -// precision highp float; - -// uniform float f[3]; -// uniform int i[3]; -// uniform bool b[3]; -// uniform vec2 v2[3]; -// uniform vec3 v3[3]; -// uniform vec4 v4[3]; -// // int vectors -// // bool vectors -// uniform mat2 m2[3]; -// uniform mat3 m3[3]; -// uniform mat4 m4[3]; - -// uniform sampler2D s2d[5]; -// // uniform samplerCube sCube; - -// void main(void) { -// gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); -// } -// `; - -// const WEBGL1_ARRAYS_GOOD_UNIFORMS = { -// f: 1.0, -// i: 1, -// b: true, -// v2: new Float32Array([...[1, 2], ...[1, 2], ...[1, 2]]), -// v3: new Float32Array([...[1, 2, 3], ...[1, 2, 3], ...[1, 2, 3]]), -// v4: new Float32Array([...[1, 2, 3, 4], ...[1, 2, 3, 4], ...[1, 2, 3, 4]]), -// // INT_VEC2 0x8B53 -// // INT_VEC3 0x8B54 -// // INT_VEC4 0x8B55 -// // BOOL 0x8B56 -// // BOOL_VEC2 0x8B57 -// // BOOL_VEC3 0x8B58 -// // BOOL_VEC4 0x8B59 -// m2: new Float32Array([...MATRIX_2, ...MATRIX_2, ...MATRIX_2]), -// m3: new Float32Array([...MATRIX_3, ...MATRIX_3, ...MATRIX_3]), -// m4: new Float32Array([...MATRIX_4, ...MATRIX_4, ...MATRIX_4]), - -// s2d: [new Texture2D(gl), new Texture2D(gl), new Texture2D(gl)] -// // sCube: new TextureCube(gl) // SAMPLER_CUBE 0x8B60 -// }; - -// const WEBGL2_FRAGMENT_SHADER = ` -// precision highp float; -// uniform sampler1D; -// uniform sampler3D; - -// void main(void) { -// gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); -// } -// `; - -// const WEBGL2_GOOD_UNIFORMS = { -// s1d: 2, // SAMPLER_1D 0x8B5E -// s3d: 3 // SAMPLER_3D 0x8B60 -// }; - -// const BUFFER_DATA = new Float32Array([0, 1, 0, -1, -1, 0, 1, -1, 0]); - test('WebGL#Uniforms pre verify uniforms', t => { t.ok(checkUniformValues(WEBGL1_GOOD_UNIFORMS, 'Uniform values are well formed')); - // t.throws() - t.end(); }); @@ -384,75 +320,6 @@ test('WebGL2#Uniforms Program setUniforms for scalar arrays', t => { } }); -test('WebGL#Uniforms areUniformsEqual', t => { - const {gl} = fixture; - - const TEST_TEXTURE = new Texture2D(gl); - - const TEST_CASES = [ - { - title: 'Numeric values', - value1: 1, - value2: 1, - equals: true - }, - { - title: 'Numeric values', - value1: 1, - value2: 2, - equals: false - }, - { - title: 'Texture objects', - value1: TEST_TEXTURE, - value2: TEST_TEXTURE, - equals: true - }, - { - title: 'Texture objects', - value1: TEST_TEXTURE, - value2: new Texture2D(gl), - equals: false - }, - { - title: 'null', - value1: null, - value2: null, - equals: true - }, - { - title: 'Array vs array', - value1: [0, 0, 0], - value2: [0, 0, 0], - equals: true - }, - { - title: 'TypedArray vs array', - value1: new Float32Array(3), - value2: [0, 0, 0], - equals: true - }, - { - title: 'Array different length', - value1: [0, 0, 0, 0], - value2: new Float32Array(3), - equals: false - }, - { - title: 'Array vs null', - value1: new Float32Array(3), - value2: null, - equals: false - } - ]; - - TEST_CASES.forEach(testCase => { - t.is(areUniformsEqual(testCase.value1, testCase.value2), testCase.equals, testCase.title); - }); - - t.end(); -}); - test('WebGL#Uniforms parseUniformName', t => { const regularUniform = parseUniformName('position'); t.equal(regularUniform.name, 'position'); @@ -467,3 +334,52 @@ test('WebGL#Uniforms parseUniformName', t => { t.ok(!structArrayUniform.isArray); t.end(); }); + +test('WebGL#Uniforms caching', t => { + let called = false; + + const setCalled = () => { + called = true; + }; + + const checkCalled = (expected, fn) => { + called = false; + fn(); + t.ok(expected === called, 'Uniform setter was correctly cached.'); + }; + + const glStub = {}; + + CACHING_TEST_CASES.forEach(testCase => { + if (!glStub[testCase.function]) { + glStub[testCase.function] = getUniformStub(t, testCase.arrayType, setCalled); + } + }); + + CACHING_TEST_CASES.forEach(testCase => { + const setter = getUniformSetter(glStub, null, {type: testCase.glType}); + + checkCalled(true, () => setter(testCase.data1)); + checkCalled(false, () => setter(testCase.data1)); + checkCalled(true, () => setter(testCase.data2)); + }); + + t.end(); +}); + +function getUniformStub(t, arrayType, called) { + return (location, transpose, value) => { + if (value === undefined) { + // Not matrix + value = transpose; + } + + if (arrayType) { + t.equals(arrayType, value.constructor, 'Value is correct type.'); + } else { + t.ok(!(Array.isArray(value) || ArrayBuffer.isView(value)), 'Value is not array'); + } + + called(); + }; +}