Pārlūkot izejas kodu

EXRLoader: impl single tiled-image support (#28033)

* EXRLoader: impl single tiled-image support

* EXRLoader: optimize MIP_LEVELS log2 rounding
Guilherme Avila 1 gadu atpakaļ
vecāks
revīzija
1ff0d56b18
1 mainītis faili ar 283 papildinājumiem un 99 dzēšanām
  1. 283 99
      examples/jsm/loaders/EXRLoader.js

+ 283 - 99
examples/jsm/loaders/EXRLoader.js

@@ -1269,18 +1269,18 @@ class EXRLoader extends DataTextureLoader {
 			const inDataView = info.viewer;
 			const inDataView = info.viewer;
 			const inOffset = { value: info.offset.value };
 			const inOffset = { value: info.offset.value };
 
 
-			const outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) );
+			const outBuffer = new Uint16Array( info.columns * info.lines * ( info.inputChannels.length * info.type ) );
 			const bitmap = new Uint8Array( BITMAP_SIZE );
 			const bitmap = new Uint8Array( BITMAP_SIZE );
 
 
 			// Setup channel info
 			// Setup channel info
 			let outBufferEnd = 0;
 			let outBufferEnd = 0;
-			const pizChannelData = new Array( info.channels );
-			for ( let i = 0; i < info.channels; i ++ ) {
+			const pizChannelData = new Array( info.inputChannels.length );
+			for ( let i = 0, il = info.inputChannels.length; i < il; i ++ ) {
 
 
 				pizChannelData[ i ] = {};
 				pizChannelData[ i ] = {};
 				pizChannelData[ i ][ 'start' ] = outBufferEnd;
 				pizChannelData[ i ][ 'start' ] = outBufferEnd;
 				pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ];
 				pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ];
-				pizChannelData[ i ][ 'nx' ] = info.width;
+				pizChannelData[ i ][ 'nx' ] = info.columns;
 				pizChannelData[ i ][ 'ny' ] = info.lines;
 				pizChannelData[ i ][ 'ny' ] = info.lines;
 				pizChannelData[ i ][ 'size' ] = info.type;
 				pizChannelData[ i ][ 'size' ] = info.type;
 
 
@@ -1319,7 +1319,7 @@ class EXRLoader extends DataTextureLoader {
 			hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd );
 			hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd );
 
 
 			// Wavelet decoding
 			// Wavelet decoding
