Repository: three.js
Issue: Texture SubImage Interface
Labels: Suggestion

The current subimage interface is a little clunky:

renderer.copyTextureToTexture ( position : Vector2, srcTexture : Texture, dstTexture : Texture, level : Number ) : null

This means that in order to move textures around between atlases you have to pass a reference to a renderer around.

I worked with one of the original developers of OpenSceneGraph and I really liked the solution he used.  The suggestion is to move the subimage interface to the texture.  Each texture would have an array of SubImageData that can be appended every frame by the user when data becomes available.  Subimages get applied during rendering, then the SubImageData array gets flushed.

I worked up the idea in a dev build, but I'm a bit remiss to check it in because my implementation was far from perfect, and its such a small inclusion, a feature request seemed like a better way to discuss the idea.

here is the code I put together:

**in renderers/WebGlTextures.js:**

          function subImage2D( textureProperties, texture, slot ) {
			state.activeTexture( 33984 + slot );
			state.bindTexture( 3553, textureProperties.__webglTexture );

			_gl.pixelStorei( 37440, texture.flipY );
			_gl.pixelStorei( 37441, texture.premultiplyAlpha );
			_gl.pixelStorei( 3317, texture.unpackAlignment );

			if ( texture.subImageData.length > 0 ) {

				_gl.bindTexture( 3553, textureProperties.__webglTexture );

				for ( var i = 0; i < texture.subImageData.length; i ++ ) {

					var data = texture.subImageData[ i ];
					var glFormat = utils.convert( data.format );
					var glType = utils.convert( data.type );

					_gl.texSubImage2D( 3553,
						data.level,
						data.offset[ 0 ],
						data.offset[ 1 ],
						glFormat,
						glType,
						data.pixels );

				}

			}

			texture.subImageData = [];
			texture.needsUpdate = false;

		}
I added another elseif in the setTexture2D:

		function setTexture2D( texture, slot ) {
			
			var textureProperties = properties.get( texture );

			if ( texture.isVideoTexture ) updateVideoTexture( texture );

			if ( texture.version > 0 && textureProperties.__version !== texture.version ) {

				var image = texture.image;

				if ( image === undefined ) {

					console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );

				} else if ( image.complete === false ) {

						console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );

				} _else if(texture.subImageData.length > 0){
	                                        subImage2D(textureProperties, texture, slot);
	                                        return;
				} else if(!texture.ignoreImage){
	                                    
					uploadTexture( textureProperties, texture, slot );
					return;

				}_

			}

			state.activeTexture( 33984 + slot );
			state.bindTexture( 3553, textureProperties.__webglTexture );

		}

 **in the texture class:**

               /* in the "constructor" */
		this.subImageData = [];
		this.ignoreImage = false;
               
               /*added method*/
	    subImage : function(level, offsetX, offsetY,  pixels, format, type){
	        
			format = format !== undefined ?  format : this.format;
			type   = type   !== undefined ?  type   : this.type;

			if(type !== this.type){
				console.warn('THREE.Texture.subimage: type should equal texture type, bailing');
				return;
			}

			if(format !== this.format){
				console.warn('THREE.Texture.subimage: format should equal texture format, bailing');
				return;
			}

			/*
			once you subdata, you have to ignore the image that is attached to this texture
			seems a little clunky, but I can't think of another way to do it, unless there was
			a specific subImageTexture that didn't bind an image
			 */        

			this.ignoreImage = true;
			this.subImageData.push({
				level: level,
				offset: [offsetX,offsetY],
				format: format,
				type: type,
				pixels: pixels
			});
		},

Right now I'm using this with an empty DataTexture, and append images as they come in from the server.  An image gets pushed into the atlas and I'm using it like this:

            desTexture.subImage(0, offsetIntoDestX, offsetIntoDestY, sourceTexture);