Преглед изворни кода

UltraHDRLoader: Improve parsing performance (#32468)

mrdoob пре 1 месец
родитељ
комит
333be98402
1 измењених фајлова са 100 додато и 121 уклоњено
  1. 100 121
      examples/jsm/loaders/UltraHDRLoader.js

+ 100 - 121
examples/jsm/loaders/UltraHDRLoader.js

@@ -29,12 +29,13 @@ import {
  */
 
 
-// Calculating this SRGB powers is extremely slow for 4K images and can be sufficiently precalculated for a 3-4x speed boost
-const SRGB_TO_LINEAR = Array( 1024 )
-	.fill( 0 )
-	.map( ( _, value ) =>
-		Math.pow( ( value / 255 ) * 0.9478672986 + 0.0521327014, 2.4 )
-	);
+// Pre-calculated sRGB to linear lookup table for values 0-1023
+const SRGB_TO_LINEAR = new Float64Array( 1024 );
+for ( let i = 0; i < 1024; i ++ ) {
+
+	SRGB_TO_LINEAR[ i ] = Math.pow( ( i / 255 ) * 0.9478672986 + 0.0521327014, 2.4 );
+
+}
 
 /**
  * A loader for the Ultra HDR Image Format.
@@ -107,6 +108,8 @@ class UltraHDRLoader extends Loader {
 	 */
 	parse( buffer, onLoad ) {
 
+		console.time( 'UltraHDRLoader' );
+
 		const xmpMetadata = {
 			version: null,
 			baseRenditionIsHDR: null,
@@ -120,53 +123,69 @@ class UltraHDRLoader extends Loader {
 		};
 		const textDecoder = new TextDecoder();
 
-		const data = new DataView( buffer );
+		const bytes = new Uint8Array( buffer );
 
-		let byteOffset = 0;
 		const sections = [];
 
-		while ( byteOffset < data.byteLength ) {
+		// JPEG segment-aware scanner using length headers
+		let offset = 0;
+
+		while ( offset < bytes.length - 1 ) {
 
-			const byte = data.getUint8( byteOffset );
+			// Find marker prefix
+			if ( bytes[ offset ] !== 0xff ) {
 
-			if ( byte === 0xff ) {
+				offset ++;
+				continue;
 
-				const leadingByte = data.getUint8( byteOffset + 1 );
+			}
 
-				if (
-					[
-						/* Valid section headers */
-						0xd8, // SOI
-						0xe0, // APP0
-						0xe1, // APP1
-						0xe2, // APP2
-					].includes( leadingByte )
-				) {
+			const markerType = bytes[ offset + 1 ];
 
-					sections.push( {
-						sectionType: leadingByte,
-						section: [ byte, leadingByte ],
-						sectionOffset: byteOffset + 2,
-					} );
+			// SOI (0xD8) - Start of Image, no length field
+			if ( markerType === 0xd8 ) {
 
-					byteOffset += 2;
+				sections.push( {
+					sectionType: markerType,
+					section: bytes.subarray( offset, offset + 2 ),
+					sectionOffset: offset + 2,
+				} );
 
-				} else {
+				offset += 2;
+				continue;
 
-					sections[ sections.length - 1 ].section.push( byte, leadingByte );
+			}
 
-					byteOffset += 2;
+			// APP0-APP2 segments have length headers
+			if ( markerType === 0xe0 || markerType === 0xe1 || markerType === 0xe2 ) {
 
-				}
+				// Length is stored as big-endian 16-bit value (includes length bytes, excludes marker)
+				const segmentLength = ( bytes[ offset + 2 ] << 8 ) | bytes[ offset + 3 ];
+				const segmentEnd = offset + 2 + segmentLength;
 
-			} else {
+				sections.push( {
+					sectionType: markerType,
+					section: bytes.subarray( offset, segmentEnd ),
+					sectionOffset: offset + 2,
+				} );
+
+				offset = segmentEnd;
+				continue;
 
-				sections[ sections.length - 1 ].section.push( byte );
+			}
 
-				byteOffset ++;
+			// Skip other markers with length fields (0xC0-0xFE range, except RST and EOI)
+			if ( markerType >= 0xc0 && markerType <= 0xfe && markerType !== 0xd9 && ( markerType < 0xd0 || markerType > 0xd7 ) ) {
+
+				const segmentLength = ( bytes[ offset + 2 ] << 8 ) | bytes[ offset + 3 ];
+				offset += 2 + segmentLength;
+				continue;
 
 			}
 
+			// EOI (0xD9) or RST markers (0xD0-0xD7) - no length field
+			offset += 2;
+
 		}
 
 		let primaryImage, gainmapImage;
@@ -190,9 +209,7 @@ class UltraHDRLoader extends Loader {
 
 				/* Data Sections - MPF / EXIF / ICC Profile */
 
-				const sectionData = new DataView(
-					new Uint8Array( section.slice( 2 ) ).buffer
-				);
+				const sectionData = new DataView( section.buffer, section.byteOffset + 2, section.byteLength - 2 );
 				const sectionHeader = sectionData.getUint32( 2, false );
 
 				if ( sectionHeader === 0x4d504600 ) {
@@ -243,13 +260,13 @@ class UltraHDRLoader extends Loader {
 						6;
 
 					primaryImage = new Uint8Array(
-						data.buffer,
+						buffer,
 						primaryImageOffset,
 						primaryImageSize
 					);
 
 					gainmapImage = new Uint8Array(
-						data.buffer,
+						buffer,
 						gainmapImageOffset,
 						gainmapImageSize
 					);
@@ -275,6 +292,8 @@ class UltraHDRLoader extends Loader {
 				gainmapImage,
 				( hdrBuffer, width, height ) => {
 
+					console.timeEnd( 'UltraHDRLoader' );
+
 					onLoad( {
 						width,
 						height,
@@ -447,46 +466,14 @@ class UltraHDRLoader extends Loader {
 		onError
 	) {
 
-		const getImageDataFromBuffer = ( buffer ) =>
-			new Promise( ( resolve, reject ) => {
-
-				const imageLoader = document.createElement( 'img' );
-
-				imageLoader.onload = () => {
-
-					const image = {
-						width: imageLoader.naturalWidth,
-						height: imageLoader.naturalHeight,
-						source: imageLoader,
-					};
+		const decodeImage = ( data ) => createImageBitmap( new Blob( [ data ], { type: 'image/jpeg' } ) );
 
-					URL.revokeObjectURL( imageLoader.src );
-
-					resolve( image );
-
-				};
-
-				imageLoader.onerror = () => {
-
-					URL.revokeObjectURL( imageLoader.src );
-
-					reject();
-
-				};
-
-				imageLoader.src = URL.createObjectURL(
-					new Blob( [ buffer ], { type: 'image/jpeg' } )
-				);
-
-			} );
-
-		Promise.all( [
-			getImageDataFromBuffer( sdrBuffer ),
-			getImageDataFromBuffer( gainmapBuffer ),
-		] )
+		Promise.all( [ decodeImage( sdrBuffer ), decodeImage( gainmapBuffer ) ] )
 			.then( ( [ sdrImage, gainmapImage ] ) => {
 
-				const sdrImageAspect = sdrImage.width / sdrImage.height;
+				const sdrWidth = sdrImage.width;
+				const sdrHeight = sdrImage.height;
+				const sdrImageAspect = sdrWidth / sdrHeight;
 				const gainmapImageAspect = gainmapImage.width / gainmapImage.height;
 
 				if ( sdrImageAspect !== gainmapImageAspect ) {
@@ -505,50 +492,39 @@ class UltraHDRLoader extends Loader {
 					colorSpace: 'srgb',
 				} );
 
-				canvas.width = sdrImage.width;
-				canvas.height = sdrImage.height;
+				canvas.width = sdrWidth;
+				canvas.height = sdrHeight;
 
 				/* Use out-of-the-box interpolation of Canvas API to scale gainmap to fit the SDR resolution */
 				ctx.drawImage(
-					gainmapImage.source,
+					gainmapImage,
 					0,
 					0,
 					gainmapImage.width,
 					gainmapImage.height,
 					0,
 					0,
-					sdrImage.width,
-					sdrImage.height
+					sdrWidth,
+					sdrHeight
 				);
 				const gainmapImageData = ctx.getImageData(
 					0,
 					0,
-					sdrImage.width,
-					sdrImage.height,
+					sdrWidth,
+					sdrHeight,
 					{ colorSpace: 'srgb' }
 				);
 
-				ctx.drawImage( sdrImage.source, 0, 0 );
+				ctx.drawImage( sdrImage, 0, 0 );
 				const sdrImageData = ctx.getImageData(
 					0,
 					0,
-					sdrImage.width,
-					sdrImage.height,
+					sdrWidth,
+					sdrHeight,
 					{ colorSpace: 'srgb' }
 				);
 
 				/* HDR Recovery formula - https://developer.android.com/media/platform/hdr-image-format#use_the_gain_map_to_create_adapted_HDR_rendition */
-				let hdrBuffer;
-
-				if ( this.type === HalfFloatType ) {
-
-					hdrBuffer = new Uint16Array( sdrImageData.data.length ).fill( 15360 );
-
-				} else {
-
-					hdrBuffer = new Float32Array( sdrImageData.data.length ).fill( 1.0 );
-
-				}
 
 				const maxDisplayBoost = Math.sqrt(
 					Math.pow(
@@ -564,55 +540,58 @@ class UltraHDRLoader extends Loader {
 					Math.max( unclampedWeightFactor, 0.0 ),
 					1.0
 				);
-				const useGammaOne = xmpMetadata.gamma === 1.0;
 
-				for (
-					let pixelIndex = 0;
-					pixelIndex < sdrImageData.data.length;
-					pixelIndex += 4
-				) {
+				const sdrData = sdrImageData.data;
+				const gainmapData = gainmapImageData.data;
+				const dataLength = sdrData.length;
+				const gainMapMin = xmpMetadata.gainMapMin;
+				const gainMapMax = xmpMetadata.gainMapMax;
+				const offsetSDR = xmpMetadata.offsetSDR;
+				const offsetHDR = xmpMetadata.offsetHDR;
+				const invGamma = 1.0 / xmpMetadata.gamma;
+				const useGammaOne = xmpMetadata.gamma === 1.0;
+				const isHalfFloat = this.type === HalfFloatType;
+				const toHalfFloat = DataUtils.toHalfFloat;
 
-					const x = ( pixelIndex / 4 ) % sdrImage.width;
-					const y = Math.floor( pixelIndex / 4 / sdrImage.width );
+				const hdrBuffer = isHalfFloat
+					? new Uint16Array( dataLength ).fill( 15360 )
+					: new Float32Array( dataLength ).fill( 1.0 );
 
-					for ( let channelIndex = 0; channelIndex < 3; channelIndex ++ ) {
+				for ( let i = 0; i < dataLength; i += 4 ) {
 
-						const sdrValue = sdrImageData.data[ pixelIndex + channelIndex ];
+					for ( let c = 0; c < 3; c ++ ) {
 
-						const gainmapIndex = ( y * sdrImage.width + x ) * 4 + channelIndex;
-						const gainmapValue = gainmapImageData.data[ gainmapIndex ] / 255.0;
+						const idx = i + c;
+						const sdrValue = sdrData[ idx ];
+						const gainmapValue = gainmapData[ idx ] * 0.00392156862745098; // 1/255
 
-						/* Gamma is 1.0 by default */
 						const logRecovery = useGammaOne
 							? gainmapValue
-							: Math.pow( gainmapValue, 1.0 / xmpMetadata.gamma );
+							: Math.pow( gainmapValue, invGamma );
 
-						const logBoost =
-							xmpMetadata.gainMapMin * ( 1.0 - logRecovery ) +
-							xmpMetadata.gainMapMax * logRecovery;
+						const logBoost = gainMapMin + ( gainMapMax - gainMapMin ) * logRecovery;
 
 						const hdrValue =
-							( sdrValue + xmpMetadata.offsetSDR ) *
+							( sdrValue + offsetSDR ) *
 								( logBoost * weightFactor === 0.0
 									? 1.0
 									: Math.pow( 2, logBoost * weightFactor ) ) -
-							xmpMetadata.offsetHDR;
+							offsetHDR;
 
 						const linearHDRValue = Math.min(
 							Math.max( this._srgbToLinear( hdrValue ), 0 ),
 							65504
 						);
 
-						hdrBuffer[ pixelIndex + channelIndex ] =
-							this.type === HalfFloatType
-								? DataUtils.toHalfFloat( linearHDRValue )
-								: linearHDRValue;
+						hdrBuffer[ idx ] = isHalfFloat
+							? toHalfFloat( linearHDRValue )
+							: linearHDRValue;
 
 					}
 
 				}
 
-				onSuccess( hdrBuffer, sdrImage.width, sdrImage.height );
+				onSuccess( hdrBuffer, sdrWidth, sdrHeight );
 
 			} )
 			.catch( () => {

粤ICP备19079148号