-			for ( let i = 0; i < info.channels; ++ i ) {
+			for ( let i = 0; i < info.inputChannels.length; ++ i ) {
 
 
 				const cd = pizChannelData[ i ];
 				const cd = pizChannelData[ i ];
 
 
@@ -1347,7 +1347,7 @@ class EXRLoader extends DataTextureLoader {
 			const tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength );
 			const tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength );
 			for ( let y = 0; y < info.lines; y ++ ) {
 			for ( let y = 0; y < info.lines; y ++ ) {
 
 
-				for ( let c = 0; c < info.channels; c ++ ) {
+				for ( let c = 0; c < info.inputChannels.length; c ++ ) {
 
 
 					const cd = pizChannelData[ c ];
 					const cd = pizChannelData[ c ];
 
 
@@ -1372,8 +1372,9 @@ class EXRLoader extends DataTextureLoader {
 
 
 			const rawBuffer = fflate.unzlibSync( compressed );
 			const rawBuffer = fflate.unzlibSync( compressed );
 
 
-			const sz = info.lines * info.channels * info.width;
-			const tmpBuffer = ( info.type == 1 ) ? new Uint16Array( sz ) : new Uint32Array( sz );
+			const byteSize = info.inputChannels.length * info.lines * info.columns * info.totalBytes;
+			const tmpBuffer = new ArrayBuffer( byteSize );
+			const viewer = new DataView( tmpBuffer );
 
 
 			let tmpBufferEnd = 0;
 			let tmpBufferEnd = 0;
 			let writePtr = 0;
 			let writePtr = 0;
@@ -1381,26 +1382,27 @@ class EXRLoader extends DataTextureLoader {
 
 
 			for ( let y = 0; y < info.lines; y ++ ) {
 			for ( let y = 0; y < info.lines; y ++ ) {
 
 
-				for ( let c = 0; c < info.channels; c ++ ) {
+				for ( let c = 0; c < info.inputChannels.length; c ++ ) {
 
 
 					let pixel = 0;
 					let pixel = 0;
 
 
-					switch ( info.type ) {
+					const type = info.inputChannels[ c ].pixelType;
+					switch ( type ) {
 
 
 						case 1:
 						case 1:
 
 
 							ptr[ 0 ] = tmpBufferEnd;
 							ptr[ 0 ] = tmpBufferEnd;
-							ptr[ 1 ] = ptr[ 0 ] + info.width;
-							tmpBufferEnd = ptr[ 1 ] + info.width;
+							ptr[ 1 ] = ptr[ 0 ] + info.columns;
+							tmpBufferEnd = ptr[ 1 ] + info.columns;
 
 
-							for ( let j = 0; j < info.width; ++ j ) {
+							for ( let j = 0; j < info.columns; ++ j ) {
 
 
 								const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ];
 								const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ];
 
 
 								pixel += diff;
 								pixel += diff;
 
 
-								tmpBuffer[ writePtr ] = pixel;
-								writePtr ++;
+								viewer.setUint16( writePtr, pixel, true );
+								writePtr += 2;
 
 
 							}
 							}
 
 
@@ -1409,18 +1411,18 @@ class EXRLoader extends DataTextureLoader {
 						case 2:
 						case 2:
 
 
 							ptr[ 0 ] = tmpBufferEnd;
 							ptr[ 0 ] = tmpBufferEnd;
-							ptr[ 1 ] = ptr[ 0 ] + info.width;
-							ptr[ 2 ] = ptr[ 1 ] + info.width;
-							tmpBufferEnd = ptr[ 2 ] + info.width;
+							ptr[ 1 ] = ptr[ 0 ] + info.columns;
+							ptr[ 2 ] = ptr[ 1 ] + info.columns;
+							tmpBufferEnd = ptr[ 2 ] + info.columns;
 
 
-							for ( let j = 0; j < info.width; ++ j ) {
+							for ( let j = 0; j < info.columns; ++ j ) {
 
 
 								const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 );
 								const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 );
 
 
 								pixel += diff;
 								pixel += diff;
 
 
-								tmpBuffer[ writePtr ] = pixel;
-								writePtr ++;
+								viewer.setUint32( writePtr, pixel, true );
+								writePtr += 4;
 
 
 							}
 							}
 
 
@@ -1432,7 +1434,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			return new DataView( tmpBuffer.buffer );
+			return viewer;
 
 
 		}
 		}
 
 
@@ -1440,7 +1442,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 			const inDataView = info.viewer;
 			const inDataView = info.viewer;
 			const inOffset = { value: info.offset.value };
 			const inOffset = { value: info.offset.value };
-			const outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) );
+			const outBuffer = new Uint8Array( info.columns * info.lines * ( info.inputChannels.length * info.type * INT16_SIZE ) );
 
 
 			// Read compression header information
 			// Read compression header information
 			const dwaHeader = {
 			const dwaHeader = {
@@ -1488,9 +1490,9 @@ class EXRLoader extends DataTextureLoader {
 
 
 			// Classify channels
 			// Classify channels
 			const channels = EXRHeader.channels;
 			const channels = EXRHeader.channels;
-			const channelData = new Array( info.channels );
+			const channelData = new Array( info.inputChannels.length );
 
 
-			for ( let i = 0; i < info.channels; ++ i ) {
+			for ( let i = 0; i < info.inputChannels.length; ++ i ) {
 
 
 				const cd = channelData[ i ] = {};
 				const cd = channelData[ i ] = {};
 				const channel = channels[ i ];
 				const channel = channels[ i ];
@@ -1500,7 +1502,7 @@ class EXRLoader extends DataTextureLoader {
 				cd.decoded = false;
 				cd.decoded = false;
 				cd.type = channel.pixelType;
 				cd.type = channel.pixelType;
 				cd.pLinear = channel.pLinear;
 				cd.pLinear = channel.pLinear;
-				cd.width = info.width;
+				cd.width = info.columns;
 				cd.height = info.lines;
 				cd.height = info.lines;
 
 
 			}
 			}
@@ -1509,7 +1511,7 @@ class EXRLoader extends DataTextureLoader {
 				idx: new Array( 3 )
 				idx: new Array( 3 )
 			};
 			};
 
 
-			for ( let offset = 0; offset < info.channels; ++ offset ) {
+			for ( let offset = 0; offset < info.inputChannels.length; ++ offset ) {
 
 
 				const cd = channelData[ offset ];
 				const cd = channelData[ offset ];
 
 
@@ -1899,7 +1901,8 @@ class EXRLoader extends DataTextureLoader {
 
 
 			const lineOrders = [
 			const lineOrders = [
 				'INCREASING_Y',
 				'INCREASING_Y',
-				'DECREASING_Y'
+				'DECREASING_Y',
+				'RANDOM_Y',
 			];
 			];
 
 
 			const lineOrder = parseUint8( dataView, offset );
 			const lineOrder = parseUint8( dataView, offset );
@@ -1908,6 +1911,45 @@ class EXRLoader extends DataTextureLoader {
 
 
 		}
 		}
 
 
+		function parseEnvmap( dataView, offset ) {
+
+			const envmaps = [
+				'ENVMAP_LATLONG',
+				'ENVMAP_CUBE'
+			];
+
+			const envmap = parseUint8( dataView, offset );
+
+			return envmaps[ envmap ];
+
+		}
+
+		function parseTiledesc( dataView, offset ) {
+
+			const levelModes = [
+				'ONE_LEVEL',
+				'MIPMAP_LEVELS',
+				'RIPMAP_LEVELS',
+			];
+
+			const roundingModes = [
+				'ROUND_DOWN',
+				'ROUND_UP',
+			];
+
+			const xSize = parseUint32( dataView, offset );
+			const ySize = parseUint32( dataView, offset );
+			const modes = parseUint8( dataView, offset );
+
+			return {
+				xSize: xSize,
+				ySize: ySize,
+				levelMode: levelModes[ modes & 0xf ],
+				roundingMode: roundingModes[ modes >> 4 ]
+			};
+
+		}
+
 		function parseV2f( dataView, offset ) {
 		function parseV2f( dataView, offset ) {
 
 
 			const x = parseFloat32( dataView, offset );
 			const x = parseFloat32( dataView, offset );
@@ -1949,6 +1991,14 @@ class EXRLoader extends DataTextureLoader {
 
 
 				return parseBox2i( dataView, offset );
 				return parseBox2i( dataView, offset );
 
 
+			} else if ( type === 'envmap' ) {
+
+				return parseEnvmap( dataView, offset );
+
+			} else if ( type === 'tiledesc' ) {
+
+				return parseTiledesc( dataView, offset );
+
 			} else if ( type === 'lineOrder' ) {
 			} else if ( type === 'lineOrder' ) {
 
 
 				return parseLineOrder( dataView, offset );
 				return parseLineOrder( dataView, offset );
@@ -1991,6 +2041,163 @@ class EXRLoader extends DataTextureLoader {
 
 
 		}
 		}
 
 
+		function roundLog2( x, mode ) {
+
+			const log2 = Math.log2( x );
+			return mode == 'ROUND_DOWN' ? Math.floor( log2 ) : Math.ceil( log2 );
+
+		}
+
+		function calculateTileLevels( tiledesc, w, h ) {
+
+			let num = 0;
+
+			switch ( tiledesc.levelMode ) {
+
+				case 'ONE_LEVEL':
+					num = 1;
+					break;
+
+				case 'MIPMAP_LEVELS':
+					num = roundLog2( Math.max( w, h ), tiledesc.roundingMode ) + 1;
+					break;
+
+				case 'RIPMAP_LEVELS':
+					throw new Error( 'THREE.EXRLoader: RIPMAP_LEVELS tiles currently unsupported.' );
+
+			}
+
+			return num;
+
+		}
+
+		function calculateTiles( count, dataSize, size, roundingMode ) {
+
+			const tiles = new Array( count );
+
+			for ( let i = 0; i < count; i ++ ) {
+
+				const b = ( 1 << i );
+				let s = ( dataSize / b ) | 0;
+
+				if ( roundingMode == 'ROUND_UP' && s * b < dataSize ) s += 1;
+
+				const l = Math.max( s, 1 );
+
+				tiles[ i ] = ( ( l + size - 1 ) / size ) | 0;
+
+			}
+
+			return tiles;
+
+		}
+
+		function parseTiles() {
+
+			const EXRDecoder = this;
+			const offset = EXRDecoder.offset;
+			const tmpOffset = { value: 0 };
+
+			for ( let tile = 0; tile < EXRDecoder.tileCount; tile ++ ) {
+
+				const tileX = parseInt32( EXRDecoder.viewer, offset );
+				const tileY = parseInt32( EXRDecoder.viewer, offset );
+				offset.value += 8; // skip levels - only parsing top-level
+				EXRDecoder.size = parseUint32( EXRDecoder.viewer, offset );
+
+				const startX = tileX * EXRDecoder.blockWidth;
+				const startY = tileY * EXRDecoder.blockHeight;
+				EXRDecoder.columns = ( startX + EXRDecoder.blockWidth > EXRDecoder.width ) ? EXRDecoder.width - startX : EXRDecoder.blockWidth;
+				EXRDecoder.lines = ( startY + EXRDecoder.blockHeight > EXRDecoder.height ) ? EXRDecoder.height - startY : EXRDecoder.blockHeight;
+
+				const bytesBlockLine = EXRDecoder.columns * EXRDecoder.totalBytes;
+				const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesBlockLine;
+				const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
+
+				offset.value += EXRDecoder.size;
+
+				for ( let line = 0; line < EXRDecoder.lines; line ++ ) {
+
+					const lineOffset = line * EXRDecoder.columns * EXRDecoder.totalBytes;
+
+					for ( let channelID = 0; channelID < EXRDecoder.inputChannels.length; channelID ++ ) {
+
+						const name = EXRHeader.channels[ channelID ].name;
+						const lOff = EXRDecoder.channelByteOffsets[ name ] * EXRDecoder.columns;
+						const cOff = EXRDecoder.decodeChannels[ name ];
+
+						if ( cOff === undefined ) continue;
+
+						tmpOffset.value = lineOffset + lOff;
+						const outLineOffset = ( EXRDecoder.height - ( 1 + startY + line ) ) * EXRDecoder.outLineWidth;
+
+						for ( let x = 0; x < EXRDecoder.columns; x ++ ) {
+
+							const outIndex = outLineOffset + ( x + startX ) * EXRDecoder.outputChannels + cOff;
+							EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
+		function parseScanline() {
+
+			const EXRDecoder = this;
+			const offset = EXRDecoder.offset;
+			const tmpOffset = { value: 0 };
+
+			for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.blockHeight; scanlineBlockIdx ++ ) {
+
+				const line = parseInt32( EXRDecoder.viewer, offset ) - EXRHeader.dataWindow.yMin; // line_no
+				EXRDecoder.size = parseUint32( EXRDecoder.viewer, offset ); // data_len
+				EXRDecoder.lines = ( ( line + EXRDecoder.blockHeight > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.blockHeight );
+
+				const bytesPerLine = EXRDecoder.columns * EXRDecoder.totalBytes;
+				const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesPerLine;
+				const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
+
+				offset.value += EXRDecoder.size;
+
+				for ( let line_y = 0; line_y < EXRDecoder.blockHeight; line_y ++ ) {
+
+					const scan_y = scanlineBlockIdx * EXRDecoder.blockHeight;
+					const true_y = line_y + EXRDecoder.scanOrder( scan_y );
+					if ( true_y >= EXRDecoder.height ) continue;
+
+					const lineOffset = line_y * bytesPerLine;
+					const outLineOffset = ( EXRDecoder.height - 1 - true_y ) * EXRDecoder.outLineWidth;
+
+					for ( let channelID = 0; channelID < EXRDecoder.inputChannels.length; channelID ++ ) {
+
+						const name = EXRHeader.channels[ channelID ].name;
+						const lOff = EXRDecoder.channelByteOffsets[ name ] * EXRDecoder.columns;
+						const cOff = EXRDecoder.decodeChannels[ name ];
+
+						if ( cOff === undefined ) continue;
+
+						tmpOffset.value = lineOffset + lOff;
+
+						for ( let x = 0; x < EXRDecoder.columns; x ++ ) {
+
+							const outIndex = outLineOffset + x * EXRDecoder.outputChannels + cOff;
+							EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
 		function parseHeader( dataView, buffer, offset ) {
 		function parseHeader( dataView, buffer, offset ) {
 
 
 			const EXRHeader = {};
 			const EXRHeader = {};
@@ -2046,7 +2253,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			if ( ( spec & ~ 0x04 ) != 0 ) { // unsupported tiled, deep-image, multi-part
+			if ( ( spec & ~ 0x06 ) != 0 ) { // unsupported deep-image, multi-part
 
 
 				console.error( 'THREE.EXRHeader:', EXRHeader );
 				console.error( 'THREE.EXRHeader:', EXRHeader );
 				throw new Error( 'THREE.EXRLoader: Provided file is currently unsupported.' );
 				throw new Error( 'THREE.EXRLoader: Provided file is currently unsupported.' );
@@ -2066,12 +2273,12 @@ class EXRLoader extends DataTextureLoader {
 				offset: offset,
 				offset: offset,
 				width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1,
 				width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1,
 				height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1,
 				height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1,
-				channels: EXRHeader.channels.length,
-				channelLineOffsets: {},
+				inputChannels: EXRHeader.channels,
+				channelByteOffsets: {},
 				scanOrder: null,
 				scanOrder: null,
-				bytesPerLine: null,
+				totalBytes: null,
+				columns: null,
 				lines: null,
 				lines: null,
-				inputSize: null,
 				type: null,
 				type: null,
 				uncompress: null,
 				uncompress: null,
 				getter: null,
 				getter: null,
@@ -2082,42 +2289,42 @@ class EXRLoader extends DataTextureLoader {
 			switch ( EXRHeader.compression ) {
 			switch ( EXRHeader.compression ) {
 
 
 				case 'NO_COMPRESSION':
 				case 'NO_COMPRESSION':
-					EXRDecoder.lines = 1;
+					EXRDecoder.blockHeight = 1;
 					EXRDecoder.uncompress = uncompressRAW;
 					EXRDecoder.uncompress = uncompressRAW;
 					break;
 					break;
 
 
 				case 'RLE_COMPRESSION':
 				case 'RLE_COMPRESSION':
-					EXRDecoder.lines = 1;
+					EXRDecoder.blockHeight = 1;
 					EXRDecoder.uncompress = uncompressRLE;
 					EXRDecoder.uncompress = uncompressRLE;
 					break;
 					break;
 
 
 				case 'ZIPS_COMPRESSION':
 				case 'ZIPS_COMPRESSION':
-					EXRDecoder.lines = 1;
+					EXRDecoder.blockHeight = 1;
 					EXRDecoder.uncompress = uncompressZIP;
 					EXRDecoder.uncompress = uncompressZIP;
 					break;
 					break;
 
 
 				case 'ZIP_COMPRESSION':
 				case 'ZIP_COMPRESSION':
-					EXRDecoder.lines = 16;
+					EXRDecoder.blockHeight = 16;
 					EXRDecoder.uncompress = uncompressZIP;
 					EXRDecoder.uncompress = uncompressZIP;
 					break;
 					break;
 
 
 				case 'PIZ_COMPRESSION':
 				case 'PIZ_COMPRESSION':
-					EXRDecoder.lines = 32;
+					EXRDecoder.blockHeight = 32;
 					EXRDecoder.uncompress = uncompressPIZ;
 					EXRDecoder.uncompress = uncompressPIZ;
 					break;
 					break;
 
 
 				case 'PXR24_COMPRESSION':
 				case 'PXR24_COMPRESSION':
-					EXRDecoder.lines = 16;
+					EXRDecoder.blockHeight = 16;
 					EXRDecoder.uncompress = uncompressPXR;
 					EXRDecoder.uncompress = uncompressPXR;
 					break;
 					break;
 
 
 				case 'DWAA_COMPRESSION':
 				case 'DWAA_COMPRESSION':
-					EXRDecoder.lines = 32;
+					EXRDecoder.blockHeight = 32;
 					EXRDecoder.uncompress = uncompressDWA;
 					EXRDecoder.uncompress = uncompressDWA;
 					break;
 					break;
 
 
 				case 'DWAB_COMPRESSION':
 				case 'DWAB_COMPRESSION':
-					EXRDecoder.lines = 256;
+					EXRDecoder.blockHeight = 256;
 					EXRDecoder.uncompress = uncompressDWA;
 					EXRDecoder.uncompress = uncompressDWA;
 					break;
 					break;
 
 
@@ -2126,8 +2333,6 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			EXRDecoder.scanlineBlockSize = EXRDecoder.lines;
-
 			const channels = {};
 			const channels = {};
 			for ( const channel of EXRHeader.channels ) {
 			for ( const channel of EXRHeader.channels ) {
 
 
@@ -2172,12 +2377,10 @@ class EXRLoader extends DataTextureLoader {
 
 
 					case FloatType:
 					case FloatType:
 						EXRDecoder.getter = parseFloat16;
 						EXRDecoder.getter = parseFloat16;
-						EXRDecoder.inputSize = INT16_SIZE;
 						break;
 						break;
 
 
 					case HalfFloatType:
 					case HalfFloatType:
 						EXRDecoder.getter = parseUint16;
 						EXRDecoder.getter = parseUint16;
-						EXRDecoder.inputSize = INT16_SIZE;
 						break;
 						break;
 
 
 				}
 				}
@@ -2189,12 +2392,10 @@ class EXRLoader extends DataTextureLoader {
 
 
 					case FloatType:
 					case FloatType:
 						EXRDecoder.getter = parseFloat32;
 						EXRDecoder.getter = parseFloat32;
-						EXRDecoder.inputSize = FLOAT32_SIZE;
 						break;
 						break;
 
 
 					case HalfFloatType:
 					case HalfFloatType:
 						EXRDecoder.getter = decodeFloat32;
 						EXRDecoder.getter = decodeFloat32;
-						EXRDecoder.inputSize = FLOAT32_SIZE;
 
 
 				}
 				}
 
 
@@ -2204,13 +2405,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			EXRDecoder.blockCount = EXRDecoder.height / EXRDecoder.scanlineBlockSize;
-
-			for ( let i = 0; i < EXRDecoder.blockCount; i ++ )
-				parseInt64( dataView, offset ); // scanlineOffset
-
-			// we should be passed the scanline offset table, ready to start reading pixel data.
-
+			EXRDecoder.columns = EXRDecoder.width;
 			const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels;
 			const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels;
 
 
 			switch ( outputType ) {
 			switch ( outputType ) {
@@ -2243,7 +2438,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 				if ( EXRDecoder.decodeChannels[ channel.name ] !== undefined ) {
 				if ( EXRDecoder.decodeChannels[ channel.name ] !== undefined ) {
 
 
-					EXRDecoder.channelLineOffsets[ channel.name ] = byteOffset * EXRDecoder.width;
+					EXRDecoder.channelByteOffsets[ channel.name ] = byteOffset;
 
 
 				}
 				}
 
 
@@ -2251,7 +2446,7 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			EXRDecoder.bytesPerLine = EXRDecoder.width * byteOffset;
+			EXRDecoder.totalBytes = byteOffset;
 			EXRDecoder.outLineWidth = EXRDecoder.width * EXRDecoder.outputChannels;
 			EXRDecoder.outLineWidth = EXRDecoder.width * EXRDecoder.outputChannels;
 
 
 			if ( EXRHeader.lineOrder === 'INCREASING_Y' ) {
 			if ( EXRHeader.lineOrder === 'INCREASING_Y' ) {
@@ -2276,66 +2471,55 @@ class EXRLoader extends DataTextureLoader {
 
 
 			}
 			}
 
 
-			return EXRDecoder;
-
-		}
-
-		// start parsing file [START]
-
-		const bufferDataView = new DataView( buffer );
-		const uInt8Array = new Uint8Array( buffer );
-		const offset = { value: 0 };
-
-		// get header information and validate format.
-		const EXRHeader = parseHeader( bufferDataView, buffer, offset );
-
-		// get input compression information and prepare decoding.
-		const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type );
-
-		const tmpOffset = { value: 0 };
+			if ( EXRHeader.spec.singleTile ) {
 
 
-		for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) {
+				EXRDecoder.blockHeight = EXRHeader.tiles.ySize;
+				EXRDecoder.blockWidth = EXRHeader.tiles.xSize;
 
 
-			const line = parseInt32( bufferDataView, offset ) - EXRHeader.dataWindow.yMin; // line_no
-			EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len
-			EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.scanlineBlockSize );
+				const numXLevels = calculateTileLevels( EXRHeader.tiles, EXRDecoder.width, EXRDecoder.height );
+				// const numYLevels = calculateTileLevels( EXRHeader.tiles, EXRDecoder.width, EXRDecoder.height );
 
 
-			const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine;
-			const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
+				const numXTiles = calculateTiles( numXLevels, EXRDecoder.width, EXRHeader.tiles.xSize, EXRHeader.tiles.roundingMode );
+				const numYTiles = calculateTiles( numXLevels, EXRDecoder.height, EXRHeader.tiles.ySize, EXRHeader.tiles.roundingMode );
 
 
-			offset.value += EXRDecoder.size;
+				EXRDecoder.tileCount = numXTiles[ 0 ] * numYTiles[ 0 ];
 
 
-			for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) {
+				for ( let l = 0; l < numXLevels; l ++ )
+					for ( let y = 0; y < numYTiles[ l ]; y ++ )
+						for ( let x = 0; x < numXTiles[ l ]; x ++ )
+							parseInt64( dataView, offset ); // tileOffset
 
 
-				const scan_y = scanlineBlockIdx * EXRDecoder.scanlineBlockSize;
-				const true_y = line_y + EXRDecoder.scanOrder( scan_y );
-				if ( true_y >= EXRDecoder.height ) continue;
+				EXRDecoder.decode = parseTiles.bind( EXRDecoder );
 
 
-				const lineOffset = line_y * EXRDecoder.bytesPerLine;
-				const outLineOffset = ( EXRDecoder.height - 1 - true_y ) * EXRDecoder.outLineWidth;
+			} else {
 
 
-				for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) {
+				EXRDecoder.blockWidth = EXRDecoder.width;
+				const blockCount = Math.ceil( EXRDecoder.height / EXRDecoder.blockHeight );
 
 
-					const name = EXRHeader.channels[ channelID ].name;
-					const lOff = EXRDecoder.channelLineOffsets[ name ];
-					const cOff = EXRDecoder.decodeChannels[ name ];
+				for ( let i = 0; i < blockCount; i ++ )
+					parseInt64( dataView, offset ); // scanlineOffset
 
 
-					if ( cOff === undefined ) continue;
+				EXRDecoder.decode = parseScanline.bind( EXRDecoder );
 
 
-					tmpOffset.value = lineOffset + lOff;
+			}
 
 
-					for ( let x = 0; x < EXRDecoder.width; x ++ ) {
+			return EXRDecoder;
 
 
-						const outIndex = outLineOffset + x * EXRDecoder.outputChannels + cOff;
-						EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
+		}
 
 
-					}
+		// start parsing file [START]
+		const offset = { value: 0 };
+		const bufferDataView = new DataView( buffer );
+		const uInt8Array = new Uint8Array( buffer );
 
 
-				}
+		// get header information and validate format.
+		const EXRHeader = parseHeader( bufferDataView, buffer, offset );
 
 
-			}
+		// get input compression information and prepare decoding.
+		const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type );
 
 
-		}
+		// parse input data
+		EXRDecoder.decode();
 
 
 		return {
 		return {
 			header: EXRHeader,
 			header: EXRHeader,