Просмотр исходного кода

Use WebGL for YCbCr to RGBA conversion where available; see #1

Dominic Szablewski 11 лет назад
Родитель
Сommit
ad6abe9013
1 измененных файлов: 148 добавлений и 41 удалений
  1. 148 41
      jsmpg.js

+ 148 - 41
jsmpg.js Просмотреть файл

@@ -33,15 +33,20 @@ var jsmpeg = window.jsmpeg = function( url, opts ) {
33 33
 	this.externalLoadCallback = opts.onload || null;
34 34
 	this.externalDecodeCallback = opts.ondecodeframe || null;
35 35
 	this.externalFinishedCallback = opts.onfinished || null;
36
-	this.bwFilter = opts.bwFilter || false;
37 36
 
38 37
 	this.customIntraQuantMatrix = new Uint8Array(64);
39 38
 	this.customNonIntraQuantMatrix = new Uint8Array(64);
40 39
 	this.blockData = new Int32Array(64);
41 40
 	this.zeroBlockData = new Int32Array(64);
42 41
 	this.fillArray(this.zeroBlockData, 0);
43
-
44
-	this.canvasContext = this.canvas.getContext('2d');
42
+	
43
+	// use WebGL for YCbCrToRGBA conversion if possible (much faster)
44
+	if( !opts.forceCanvas2D && this.initWebGL() ) {
45
+		this.renderFrame = this.renderFrameGL;
46
+	} else {
47
+		this.canvasContext = this.canvas.getContext('2d');
48
+		this.renderFrame = this.renderFrame2D;
49
+	}
45 50
 
46 51
 	if( url instanceof WebSocket ) {
47 52
 		this.client = url;
@@ -271,7 +276,10 @@ jsmpeg.prototype.load = function( url ) {
271 276
 			that.loadCallback(request.response);
272 277
 		}
273 278
 	};
274
-	request.onprogress = this.updateLoader.bind(this);
279
+
280
+	if( !this.gl ) {
281
+		request.onprogress = this.updateLoader.bind(this);
282
+	}
275 283
 
276 284
 	request.open('GET', url);
277 285
 	request.responseType = "arraybuffer";
@@ -542,13 +550,10 @@ jsmpeg.prototype.initBuffers = function() {
542 550
 	this.canvas.width = this.width;
543 551
 	this.canvas.height = this.height;
544 552
 	
545
-	this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height);
546
-
547
-	if( this.bwFilter ) {
548
-		// This fails in IE10; don't use the bwFilter if you need to support it.
549
-		this.currentRGBA32 = new Uint32Array( this.currentRGBA.data.buffer );
553
+	if( !this.gl ) {
554
+		this.currentRGBA = this.canvasContext.getImageData(0, 0, this.width, this.height);
555
+		this.fillArray(this.currentRGBA.data, 255);
550 556
 	}
551
-	this.fillArray(this.currentRGBA.data, 255);
552 557
 };
553 558
 
554 559
 
@@ -617,13 +622,7 @@ jsmpeg.prototype.decodePicture = function(skipOutput) {
617 622
 	
618 623
 	
619 624
 	if( skipOutput != DECODE_SKIP_OUTPUT ) {
620
-		if( this.bwFilter ) {
621
-			this.YToRGBA();
622
-		}
623
-		else {
624
-			this.YCbCrToRGBA();	
625
-		}
626
-		this.canvasContext.putImageData(this.currentRGBA, 0, 0);
625
+		this.renderFrame();
627 626
 
628 627
 		if(this.externalDecodeCallback) {
629 628
 			this.externalDecodeCallback(this, this.canvas);
@@ -725,31 +724,113 @@ jsmpeg.prototype.YCbCrToRGBA = function() {
725 724
 	}
726 725
 };
727 726
 
728
-jsmpeg.prototype.YToRGBA = function() {	
729
-	// Luma only
730
-	var pY = this.currentY;
731
-	var pRGBA = this.currentRGBA32;
732
-
733
-	var yIndex = 0;
734
-	var yNext2Lines = (this.codedWidth - this.width);
727
+jsmpeg.prototype.renderFrame2D = function() {
728
+	this.YCbCrToRGBA();
729
+	this.canvasContext.putImageData(this.currentRGBA, 0, 0);
730
+};
735 731
 
736
-	var rgbaIndex = 0;	
737
-	var cols = this.width;
738
-	var rows = this.height;
739 732
 
740
-	var y;
733
+// ----------------------------------------------------------------------------
734
+// Accelerated WebGL YCbCrToRGBA conversion
735
+
736
+jsmpeg.prototype.gl = null;
737
+jsmpeg.prototype.program = null;
738
+jsmpeg.prototype.YTexture = null;
739
+jsmpeg.prototype.UTexture = null;
740
+jsmpeg.prototype.VTexture = null;
741
+
742
+jsmpeg.prototype.createTexture = function(index, name) {
743
+	var gl = this.gl;
744
+	var texture = gl.createTexture();
745
+	
746
+	gl.bindTexture(gl.TEXTURE_2D, texture);
747
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
748
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
749
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
750
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
751
+	gl.uniform1i(gl.getUniformLocation(this.program, name), index);
752
+	
753
+	return texture;
754
+};
741 755
 
742
-	for( var row = 0; row < rows; row++ ) {
743
-		for( var col = 0; col < cols; col++ ) {
744
-			y = pY[yIndex++];
745
-			pRGBA[rgbaIndex++] = 0xff000000 | y << 16 | y << 8 | y;
746
-		}
756
+jsmpeg.prototype.compileShader = function(type, source) {
757
+	var gl = this.gl;
758
+	var shader = gl.createShader(type);
759
+	gl.shaderSource(shader, source);
760
+	gl.compileShader(shader);
761
+	
762
+	if( !gl.getShaderParameter(shader, gl.COMPILE_STATUS) ) {
763
+		throw new Error(gl.getShaderInfoLog(shader));
764
+	}
747 765
 		
748
-		yIndex += yNext2Lines;
766
+	return shader;
767
+};
768
+
769
+jsmpeg.prototype.initWebGL = function() {
770
+	// attempt to get a webgl context
771
+	try {
772
+		var gl = this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
773
+	} catch (e) {
774
+		return false;
749 775
 	}
776
+	
777
+	if (!gl) {
778
+		return false;
779
+	}
780
+	
781
+	// setup shaders
782
+	this.program = gl.createProgram();
783
+	gl.attachShader(this.program, this.compileShader(gl.VERTEX_SHADER, YCBCRTORGBA_VERTEX_SHADER));
784
+	gl.attachShader(this.program, this.compileShader(gl.FRAGMENT_SHADER, YCBCRTORGBA_FRAGMENT_SHADER));
785
+	gl.linkProgram(this.program);
786
+	
787
+	if( !gl.getProgramParameter(this.program, gl.LINK_STATUS) ) {
788
+		throw new Error(gl.getProgramInfoLog(this.program));
789
+	}
790
+	
791
+	gl.useProgram(this.program);
792
+	
793
+	// setup textures
794
+	this.YTexture = this.createTexture(0, 'YTexture');
795
+	this.UTexture = this.createTexture(1, 'UTexture');
796
+	this.VTexture = this.createTexture(2, 'VTexture');
797
+	
798
+	// init buffers
799
+	this.buffer = gl.createBuffer();
800
+	gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
801
+	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);
802
+	
803
+	var vertexAttr = gl.getAttribLocation(this.program, 'vertex');
804
+	gl.enableVertexAttribArray(vertexAttr);
805
+	gl.vertexAttribPointer(vertexAttr, 2, gl.FLOAT, false, 0, 0);
806
+	
807
+	return true;
750 808
 };
751 809
 
810
+jsmpeg.prototype.renderFrameGL = function() {
811
+	var gl = this.gl;
752 812
 
813
+	// WebGL doesn't like Uint8ClampedArrays, so we have to create a Uint8Array view for 
814
+	// each plane
815
+	var uint8Y = new Uint8Array(this.currentY.buffer),
816
+		uint8Cr = new Uint8Array(this.currentCr.buffer),
817
+		uint8Cb = new Uint8Array(this.currentCb.buffer);
818
+	
819
+	gl.activeTexture(gl.TEXTURE0);
820
+	gl.bindTexture(gl.TEXTURE_2D, this.YTexture);
821
+
822
+	gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.width, this.height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Y);
823
+	
824
+	gl.activeTexture(gl.TEXTURE1);
825
+	gl.bindTexture(gl.TEXTURE_2D, this.UTexture);
826
+	gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.halfHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cr);
827
+	
828
+	gl.activeTexture(gl.TEXTURE2);
829
+	gl.bindTexture(gl.TEXTURE_2D, this.VTexture);
830
+	gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, this.halfWidth, this.halfHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, uint8Cb);
831
+	
832
+	gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
833
+};
753 834
 
754 835
 
755 836
 // ----------------------------------------------------------------------------
@@ -1054,12 +1135,6 @@ jsmpeg.prototype.copyMacroblock = function(motionH, motionV, sY, sCr, sCb ) {
1054 1135
 			}
1055 1136
 		}
