From 4d97bc20595f9e6cbf1e39705d97127432135210 Mon Sep 17 00:00:00 2001 From: Jongmoon Yoon Date: Thu, 21 Dec 2017 18:18:34 +0900 Subject: [PATCH] feat(PanoImageRenderer): Refactoring/tune renderer & add keepUpdate Ref #89 --- src/PanoImageRenderer/PanoImageRenderer.js | 61 ++++------ src/PanoImageRenderer/WebGLUtils.js | 7 +- .../renderer/CubeRenderer.js | 112 +++++++++--------- .../renderer/SphereRenderer.js | 40 +++---- src/PanoViewer/PanoViewer.js | 5 + 5 files changed, 109 insertions(+), 116 deletions(-) diff --git a/src/PanoImageRenderer/PanoImageRenderer.js b/src/PanoImageRenderer/PanoImageRenderer.js index bd9ed7650..5bafd1caa 100644 --- a/src/PanoImageRenderer/PanoImageRenderer.js +++ b/src/PanoImageRenderer/PanoImageRenderer.js @@ -67,6 +67,7 @@ export default class PanoImageRenderer extends Component { this._image = null; this._imageIsReady = false; + this._keepUpdate = false; // Flag to specify 'continuous update' on video even when still. this._onContentLoad = this._onContentLoad.bind(this); this._onContentError = this._onContentError.bind(this); @@ -91,8 +92,10 @@ export default class PanoImageRenderer extends Component { if (isVideo) { this._contentLoader = new VideoLoader(); + this._keepUpdate = true; } else { this._contentLoader = new ImageLoader(); + this._keepUpdate = false; } // img element or img url @@ -169,7 +172,8 @@ export default class PanoImageRenderer extends Component { } isImageLoaded() { - return !!this._image && this._imageIsReady; + return !!this._image && this._imageIsReady && + (!this._isVideo || this._image.readyState >= 2 /* HAVE_CURRENT_DATA */); } cancelLoadImage() { @@ -312,6 +316,9 @@ export default class PanoImageRenderer extends Component { if (!this.shaderProgram) { throw new Error(`Failed to intialize shaders: ${WebGLUtils.getErrorNameFromWebGLErrorCode(this.context.getError())}`); } + + // Buffers for shader + this._initBuffers(); } catch (e) { this.trigger(EVENTS.ERROR, { type: ERROR_TYPE.NO_WEBGL, @@ -389,14 +396,16 @@ export default class PanoImageRenderer extends Component { const gl = this.context; this.vertexBuffer = WebGLUtils.initBuffer( - gl, gl.ARRAY_BUFFER, new Float32Array(vertexPositionData), 3); + gl, gl.ARRAY_BUFFER, new Float32Array(vertexPositionData), 3, + this.shaderProgram.vertexPositionAttribute); this.indexBuffer = WebGLUtils.initBuffer( gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), 1); if (textureCoordData !== null) { this.textureCoordBuffer = WebGLUtils.initBuffer( - gl, gl.ARRAY_BUFFER, new Float32Array(textureCoordData), 2); + gl, gl.ARRAY_BUFFER, new Float32Array(textureCoordData), 2, + this.shaderProgram.textureCoordAttribute); } } @@ -408,7 +417,7 @@ export default class PanoImageRenderer extends Component { } renderWithQuaternion(quaternion, fieldOfView) { - if (!this.isImageLoaded() || !this.hasRenderingContext()) { + if (!this.isImageLoaded()) { return; } @@ -447,16 +456,17 @@ export default class PanoImageRenderer extends Component { } } + keepUpdate(doUpdate) { + this._keepUpdate = doUpdate; + } + render(yaw, pitch, fieldOfView) { - if (!this.isImageLoaded() || !this.hasRenderingContext()) { + if (!this.isImageLoaded()) { return; } - if (this._isVideo) { /* TODO: && Check if isPlaying */ - this._bindTexture(); - } - - if (this._lastYaw !== null && this._lastYaw === yaw && + if (this._keepUpdate === false && + this._lastYaw !== null && this._lastYaw === yaw && this._lastPitch !== null && this._lastPitch === pitch && this.fieldOfView && this.fieldOfView === fieldOfView && this._shouldForceDraw === false) { @@ -485,45 +495,20 @@ export default class PanoImageRenderer extends Component { _draw() { const gl = this.context; - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.activeTexture(gl.TEXTURE0); - if (this._isCubeStrip) { - gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.texture); - } else { - gl.bindTexture(gl.TEXTURE_2D, this.texture); - } - - if (this.vertexBuffer === null || this.indexBuffer === null) { - this._initBuffers(); - } + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); - // gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.uniform1i(this.shaderProgram.samplerUniform, 0); gl.uniformMatrix4fv(this.shaderProgram.pMatrixUniform, false, this.pMatrix); gl.uniformMatrix4fv(this.shaderProgram.mvMatrixUniform, false, this.mvMatrix); - // textureCoordBuffer is used in sphere - if (this.textureCoordBuffer) { - WebGLUtils.bindBufferToAttribute( - gl, this.textureCoordBuffer, this.shaderProgram.textureCoordAttribute); - } - - if (this.vertexBuffer) { - WebGLUtils.bindBufferToAttribute( - gl, this.vertexBuffer, this.shaderProgram.vertexPositionAttribute); + if (this._isVideo) { + this._renderer.texImage2D(this.context, this._image); } if (this.indexBuffer) { - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.drawElements( gl.TRIANGLES, this.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0); } - - if (this._isCubeStrip) { - gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); - } else { - gl.bindTexture(gl.TEXTURE_2D, null); - } } } diff --git a/src/PanoImageRenderer/WebGLUtils.js b/src/PanoImageRenderer/WebGLUtils.js index bc3a2db96..3a17f930a 100644 --- a/src/PanoImageRenderer/WebGLUtils.js +++ b/src/PanoImageRenderer/WebGLUtils.js @@ -42,7 +42,7 @@ export default class WebGLUtils { return null; } - static initBuffer(gl, target /* bind point */, data, itemSize) { + static initBuffer(gl, target /* bind point */, data, itemSize, attr) { const buffer = gl.createBuffer(); gl.bindBuffer(target, buffer); @@ -53,6 +53,11 @@ export default class WebGLUtils { buffer.numItems = data.length / itemSize; } + if (attr !== undefined) { + gl.enableVertexAttribArray(attr); + gl.vertexAttribPointer(attr, buffer.itemSize, gl.FLOAT, false, 0, 0); + } + return buffer; } diff --git a/src/PanoImageRenderer/renderer/CubeRenderer.js b/src/PanoImageRenderer/renderer/CubeRenderer.js index 1a5e6ae47..da32fce85 100644 --- a/src/PanoImageRenderer/renderer/CubeRenderer.js +++ b/src/PanoImageRenderer/renderer/CubeRenderer.js @@ -99,6 +99,17 @@ export default class CubeRenderer extends Renderer { return; } + try { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); + + this.texImage2D(gl, image); + } catch (e) { + + } + } + + static texImage2D(gl, image) { const agent = Agent(); const width = image.naturalWidth || image.videoWidth; const height = image.naturalHeight || image.videoHeight; @@ -106,67 +117,58 @@ export default class CubeRenderer extends Renderer { const maxCubeMapTextureSize = CubeRenderer.getMaxCubeMapTextureSize(gl, image, agent); const heightScale = CubeRenderer.getHightScale(width, agent); - try { - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture); - - if (!hasDrawImageBug) { - const canvas = document.createElement("canvas"); + if (!hasDrawImageBug) { + const canvas = document.createElement("canvas"); - canvas.width = maxCubeMapTextureSize; - canvas.height = maxCubeMapTextureSize; - const context = canvas.getContext("2d"); + canvas.width = maxCubeMapTextureSize; + canvas.height = maxCubeMapTextureSize; + const context = canvas.getContext("2d"); - for (let surfaceIdx = 0; surfaceIdx < 6; surfaceIdx++) { - context.drawImage( - image, 0, surfaceIdx * (width * heightScale), - width, width * heightScale, 0, 0, maxCubeMapTextureSize, maxCubeMapTextureSize); + for (let surfaceIdx = 0; surfaceIdx < 6; surfaceIdx++) { + context.drawImage( + image, 0, surfaceIdx * (width * heightScale), + width, width * heightScale, 0, 0, maxCubeMapTextureSize, maxCubeMapTextureSize); + gl.texImage2D( + gl.TEXTURE_CUBE_MAP_POSITIVE_X + surfaceIdx, 0, gl.RGBA, + gl.RGBA, gl.UNSIGNED_BYTE, canvas); + } + } else { + // #288, drawImage bug + const halfCanvas = document.createElement("canvas"); + const context = halfCanvas.getContext("2d"); + + halfCanvas.width = maxCubeMapTextureSize * 3; + halfCanvas.height = maxCubeMapTextureSize; + + const tileCanvas = document.createElement("canvas"); + const tileContext = tileCanvas.getContext("2d"); + + tileCanvas.width = maxCubeMapTextureSize; + tileCanvas.height = maxCubeMapTextureSize; + + for (let i = 0; i < 2; i++) { + context.save(); + context.translate(0, maxCubeMapTextureSize); + context.rotate(-Math.PI / 2); + context.scale(1 / 3, 3); + context.drawImage( + image, 0, width * 3 * i * heightScale, width, height / 2 * heightScale, + 0, 0, halfCanvas.width, halfCanvas.height); + context.restore(); + for (let j = 0; j < 3; j++) { + tileContext.save(); + tileContext.translate(maxCubeMapTextureSize, 0); + tileContext.rotate(Math.PI / 2); + tileContext.drawImage( + halfCanvas, j * width, 0, width, width, 0, 0, maxCubeMapTextureSize, maxCubeMapTextureSize); + tileContext.restore(); gl.texImage2D( - gl.TEXTURE_CUBE_MAP_POSITIVE_X + surfaceIdx, 0, gl.RGBA, - gl.RGBA, gl.UNSIGNED_BYTE, canvas); - } - } else { - // #288, drawImage bug - const halfCanvas = document.createElement("canvas"); - const context = halfCanvas.getContext("2d"); - - halfCanvas.width = maxCubeMapTextureSize * 3; - halfCanvas.height = maxCubeMapTextureSize; - - const tileCanvas = document.createElement("canvas"); - const tileContext = tileCanvas.getContext("2d"); - - tileCanvas.width = maxCubeMapTextureSize; - tileCanvas.height = maxCubeMapTextureSize; - - for (let i = 0; i < 2; i++) { - context.save(); - context.translate(0, maxCubeMapTextureSize); - context.rotate(-Math.PI / 2); - context.scale(1 / 3, 3); - context.drawImage( - image, 0, width * 3 * i * heightScale, width, height / 2 * heightScale, - 0, 0, halfCanvas.width, halfCanvas.height); - context.restore(); - for (let j = 0; j < 3; j++) { - tileContext.save(); - tileContext.translate(maxCubeMapTextureSize, 0); - tileContext.rotate(Math.PI / 2); - tileContext.drawImage( - halfCanvas, j * width, 0, width, width, 0, 0, maxCubeMapTextureSize, maxCubeMapTextureSize); - tileContext.restore(); - gl.texImage2D( - gl.TEXTURE_CUBE_MAP_POSITIVE_X + i * 3 + j, 0, - gl.RGBA, maxCubeMapTextureSize, maxCubeMapTextureSize, 0, - gl.RGBA, gl.UNSIGNED_BYTE, tileCanvas); - } + gl.TEXTURE_CUBE_MAP_POSITIVE_X + i * 3 + j, 0, + gl.RGBA, maxCubeMapTextureSize, maxCubeMapTextureSize, 0, + gl.RGBA, gl.UNSIGNED_BYTE, tileCanvas); } } - } catch (e) { - } - - gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); } static getMaxCubeMapTextureSize(gl, image, agent) { diff --git a/src/PanoImageRenderer/renderer/SphereRenderer.js b/src/PanoImageRenderer/renderer/SphereRenderer.js index a049ae63e..15cd17a1b 100644 --- a/src/PanoImageRenderer/renderer/SphereRenderer.js +++ b/src/PanoImageRenderer/renderer/SphereRenderer.js @@ -55,32 +55,28 @@ export default class SphereRenderer extends Renderer { return; } - const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); - const width = image.naturalWidth || image.videoWidth;// imageWidth; - const height = image.naturalHeight || image.videoHeight;// imageHeight; - const aspectRatio = height / width; - const canvas = document.createElement("canvas"); - - if (aspectRatio <= 1) { - canvas.width = Math.min(width, maxTextureSize); - canvas.height = canvas.width * aspectRatio; - } else { - canvas.height = Math.min(height, maxTextureSize); - canvas.width = canvas.height / aspectRatio; + // Make sure image isn't too big + const width = Math.max(image.width, image.height); + const maxWidth = gl.getParameter(gl.MAX_TEXTURE_SIZE); + + if (width > maxWidth) { + console.warn(`Image width(${width}) exceeds device limit(${maxWidth}))`); + return; } - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - const context = canvas.getContext("2d"); + gl.activeTexture(gl.TEXTURE0); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.bindTexture(gl.TEXTURE_2D, texture); - context.drawImage(image, 0, 0, width, height, 0, 0, canvas.width, canvas.height); - const data = new Uint8Array(context.getImageData(0, 0, canvas.width, canvas.height).data); + // Draw first frame + this.texImage2D(gl, image); + } - gl.texImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, - canvas.width, canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data); - // this.trigger(EVENTS.BIND_TEXTURE); - gl.bindTexture(gl.TEXTURE_2D, null); + /** + * https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml + */ + static texImage2D(gl, image) { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); } static _initData() { diff --git a/src/PanoViewer/PanoViewer.js b/src/PanoViewer/PanoViewer.js index b8c4c16c4..dba955c2d 100644 --- a/src/PanoViewer/PanoViewer.js +++ b/src/PanoViewer/PanoViewer.js @@ -194,6 +194,11 @@ export default class PanoViewer extends Component { return this; } + keepUpdate(doUpdate) { + this._photoSphereRenderer.keepUpdate(doUpdate); + return this; + } + /** * Get projection type (equirectangular/cube) * @ko 프로젝션 타입(Equirectangular 혹은 Cube)을 반환한다.