diff --git a/h3d/impl/GlDriver.hx b/h3d/impl/GlDriver.hx index 82dc241bb5..848d640fce 100644 --- a/h3d/impl/GlDriver.hx +++ b/h3d/impl/GlDriver.hx @@ -127,6 +127,7 @@ class GlDriver extends Driver { static var UID = 0; public var gl : GL2; public static var ALLOW_WEBGL2 = true; + public var textureSupport:hxd.PixelFormat; #end #if (hlsdl||usegl) @@ -783,7 +784,10 @@ class GlDriver extends Driver { case GL2.RED, GL2.R8, GL2.R16F, GL2.R32F: GL2.RED; case GL2.RG, GL2.RG8, GL2.RG16F, GL2.RG32F: GL2.RG; case GL2.RGB16F, GL2.RGB32F: GL.RGB; - case 0x83F1, 0x83F2, 0x83F3: GL.RGBA; + case hxd.PixelFormat.DXT_FORMAT.RGBA_DXT1,hxd.PixelFormat.DXT_FORMAT.RGBA_DXT3, + hxd.PixelFormat.DXT_FORMAT.RGBA_DXT5,hxd.PixelFormat.ASTC_FORMAT.RGBA_4x4, + hxd.PixelFormat.PVRTC_FORMAT.RGBA_4BPPV1: GL.RGBA; + case hxd.PixelFormat.PVRTC_FORMAT.RGB_4BPPV1, hxd.PixelFormat.ETC_FORMAT.RGB_ETC1: GL.RGB; default: throw "Invalid format " + t.internalFmt; } } @@ -795,6 +799,7 @@ class GlDriver extends Driver { case SRGB, SRGB_ALPHA: hasFeature(SRGBTextures); case R8, RG8, RGB8, R16F, RG16F, RGB16F, R32F, RG32F, RGB32F, RG11B10UF, RGB10A2: #if js glES >= 3 #else true #end; case S3TC(n): n <= maxCompressedTexturesSupport; + case ASTC(_), ETC(_), PVRTC(_): #if js true #else false #end; default: false; } } @@ -856,12 +861,30 @@ class GlDriver extends Driver { tt.internalFmt = GL2.R11F_G11F_B10F; tt.pixelFmt = GL2.UNSIGNED_INT_10F_11F_11F_REV; case S3TC(n) if( n <= maxCompressedTexturesSupport ): - if( t.width&3 != 0 || t.height&3 != 0 ) - throw "Compressed texture "+t+" has size "+t.width+"x"+t.height+" - must be a multiple of 4"; + checkMult4(t); switch( n ) { - case 1: tt.internalFmt = 0x83F1; // COMPRESSED_RGBA_S3TC_DXT1_EXT - case 2: tt.internalFmt = 0x83F2; // COMPRESSED_RGBA_S3TC_DXT3_EXT - case 3: tt.internalFmt = 0x83F3; // COMPRESSED_RGBA_S3TC_DXT5_EXT + case 1: tt.internalFmt = hxd.PixelFormat.DXT_FORMAT.RGBA_DXT1; + case 2: tt.internalFmt = hxd.PixelFormat.DXT_FORMAT.RGBA_DXT3; + case 3: tt.internalFmt = hxd.PixelFormat.DXT_FORMAT.RGBA_DXT5; + default: throw "Unsupported texture format "+t.format; + } + case ASTC(n): + checkMult4(t); + switch( n ) { + case 10: tt.internalFmt = hxd.PixelFormat.ASTC_FORMAT.RGBA_4x4; + default: throw "Unsupported texture format "+t.format; + } + case ETC(n): + checkMult4(t); + switch( n ) { + case 0: tt.internalFmt = hxd.PixelFormat.ETC_FORMAT.RGB_ETC1; + default: throw "Unsupported texture format "+t.format; + } + case PVRTC(n): + checkMult4(t); + switch(n) { + case 8: tt.internalFmt = hxd.PixelFormat.PVRTC_FORMAT.RGB_4BPPV1; + case 9: tt.internalFmt = hxd.PixelFormat.PVRTC_FORMAT.RGBA_4BPPV1; default: throw "Unsupported texture format "+t.format; } default: @@ -885,7 +908,7 @@ class GlDriver extends Driver { if( t.flags.has(Cube) ) { for( i in 0...6 ) { - gl.texImage2D(CUBE_FACES[i], 0, tt.internalFmt, tt.width, tt.height, 0, getChannels(tt), tt.pixelFmt, null); + gl.texImage2D(CUBE_FACES[i], 0, tt.internalFmt, tt.width, tt.height, 0, getChannels(tt), tt.pixelFmt, null); if( checkError() ) break; } } else if( t.flags.has(IsArray) ) { @@ -893,7 +916,7 @@ class GlDriver extends Driver { checkError(); } else { #if js - if( !t.format.match(S3TC(_)) ) + if( !t.format.match(S3TC(_)) && !t.format.match(ETC(_)) && !t.format.match(ASTC(_)) && !t.format.match(PVRTC(_))) #end gl.texImage2D(bind, 0, tt.internalFmt, tt.width, tt.height, 0, getChannels(tt), tt.pixelFmt, null); checkError(); @@ -908,6 +931,11 @@ class GlDriver extends Driver { return tt; } + inline function checkMult4(t) { + if( t.width&3 != 0 || t.height&3 != 0 ) + throw "Compressed texture "+t+" has size "+t.width+"x"+t.height+" - must be a multiple of 4"; + } + function restoreBind() { var t = boundTextures[lastActiveIndex]; if( t == null ) @@ -1127,12 +1155,15 @@ class GlDriver extends Driver { case RGBA32F, R32F, RG32F, RGB32F: new Float32Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, bufLen>>2); case RGBA16F, R16F, RG16F, RGB16F: new Uint16Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, bufLen>>1); case RGB10A2, RG11B10UF: new Uint32Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, bufLen>>2); + case ETC(_), PVRTC(_): new Uint8Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset); default: new Uint8Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset, bufLen); } - if( t.format.match(S3TC(_)) ) - gl.compressedTexImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, buffer); - else - gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, buffer); + switch (t.format) { + case S3TC(_), ASTC(_), PVRTC(_), ETC(_): + gl.compressedTexImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, buffer); + default: + gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, buffer); + } #else throw "Not implemented"; #end @@ -1526,12 +1557,33 @@ class GlDriver extends Driver { } #if js + public function checkTextureSupport():hxd.PixelFormat { + var astcSupported = gl.getExtension('WEBGL_compressed_texture_astc') != null; + var dxtSupported = gl.getExtension('WEBGL_compressed_texture_s3tc') != null; + var pvrtcSupported = gl.getExtension('WEBGL_compressed_texture_pvrtc') != null + || gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc') != null; + var etcSupported = gl.getExtension('WEBGL_compressed_texture_etc1') != null; + return if(astcSupported) { + hxd.PixelFormat.ASTC(); + } else if(dxtSupported){ + hxd.PixelFormat.S3TC(); + } else if(pvrtcSupported){ + hxd.PixelFormat.PVRTC(); + } else if(etcSupported){ + hxd.PixelFormat.ETC(); + } else { + null; + } + } var features : Map = new Map(); function makeFeatures() { for( f in Type.allEnums(Feature) ) features.set(f,checkFeature(f)); - if( gl.getExtension("WEBGL_compressed_texture_s3tc") != null ) - maxCompressedTexturesSupport = 3; + textureSupport = checkTextureSupport(); + maxCompressedTexturesSupport = switch (textureSupport) { + case hxd.PixelFormat.S3TC(_), hxd.PixelFormat.ASTC(_), hxd.PixelFormat.ETC(_), hxd.PixelFormat.PVRTC(_): 3; + default: 0; + } } function checkFeature( f : Feature ) { return switch( f ) { diff --git a/hxd/PixelFormat.hx b/hxd/PixelFormat.hx index c7ed0743e5..d9decd2439 100644 --- a/hxd/PixelFormat.hx +++ b/hxd/PixelFormat.hx @@ -19,5 +19,27 @@ enum PixelFormat { SRGB_ALPHA; RGB10A2; RG11B10UF; // unsigned float - S3TC( v : Int ); -} \ No newline at end of file + ASTC( ?v : Int ); + ETC( ?v : Int ); + S3TC( ?v : Int ); + PVRTC( ?v : Int); +} + +enum abstract ASTC_FORMAT(Int) from Int to Int { + final RGBA_4x4 = 0x93B0; +} + +enum abstract DXT_FORMAT(Int) from Int to Int { + final RGBA_DXT1 = 0x83F1; + final RGBA_DXT3 = 0x83F2; + final RGBA_DXT5 = 0x83F3; +} + +enum abstract ETC_FORMAT(Int) from Int to Int { + final RGB_ETC1 = 0x8D64; +} + +enum abstract PVRTC_FORMAT(Int) from Int to Int { + final RGB_4BPPV1 = 0x8C00; + final RGBA_4BPPV1 = 0x8C02; +} diff --git a/hxd/Pixels.hx b/hxd/Pixels.hx index 6d0168262a..9a60a6867b 100644 --- a/hxd/Pixels.hx +++ b/hxd/Pixels.hx @@ -321,6 +321,9 @@ class Pixels { } case [S3TC(a),S3TC(b)] if( a == b ): + case [ASTC(a),ASTC(b)] if( a == b ): + case [ETC(a),ETC(b)] if( a == b ): + case [PVRTC(a),PVRTC(b)] if( a == b ): // nothing #if (hl && hl_ver >= "1.10") @@ -420,6 +423,7 @@ class Pixels { case RGB32F: 12; case RGB10A2: 4; case RG11B10UF: 4; + case ASTC(n), ETC(n), PVRTC(n): 1; case S3TC(n): if( n == 1 || n == 4 ) return width >> 1; @@ -455,7 +459,7 @@ class Pixels { channel.toInt() * 4; case RGB10A2, RG11B10UF: throw "Bit packed format"; - case S3TC(_): + case S3TC(_), ASTC(_), ETC(_), PVRTC(_): throw "Not supported"; } } diff --git a/hxd/net/BinaryLoader.hx b/hxd/net/BinaryLoader.hx index c70dc23e21..8990bb9ddd 100644 --- a/hxd/net/BinaryLoader.hx +++ b/hxd/net/BinaryLoader.hx @@ -21,7 +21,7 @@ class BinaryLoader { throw msg; } - public function load() { + public function load(raw = false) { #if flash loader = new flash.net.URLLoader(); loader.dataFormat = flash.net.URLLoaderDataFormat.BINARY; @@ -43,7 +43,7 @@ class BinaryLoader { onError(xhr.statusText); return; } - onLoaded(haxe.io.Bytes.ofData(xhr.response)); + onLoaded(raw ? xhr.response : haxe.io.Bytes.ofData(xhr.response)); } xhr.onprogress = function(e) { diff --git a/hxd/res/BasisTextureLoader.hx b/hxd/res/BasisTextureLoader.hx new file mode 100644 index 0000000000..1dd8fdc634 --- /dev/null +++ b/hxd/res/BasisTextureLoader.hx @@ -0,0 +1,387 @@ +package hxd.res; +#if js +import hxd.PixelFormat; +import js.html.ImageData; +import h3d.mat.Texture; +import h3d.impl.GlDriver; +import h3d.mat.Data; + +class BasisTextureLoader { + public static var workerLimit = 4; + public static var transcoderPath = 'vendor/basis_transcoder.js'; + public static var wasmPath = 'vendor/basis_transcoder.wasm'; + + static var _workerNextTaskID = 1; + static var _workerSourceURL:String; + static var _workerConfig = { + format: 0, + astcSupported: false, + etc1Supported: false, + etc2Supported: false, + dxtSupported: false, + pvrtcSupported: false, + }; + static var _workerPool:Array = []; + static var _transcoderPending:js.lib.Promise; + static var _transcoderBinary:Dynamic; + + public static function getTexture(bytes:haxe.io.BytesData) { + detectSupport(); + return createTexture(bytes); + } + + static function detectSupport() { + final driver:GlDriver = cast h3d.Engine.getCurrent().driver; + final fmt = driver.textureSupport; + _workerConfig.format = switch(fmt) { + case ETC(_): BASIS_FORMAT.cTFETC1; + case ASTC(_): BASIS_FORMAT.cTFASTC_4x4; + case S3TC(_): BASIS_FORMAT.cTFBC3; + case PVRTC(_): BASIS_FORMAT.cTFPVRTC1_4_RGBA; + default: throw 'No suitable compressed texture format found.'; + } + } + + static function createTexture(buffer:haxe.io.BytesData):js.lib.Promise { + var worker:js.html.Worker; + var workerTask:WorkerTask; + var taskID:Int; + var texturePending = getWorker().then((task) -> { + workerTask = task; + worker = workerTask.worker; + taskID = _workerNextTaskID++; + return new js.lib.Promise((resolve, reject) -> { + workerTask.callbacks.set(taskID, { + resolve: resolve, + reject: reject, + }); + workerTask.taskCosts.set(taskID, buffer.byteLength); + workerTask.taskLoad += workerTask.taskCosts.get(taskID); + worker.postMessage({type: 'transcode', id: taskID, buffer: buffer}, [buffer]); + }); + }).then((message) -> { + final w = message.width; + final h = message.height; + final mipmaps:Array = message.mipmaps; + final format = message.format; + final create = (fmt) -> { + final texture = new h3d.mat.Texture(w, h, null, fmt); + var level = 0; + for (mipmap in mipmaps) { + final bytes = haxe.io.Bytes.ofData(cast mipmap.data); + final pixels = new hxd.Pixels(mipmap.width, mipmap.height, bytes, fmt); + texture.uploadPixels(pixels, level); + level++; + } + return texture; + } + var texture:h3d.mat.Texture; + switch (format) { + case BASIS_FORMAT.cTFASTC_4x4: + texture = create(hxd.PixelFormat.ASTC(format)); + case BASIS_FORMAT.cTFBC1, BASIS_FORMAT.cTFBC3: + texture = create(hxd.PixelFormat.S3TC(format)); + case BASIS_FORMAT.cTFETC1: + texture = create(hxd.PixelFormat.ETC(format)); + case BASIS_FORMAT.cTFPVRTC1_4_RGB, BASIS_FORMAT.cTFPVRTC1_4_RGBA: + texture = create(hxd.PixelFormat.PVRTC(format)); + default: + throw 'BasisTextureLoader: No supported format available.'; + } + if (mipmaps.length > 1) { + texture.flags.set(MipMapped); + } + return texture; + }).then((tex) -> { + if (workerTask != null && taskID > 0) { + workerTask.taskLoad -= workerTask.taskCosts.get(taskID); + workerTask.callbacks.remove(taskID); + workerTask.taskCosts.remove(taskID); + } + return tex; + }); + return texturePending; + } + + static function initTranscoder() { + if (_transcoderBinary == null) { + // Load transcoder wrapper. + final jsLoader = new hxd.net.BinaryLoader(transcoderPath); + final jsContent = new js.lib.Promise((resolve, reject) -> { + jsLoader.onLoaded = resolve; + jsLoader.onError = reject; + jsLoader.load(); + }); + // Load transcoder WASM binary. + final binaryLoader = new hxd.net.BinaryLoader(wasmPath); + final binaryContent = new js.lib.Promise((resolve, reject) -> { + binaryLoader.onLoaded = resolve; + binaryLoader.onError = reject; + binaryLoader.load(true); + }); + + _transcoderPending = js.lib.Promise.all([jsContent, binaryContent]).then((arr) -> { + final transcoder = arr[0]; + final wasm = arr[1]; + var fn = BasisWorker.func; + + var body = [ + '/* basis_transcoder.js */', + transcoder, + '/* worker */', + fn.substring(fn.indexOf('{') + 1, fn.lastIndexOf('}')) + ].join('\n'); + + _workerSourceURL = js.html.URL.createObjectURL(new js.html.Blob([body])); + _transcoderBinary = wasm; + }); + } + + return _transcoderPending; + } + + static function getWorker() { + return initTranscoder().then((val) -> { + if (_workerPool.length < workerLimit) { + final worker = new js.html.Worker(_workerSourceURL); + final workerTask:WorkerTask = { + worker: worker, + callbacks: new haxe.ds.IntMap(), + taskCosts: new haxe.ds.IntMap(), + taskLoad: 0, + } + + worker.postMessage({ + type: 'init', + config: _workerConfig, + transcoderBinary: _transcoderBinary, + }); + + worker.onmessage = function(e) { + var message = e.data; + + switch (message.type) { + case 'transcode': + workerTask.callbacks.get(message.id).resolve(message); + case 'error': + workerTask.callbacks.get(message.id).reject(message); + default: + throw 'BasisTextureLoader: Unexpected message, "' + message.type + '"'; + } + }; + + _workerPool.push(workerTask); + } else { + _workerPool.sort(function(a, b) { + return a.taskLoad > b.taskLoad ? -1 : 1; + }); + } + + return _workerPool[_workerPool.length - 1]; + }); + } +} + +enum abstract BASIS_FORMAT(Int) from Int to Int { + final cTFETC1 = 0; + final cTFETC2 = 1; + final cTFBC1 = 2; + final cTFBC3 = 3; + final cTFBC4 = 4; + final cTFBC5 = 5; + final cTFBC7_M6_OPAQUE_ONLY = 6; + final cTFBC7_M5 = 7; + final cTFPVRTC1_4_RGB = 8; + final cTFPVRTC1_4_RGBA = 9; + final cTFASTC_4x4 = 10; + final cTFATC_RGB = 11; + final cTFATC_RGBA_INTERPOLATED_ALPHA = 12; + final cTFRGBA32 = 13; + final cTFRGB565 = 14; + final cTFBGR565 = 15; + final cTFRGBA4444 = 16; +} + +enum abstract FORMAT(Int) from Int to Int { + final RGB_S3TC_DXT1_Format = 33776; + final RGBA_S3TC_DXT1_Format = 33777; + final RGBA_S3TC_DXT3_Format = 33778; + final RGBA_S3TC_DXT5_Format = 33779; + final RGB_PVRTC_4BPPV1_Format = 35840; + final RGB_PVRTC_2BPPV1_Format = 35841; + final RGBA_PVRTC_4BPPV1_Format = 35842; + final RGBA_PVRTC_2BPPV1_Format = 35843; + final RGB_ETC1_Format = 36196; + final RGBA_ASTC_4x4_Format = 37808; + final RGBA_ASTC_5x4_Format = 37809; + final RGBA_ASTC_5x5_Format = 37810; + final RGBA_ASTC_6x5_Format = 37811; + final RGBA_ASTC_6x6_Format = 37812; + final RGBA_ASTC_8x5_Format = 37813; + final RGBA_ASTC_8x6_Format = 37814; + final RGBA_ASTC_8x8_Format = 37815; + final RGBA_ASTC_10x5_Format = 37816; + final RGBA_ASTC_10x6_Format = 37817; + final RGBA_ASTC_10x8_Format = 37818; + final RGBA_ASTC_10x10_Format = 37819; + final RGBA_ASTC_12x10_Format = 37820; + final RGBA_ASTC_12x12_Format = 37821; +} + +typedef WorkerTask = { + worker:js.html.Worker, + callbacks:haxe.ds.IntMap<{resolve:(value:Dynamic) -> Void, reject:(reason:Dynamic) -> Void}>, + taskCosts:haxe.ds.IntMap, + taskLoad:Int, +} + +class BasisWorker { + static public final func = "function () { + + var config; + var transcoderPending; + var _BasisFile; + + onmessage = function ( e ) { + + var message = e.data; + + switch ( message.type ) { + + case 'init': + config = message.config; + init( message.transcoderBinary ); + break; + + case 'transcode': + transcoderPending.then( () => { + + try { + + var { width, height, hasAlpha, mipmaps, format } = transcode( message.buffer ); + + var buffers = []; + + for ( var i = 0; i < mipmaps.length; ++ i ) { + + buffers.push( mipmaps[ i ].data.buffer ); + + } + self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } + + } ); + break; + + } + + }; + + function init( wasmBinary ) { + + var BasisModule; + transcoderPending = new Promise( ( resolve ) => { + + BasisModule = { wasmBinary, onRuntimeInitialized: resolve }; + BASIS( BasisModule ); + + } ).then( () => { + + var { BasisFile, initializeBasis } = BasisModule; + + _BasisFile = BasisFile; + + initializeBasis(); + + } ); + + } + + function transcode( buffer ) { + + var basisFile = new _BasisFile( new Uint8Array( buffer ) ); + + var width = basisFile.getImageWidth( 0, 0 ); + var height = basisFile.getImageHeight( 0, 0 ); + var levels = basisFile.getNumLevels( 0 ); + var hasAlpha = basisFile.getHasAlpha(); + + function cleanup() { + + basisFile.close(); + basisFile.delete(); + + } + + if ( ! hasAlpha ) { + + switch ( config.format ) { + + case 9: // Hardcoded: BASIS_FORMAT.cTFPVRTC1_4_RGBA + config.format = 8; // BASIS_FORMAT.cTFPVRTC1_4_RGB; + break; + default: + break; + + } + + } + + if ( ! width || ! height || ! levels ) { + + cleanup(); + throw new Error( 'BasisTextureLoader: Invalid .basis file' ); + + } + + if ( ! basisFile.startTranscoding() ) { + + cleanup(); + throw new Error( 'BasisTextureLoader: .startTranscoding failed' ); + + } + + var mipmaps = []; + + for ( var mip = 0; mip < levels; mip ++ ) { + + var mipWidth = basisFile.getImageWidth( 0, mip ); + var mipHeight = basisFile.getImageHeight( 0, mip ); + var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, config.format ) ); + + var status = basisFile.transcodeImage( + dst, + 0, + mip, + config.format, + 0, + hasAlpha + ); + + if ( ! status ) { + + cleanup(); + throw new Error( 'BasisTextureLoader: .transcodeImage failed.' ); + + } + + mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } ); + + } + + cleanup(); + + return { width, height, hasAlpha, mipmaps, format: config.format }; + + } + + }"; +} +#end diff --git a/hxd/res/Image.hx b/hxd/res/Image.hx index 9ebefc8fa8..391db3c1d6 100644 --- a/hxd/res/Image.hx +++ b/hxd/res/Image.hx @@ -7,6 +7,7 @@ package hxd.res; var Gif = 2; var Tga = 3; var Dds = 4; + var Basis = 5; /* Tells if we might not be able to directly decode the image without going through a loadBitmap async call. @@ -128,7 +129,13 @@ class Image extends Resource { if( bc == 0 ) throw entry.path+" has unsupported 4CC "+String.fromCharCode(fourCC&0xFF)+String.fromCharCode((fourCC>>8)&0xFF)+String.fromCharCode((fourCC>>16)&0xFF)+String.fromCharCode(fourCC>>>24); - + case 0x4273: + format = Basis; + f.skip(63); + var slicesPos = f.readInt32(); + f.skip(slicesPos-42); + width = f.readUInt16(); + height = f.readUInt16(); case _ if( entry.extension == "tga" ): format = Tga; f.skip(10); @@ -203,7 +210,23 @@ class Image extends Resource { case Dds: var bytes = entry.getBytes(); pixels = new hxd.Pixels(inf.width, inf.height, bytes, S3TC(inf.bc), 128 + (inf.bc >= 6 ? 20 : 0)); + case Basis: + #if js + var bytes = entry.getBytes(); + var driver:h3d.impl.GlDriver = cast h3d.Engine.getCurrent().driver; + var f = switch(driver.checkTextureSupport()) { + case hxd.PixelFormat.S3TC(_): hxd.PixelFormat.S3TC(inf.bc); + case hxd.PixelFormat.ETC(_): hxd.PixelFormat.ETC(0); + case hxd.PixelFormat.ASTC(_): hxd.PixelFormat.ASTC(10); + case hxd.PixelFormat.PVRTC(_): hxd.PixelFormat.PVRTC(9); + default: throw 'Unsupported basis texture'; + } + pixels = new hxd.Pixels(inf.width, inf.height, bytes, f); + #else + throw 'Basis only supported on js target'; + #end } + if( fmt != null ) pixels.convert(fmt); if( flipY != null ) pixels.setFlip(flipY); return pixels;