1056 1137
 	}
1057
-	
1058
-	if( this.bwFilter ) {
1059
-		// No need to copy chrominance when black&white filter is active
1060
-		return;
1061
-	}
1062
-	
1063 1138
 
1064 1139
 	// Chrominance
1065 1140
 	
@@ -2210,7 +2285,39 @@ var
2210 2285
 	START_SLICE_LAST = 0xAF,
2211 2286
 	START_PICTURE = 0x00,
2212 2287
 	START_EXTENSION = 0xB5,
2213
-	START_USER_DATA = 0xB2;
2288
+	START_USER_DATA = 0xB2,
2289
+
2290
+	// Shaders for accelerated WebGL YCbCrToRGBA conversion
2291
+	YCBCRTORGBA_FRAGMENT_SHADER = [
2292
+		'precision highp float;',
2293
+		'uniform sampler2D YTexture;',
2294
+		'uniform sampler2D UTexture;',
2295
+		'uniform sampler2D VTexture;',
2296
+		'varying vec2 texCoord;',
2297
+	
2298
+		'void main() {',
2299
+			'float y = texture2D(YTexture, texCoord).r;',
2300
+			'float cr = texture2D(UTexture, texCoord).r - 0.5;',
2301
+			'float cb = texture2D(VTexture, texCoord).r - 0.5;',
2302
+			
2303
+			'gl_FragColor = vec4(',
2304
+				'y + 1.4 * cr,',
2305
+				'y + -0.343 * cb - 0.711 * cr,',
2306
+				'y + 1.765 * cb,',
2307
+				'1.0',
2308
+			');',
2309
+		'}'
2310
+	].join('\n'),
2311
+	
2312
+	YCBCRTORGBA_VERTEX_SHADER = [
2313
+		'attribute vec2 vertex;',
2314
+		'varying vec2 texCoord;',
2315
+		
2316
+		'void main() {',
2317
+			'texCoord = vertex;',
2318
+			'gl_Position = vec4((vertex * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0);',
2319
+		'}'
2320
+	].join('\n');
2214 2321
 	
2215 2322
 var MACROBLOCK_TYPE_TABLES = [
2216 2323
 	null,