goog.provide('ol.webgl.Context'); goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.log'); goog.require('goog.object'); goog.require('ol.structs.Buffer'); goog.require('ol.structs.IntegerSet'); goog.require('ol.webgl.WebGLContextEventType'); /** * @typedef {{buf: ol.structs.Buffer, * buffer: WebGLBuffer, * dirtySet: ol.structs.IntegerSet}} */ ol.webgl.BufferCacheEntry; /** * @classdesc * A WebGL context for accessing low-level WebGL capabilities. * * @constructor * @extends {goog.events.EventTarget} * @param {HTMLCanvasElement} canvas Canvas. * @param {WebGLRenderingContext} gl GL. * @todo api */ ol.webgl.Context = function(canvas, gl) { /** * @private * @type {HTMLCanvasElement} */ this.canvas_ = canvas; /** * @private * @type {WebGLRenderingContext} */ this.gl_ = gl; /** * @private * @type {Object.} */ this.bufferCache_ = {}; /** * @private * @type {Object.} */ this.shaderCache_ = {}; /** * @private * @type {Object.} */ this.programCache_ = {}; /** * @private * @type {WebGLProgram} */ this.currentProgram_ = null; goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.LOST, this.handleWebGLContextLost, false, this); goog.events.listen(this.canvas_, ol.webgl.WebGLContextEventType.RESTORED, this.handleWebGLContextRestored, false, this); }; /** * @param {number} target Target. * @param {ol.structs.Buffer} buf Buffer. */ ol.webgl.Context.prototype.bindBuffer = function(target, buf) { var gl = this.getGL(); var arr = buf.getArray(); var bufferKey = goog.getUid(buf); if (bufferKey in this.bufferCache_) { var bufferCacheEntry = this.bufferCache_[bufferKey]; gl.bindBuffer(target, bufferCacheEntry.buffer); bufferCacheEntry.dirtySet.forEachRange(function(start, stop) { // FIXME check if slice is really efficient here var slice = arr.slice(start, stop); gl.bufferSubData( target, start, target == goog.webgl.ARRAY_BUFFER ? new Float32Array(slice) : new Uint16Array(slice)); }); bufferCacheEntry.dirtySet.clear(); } else { var buffer = gl.createBuffer(); gl.bindBuffer(target, buffer); gl.bufferData( target, target == goog.webgl.ARRAY_BUFFER ? new Float32Array(arr) : new Uint16Array(arr), buf.getUsage()); var dirtySet = new ol.structs.IntegerSet(); buf.addDirtySet(dirtySet); this.bufferCache_[bufferKey] = { buf: buf, buffer: buffer, dirtySet: dirtySet }; } }; /** * @param {ol.structs.Buffer} buf Buffer. */ ol.webgl.Context.prototype.deleteBuffer = function(buf) { var gl = this.getGL(); var bufferKey = goog.getUid(buf); goog.asserts.assert(bufferKey in this.bufferCache_); var bufferCacheEntry = this.bufferCache_[bufferKey]; bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet); if (!gl.isContextLost()) { gl.deleteBuffer(bufferCacheEntry.buffer); } delete this.bufferCache_[bufferKey]; }; /** * @inheritDoc */ ol.webgl.Context.prototype.disposeInternal = function() { goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { bufferCacheEntry.buf.removeDirtySet(bufferCacheEntry.dirtySet); }); var gl = this.getGL(); if (!gl.isContextLost()) { goog.object.forEach(this.bufferCache_, function(bufferCacheEntry) { gl.deleteBuffer(bufferCacheEntry.buffer); }); goog.object.forEach(this.programCache_, function(program) { gl.deleteProgram(program); }); goog.object.forEach(this.shaderCache_, function(shader) { gl.deleteShader(shader); }); } }; /** * @return {HTMLCanvasElement} Canvas. */ ol.webgl.Context.prototype.getCanvas = function() { return this.canvas_; }; /** * @return {WebGLRenderingContext} GL. * @todo api */ ol.webgl.Context.prototype.getGL = function() { return this.gl_; }; /** * @param {ol.webgl.Shader} shaderObject Shader object. * @return {WebGLShader} Shader. */ ol.webgl.Context.prototype.getShader = function(shaderObject) { var shaderKey = goog.getUid(shaderObject); if (shaderKey in this.shaderCache_) { return this.shaderCache_[shaderKey]; } else { var gl = this.getGL(); var shader = gl.createShader(shaderObject.getType()); gl.shaderSource(shader, shaderObject.getSource()); gl.compileShader(shader); if (goog.DEBUG) { if (!gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) && !gl.isContextLost()) { goog.log.error(this.logger_, gl.getShaderInfoLog(shader)); } } goog.asserts.assert( gl.getShaderParameter(shader, goog.webgl.COMPILE_STATUS) || gl.isContextLost()); this.shaderCache_[shaderKey] = shader; return shader; } }; /** * @param {ol.webgl.shader.Fragment} fragmentShaderObject Fragment shader. * @param {ol.webgl.shader.Vertex} vertexShaderObject Vertex shader. * @return {WebGLProgram} Program. */ ol.webgl.Context.prototype.getProgram = function( fragmentShaderObject, vertexShaderObject) { var programKey = goog.getUid(fragmentShaderObject) + '/' + goog.getUid(vertexShaderObject); if (programKey in this.programCache_) { return this.programCache_[programKey]; } else { var gl = this.getGL(); var program = gl.createProgram(); gl.attachShader(program, this.getShader(fragmentShaderObject)); gl.attachShader(program, this.getShader(vertexShaderObject)); gl.linkProgram(program); if (goog.DEBUG) { if (!gl.getProgramParameter(program, goog.webgl.LINK_STATUS) && !gl.isContextLost()) { goog.log.error(this.logger_, gl.getProgramInfoLog(program)); } } goog.asserts.assert( gl.getProgramParameter(program, goog.webgl.LINK_STATUS) || gl.isContextLost()); this.programCache_[programKey] = program; return program; } }; /** * FIXME empy description for jsdoc */ ol.webgl.Context.prototype.handleWebGLContextLost = function() { goog.object.clear(this.bufferCache_); goog.object.clear(this.shaderCache_); goog.object.clear(this.programCache_); this.currentProgram_ = null; }; /** * FIXME empy description for jsdoc */ ol.webgl.Context.prototype.handleWebGLContextRestored = function() { }; /** * @param {WebGLProgram} program Program. * @return {boolean} Changed. * @todo api */ ol.webgl.Context.prototype.useProgram = function(program) { if (program == this.currentProgram_) { return false; } else { var gl = this.getGL(); gl.useProgram(program); this.currentProgram_ = program; return true; } }; /** * @private * @type {goog.log.Logger} */ ol.webgl.Context.prototype.logger_ = goog.log.getLogger('ol.webgl.Context');