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

added support for S3TC compression

Nicolas Cannasse 6 лет назад
Родитель
Сommit
da0ae22bd2
7 измененных файлов с 144 добавлено и 57 удалено
  1. 18 2
      h3d/impl/DirectXDriver.hx
  2. 41 31
      h3d/impl/GlDriver.hx
  3. 2 2
      h3d/mat/TextureChannels.hx
  4. 1 0
      hxd/PixelFormat.hx
  5. 30 16
      hxd/Pixels.hx
  6. 1 1
      hxd/fmt/hsd/Serializer.hx
  7. 51 5
      hxd/res/Image.hx

+ 18 - 2
h3d/impl/DirectXDriver.hx

@@ -364,6 +364,17 @@ class DirectXDriver extends h3d.impl.Driver {
 		case RGB10A2: R10G10B10A2_UNORM;
 		case RG11B10UF: R11G11B10_FLOAT;
 		case SRGB_ALPHA: R8G8B8A8_UNORM_SRGB;
+		case S3TC(n):
+			switch( n ) {
+			case 1: BC1_UNORM;
+			case 2: BC2_UNORM;
+			case 3: BC3_UNORM;
+			case 4: BC4_UNORM;
+			case 5: BC5_UNORM;
+			case 6: BC6H_UF16;
+			case 7: BC7_UNORM;
+			default: throw "assert";
+			}
 		default: throw "Unsupported texture format " + t.format;
 		}
 	}
@@ -541,7 +552,7 @@ class DirectXDriver extends h3d.impl.Driver {
 		}
 
 		var pitch = 0;
-		var bpp = hxd.Pixels.getBytesPerPixel(tex.format);
+		var bpp = hxd.Pixels.calcStride(1, tex.format);
 		var ptr = tmp.map(0, Read, true, pitch);
 		if( pitch == desc.width * bpp )
 			@:privateAccess pixels.bytes.b.blit(0, ptr, 0, desc.width * desc.height * bpp);
@@ -566,7 +577,12 @@ class DirectXDriver extends h3d.impl.Driver {
 		pixels.setFlip(false);
 		if( hasDeviceError ) return;
 		if( mipLevel >= t.t.mips ) throw "Mip level outside texture range : " + mipLevel + " (max = " + (t.t.mips - 1) + ")";
-		t.t.res.updateSubresource(mipLevel + side * t.t.mips, null, (pixels.bytes:hl.Bytes).offset(pixels.offset), pixels.width * pixels.bytesPerPixel, 0);
+		var stride = pixels.stride;
+		switch( t.format ) {
+		case S3TC(n): stride = pixels.width * ((n == 1 || n == 4) ? 2 : 4); // "uncompressed" stride ?
+		default:
+		}
+		t.t.res.updateSubresource(mipLevel + side * t.t.mips, null, (pixels.bytes:hl.Bytes).offset(pixels.offset), stride, 0);
 		updateResCount++;
 	}
 

+ 41 - 31
h3d/impl/GlDriver.hx

@@ -186,6 +186,7 @@ class GlDriver extends Driver {
 	var firstShader = true;
 	var rightHanded = false;
 	var hasMultiIndirect = false;
+	var maxCompressedTexturesSupport = 0;
 
 	var drawMode : Int;
 
@@ -212,6 +213,7 @@ class GlDriver extends Driver {
 
 		#if hlsdl
 		hasMultiIndirect = gl.getConfigParameter(0) > 0;
+		maxCompressedTexturesSupport = 3;
 		#end
 
 		var v : String = gl.getParameter(GL.VERSION);
@@ -343,7 +345,7 @@ class GlDriver extends Driver {
 				name = switch( tt ) {
 				case TSampler2D: mode = GL.TEXTURE_2D; "Textures";
 				case TSamplerCube: mode = GL.TEXTURE_CUBE_MAP; "TexturesCube";
-				case TSampler2DArray: #if (!hlsdl || (hlsdl >= "1.7")) mode = GL2.TEXTURE_2D_ARRAY; #end "TexturesArray";
+				case TSampler2DArray: mode = GL2.TEXTURE_2D_ARRAY; "TexturesArray";
 				default: throw "Unsupported texture type "+tt;
 				}
 				index = 0;
@@ -353,13 +355,9 @@ class GlDriver extends Driver {
 			t = t.next;
 		}
 		if( shader.bufferCount > 0 ) {
-			#if (!hlsdl || (hlsdl >= "1.7"))
 			s.buffers = [for( i in 0...shader.bufferCount ) gl.getUniformBlockIndex(p.p,"uniform_buffer"+i)];
 			for( i in 0...shader.bufferCount )
 				gl.uniformBlockBinding(p.p,s.buffers[i],i);
-			#else
-			throw "Uniform buffers require HL 1.7";
-			#end
 		}
 	}
 
@@ -784,11 +782,10 @@ class GlDriver extends Driver {
 		case GL.RGB: GL.RGB;
 		case GL2.R11F_G11F_B10F: GL.RGB;
 		case GL2.RGB10_A2: GL.RGBA;
-		#if (!hlsdl || (hlsdl >= "1.7"))
 		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;
-		#end
+		case 0x83F1, 0x83F2, 0x83F3: GL.RGBA;
 		default: throw "Invalid format " + t.internalFmt;
 		}
 	}
@@ -799,6 +796,7 @@ class GlDriver extends Driver {
 		case RGBA16F, RGBA32F: hasFeature(FloatTextures);
 		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;
 		default: false;
 		}
 	}
@@ -806,7 +804,7 @@ class GlDriver extends Driver {
 	function getBindType( t : h3d.mat.Texture ) {
 		var isCube = t.flags.has(Cube);
 		var isArray = t.flags.has(IsArray);
-		return isCube ? GL.TEXTURE_CUBE_MAP : isArray ? #if (!hlsdl || (hlsdl >= "1.7")) GL2.TEXTURE_2D_ARRAY #else throw "Texture Array requires HL 1.7" #end : GL.TEXTURE_2D;
+		return isCube ? GL.TEXTURE_CUBE_MAP : isArray ? GL2.TEXTURE_2D_ARRAY : GL.TEXTURE_2D;
 	}
 
 	override function allocTexture( t : h3d.mat.Texture ) : Texture {
@@ -831,7 +829,6 @@ class GlDriver extends Driver {
 			tt.internalFmt = GL2.SRGB8_ALPHA;
 		case RGB8:
 			tt.internalFmt = GL.RGB;
-		#if (!hlsdl || (hlsdl >= "1.7"))
 		case R8:
 			tt.internalFmt = GL2.R8;
 		case RG8:
@@ -860,7 +857,13 @@ class GlDriver extends Driver {
 		case RG11B10UF:
 			tt.internalFmt = GL2.R11F_G11F_B10F;
 			tt.pixelFmt = GL2.UNSIGNED_INT_10F_11F_11F_REV;
-		#end
+		case S3TC(n) if( n <= maxCompressedTexturesSupport ):
+			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
+			default: throw "Unsupported texture format "+t.format;
+			}
 		default:
 			throw "Unsupported texture format "+t.format;
 		}
@@ -884,12 +887,13 @@ class GlDriver extends Driver {
 				gl.texImage2D(CUBE_FACES[i], 0, tt.internalFmt, tt.width, tt.height, 0, getChannels(tt), tt.pixelFmt, null);
 				if( checkError() ) break;
 			}
-		#if (!hlsdl || (hlsdl >= "1.7"))
 		} else if( t.flags.has(IsArray) ) {
 			gl.texImage3D(GL2.TEXTURE_2D_ARRAY, 0, tt.internalFmt, tt.width, tt.height, t.layerCount, 0, getChannels(tt), tt.pixelFmt, null);
 			checkError();
-		#end
 		} else {
+			#if js
+			if( !t.format.match(BC(_)) )
+			#end
 			gl.texImage2D(bind, 0, tt.internalFmt, tt.width, tt.height, 0, getChannels(tt), tt.pixelFmt, null);
 			checkError();
 		}
@@ -1040,11 +1044,11 @@ class GlDriver extends Driver {
 	}
 
 	#if !hl
-	inline static function bytesToUint8Array( b : haxe.io.Bytes ) : Uint8Array {
+	inline static function bytesToUint8Array( b : haxe.io.Bytes, offset = 0 ) : Uint8Array {
 		#if (lime && !js)
-		return new Uint8Array(b);
+		return new Uint8Array(b,offset);
 		#else
-		return new Uint8Array(b.getData());
+		return new Uint8Array(b.getData(),offset);
 		#end
 	}
 	#end
@@ -1116,17 +1120,29 @@ class GlDriver extends Driver {
 		pixels.convert(t.format);
 		pixels.setFlip(false);
 		#if hl
-		gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, streamData(pixels.bytes.getData(),pixels.offset,pixels.width*pixels.height*pixels.bytesPerPixel));
+		var dataLen = pixels.height*pixels.stride;
+		var stream = streamData(pixels.bytes.getData(),pixels.offset,dataLen);
+		if( t.format.match(S3TC(_)) ) {
+			#if( (hlsdl == "1.8.0") || (hlsdl == "1.9.0") )
+			throw "Compressed textures require hlsdl 1.10+";
+			#else
+			gl.compressedTexImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, dataLen, stream);
+			#end
+		} else
+			gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, stream);
 		#elseif lime
 		gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, bytesToUint8Array(pixels.bytes));
 		#elseif js
 		var buffer = switch( t.format ) {
-		case RGBA32F, R32F, RG32F, RGB32F: new js.html.Float32Array(@:privateAccess pixels.bytes.b.buffer);
-		case RGBA16F, R16F, RG16F, RGB16F: new js.html.Uint16Array(@:privateAccess pixels.bytes.b.buffer);
-		case RGB10A2, RG11B10UF: new js.html.Uint32Array(@:privateAccess pixels.bytes.b.buffer);
-		default: bytesToUint8Array(pixels.bytes);
+		case RGBA32F, R32F, RG32F, RGB32F: new js.html.Float32Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset);
+		case RGBA16F, R16F, RG16F, RGB16F: new js.html.Uint16Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset);
+		case RGB10A2, RG11B10UF: new js.html.Uint32Array(@:privateAccess pixels.bytes.b.buffer, pixels.offset);
+		default: bytesToUint8Array(pixels.bytes,pixels.offset);
 		}
-		gl.texImage2D(face, mipLevel, t.t.internalFmt, pixels.width, pixels.height, 0, getChannels(t.t), t.t.pixelFmt, buffer);
+		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);
 		#else
 		throw "Not implemented";
 		#end
@@ -1191,12 +1207,8 @@ class GlDriver extends Driver {
 
 	inline function updateDivisor( a : CompiledAttribute ) {
 		if( currentDivisor[a.index] != a.divisor ) {
-			#if (!hlsdl || (hlsdl >= "1.7"))
 			currentDivisor[a.index] = a.divisor;
 			gl.vertexAttribDivisor(a.index, a.divisor);
-			#else
-			throw "vertexAttribDivisor requires HL 1.7+";
-			#end
 		}
 	}
 
@@ -1276,7 +1288,7 @@ class GlDriver extends Driver {
 	}
 
 	override function allocInstanceBuffer( b : InstanceBuffer, bytes : haxe.io.Bytes ) {
-		#if( !js && (!hlsdl || (hlsdl >= "1.7")) )
+		#if hl
 		if( hasMultiIndirect ) {
 			var buf = gl.createBuffer();
 			gl.bindBuffer(GL2.DRAW_INDIRECT_BUFFER, buf);
@@ -1459,11 +1471,9 @@ class GlDriver extends Driver {
 		#end
 		gl.bindFramebuffer(GL.FRAMEBUFFER, commonFB);
 
-		#if (!hlsdl || (hlsdl >= "1.7"))
 		if( tex.flags.has(IsArray) )
 			gl.framebufferTextureLayer(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, tex.t.t, mipLevel, layer);
 		else
-		#end
 			gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, tex.flags.has(Cube) ? CUBE_FACES[layer] : GL.TEXTURE_2D, tex.t.t, mipLevel);
 		if( tex.depthBuffer != null ) {
 			gl.framebufferRenderbuffer(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT, GL.RENDERBUFFER, @:privateAccess tex.depthBuffer.b.r);
@@ -1538,6 +1548,8 @@ class GlDriver extends Driver {
 	function makeFeatures() {
 		for( f in Type.allEnums(Feature) )
 			features.set(f,checkFeature(f));
+		if( gl.getExtension("WEBGL_compressed_texture_s3tc") != null )
+			maxCompressedTexturesSupport = 3;
 	}
 	function checkFeature( f : Feature ) {
 		return switch( f ) {
@@ -1718,10 +1730,8 @@ class GlDriver extends Driver {
 		GL.FUNC_ADD,
 		GL.FUNC_SUBTRACT,
 		GL.FUNC_REVERSE_SUBTRACT,
-		#if (!hlsdl || (hlsdl >= "1.7"))
 		GL2.FUNC_MIN,
-		GL2.FUNC_MAX,
-		#end
+		GL2.FUNC_MAX
 	];
 
 	static var CUBE_FACES = [

+ 2 - 2
h3d/mat/TextureChannels.hx

@@ -33,9 +33,9 @@ class TextureChannels extends Texture {
 	function setPixelsInner( c : hxd.Pixels.Channel, src : hxd.Pixels, srcChannel : hxd.Pixels.Channel ) {
 		if( src.width != width || src.height != height )
 			throw "Size mismatch : " + src.width + "x" + src.height + " should be " + width + "x" + height;
-		var bpp = pixels.bytesPerPixel;
+		var bpp = @:privateAccess pixels.bytesPerPixel;
 		var off = hxd.Pixels.getChannelOffset(pixels.format, c);
-		var srcBpp = src.bytesPerPixel;
+		var srcBpp = @:privateAccess src.bytesPerPixel;
 		var srcOff = hxd.Pixels.getChannelOffset(src.format, srcChannel);
 		for( y in 0...height ) {
 			var r = (y * src.width * srcBpp) + srcOff;

+ 1 - 0
hxd/PixelFormat.hx

@@ -19,4 +19,5 @@ enum PixelFormat {
 	SRGB_ALPHA;
 	RGB10A2;
 	RG11B10UF; // unsigned float
+	S3TC( v : Int );
 }

+ 30 - 16
hxd/Pixels.hx

@@ -62,11 +62,12 @@ abstract PixelsFloat(Pixels) to Pixels {
 class Pixels {
 	public var bytes : haxe.io.Bytes;
 	public var format(get,never) : PixelFormat;
-	public var width : Int;
-	public var height : Int;
+	public var width(default,null) : Int;
+	public var height(default,null) : Int;
+	public var stride(default,null) : Int;
 	public var offset : Int;
 	public var flags: haxe.EnumFlags<Flags>;
-	public var bytesPerPixel(default,null) : Int;
+	var bytesPerPixel : Int;
 	var innerFormat(default, set) : PixelFormat;
 
 	public function new(width : Int, height : Int, bytes : haxe.io.Bytes, format : hxd.PixelFormat, offset = 0) {
@@ -90,7 +91,8 @@ class Pixels {
 
 	function set_innerFormat(fmt) {
 		this.innerFormat = fmt;
-		bytesPerPixel = getBytesPerPixel(fmt);
+		stride = calcStride(width,fmt);
+		bytesPerPixel = calcStride(1,fmt);
 		return fmt;
 	}
 
@@ -101,8 +103,8 @@ class Pixels {
 	public function sub( x : Int, y : Int, width : Int, height : Int ) {
 		if( x < 0 || y < 0 || x + width > this.width || y + height > this.height )
 			throw "Pixels.sub() outside bounds";
-		var out = haxe.io.Bytes.alloc(width * height * bytesPerPixel);
-		var stride = width * bytesPerPixel;
+		var out = haxe.io.Bytes.alloc(height * stride);
+		var stride = calcStride(width, format);
 		var outP = 0;
 		for( dy in 0...height ) {
 			var p = (x + yflip(y + dy) * this.width) * bytesPerPixel + offset;
@@ -124,7 +126,9 @@ class Pixels {
 		willChange();
 		src.convert(format);
 		var bpp = bytesPerPixel;
-		var stride = width * bpp;
+		if( bpp == 0 )
+			throw "assert";
+		var stride = calcStride(width, format);
 		for( dy in 0...height ) {
 			var srcP = (srcX + src.yflip(dy + srcY) * src.width) * bpp + src.offset;
 			var dstP = (x + yflip(dy + y) * this.width) * bpp + offset;
@@ -238,8 +242,8 @@ class Pixels {
 
 	function copyInner() {
 		var old = bytes;
-		bytes = haxe.io.Bytes.alloc(width * height * bytesPerPixel);
-		bytes.blit(0, old, offset, width * height * bytesPerPixel);
+		bytes = haxe.io.Bytes.alloc(height * stride);
+		bytes.blit(0, old, offset, height * stride);
 		offset = 0;
 		flags.unset(ReadOnly);
 	}
@@ -253,7 +257,6 @@ class Pixels {
 		if( flags.has(FlipY) == b ) return;
 		willChange();
 		if( b ) flags.set(FlipY) else flags.unset(FlipY);
-		var stride = width * bytesPerPixel;
 		if( stride%4 != 0 ) invalidFormat();
 		for( y in 0...height >> 1 ) {
 			var p1 = y * stride + offset;
@@ -317,6 +320,9 @@ class Pixels {
 				bytes[p] = a;
 			}
 
+		case [S3TC(a),S3TC(b)] if( a == b ):
+			// nothing
+
 		default:
 			throw "Cannot convert from " + format + " to " + target;
 		}
@@ -378,15 +384,15 @@ class Pixels {
 		p.flags = flags;
 		p.flags.unset(ReadOnly);
 		if( bytes != null ) {
-			var size = width * height * bytesPerPixel;
+			var size = height * stride;
 			p.bytes = haxe.io.Bytes.alloc(size);
 			p.bytes.blit(0, bytes, offset, size);
 		}
 		return p;
 	}
 
-	public static function getBytesPerPixel( format : PixelFormat ) {
-		return switch( format ) {
+	public static function calcStride( width : Int, format : PixelFormat ) {
+		return width * switch( format ) {
 		case ARGB, BGRA, RGBA, SRGB, SRGB_ALPHA: 4;
 		case RGBA16F: 8;
 		case RGBA32F: 16;
@@ -401,9 +407,15 @@ class Pixels {
 		case RGB32F: 12;
 		case RGB10A2: 4;
 		case RG11B10UF: 4;
+		case S3TC(n):
+			if( n == 1 || n == 4 )
+				return width >> 1;
+			1;
 		}
 	}
 
+	static var S3TC_SIZES = [0,-1,1,1,-1,1,1,1];
+
 	/**
 		Returns the byte offset for the requested channel (0=R,1=G,2=B,3=A)
 		Returns -1 if the channel is not found
@@ -413,10 +425,10 @@ class Pixels {
 		case R8, R16F, R32F:
 			if( channel == R ) 0 else -1;
 		case RG8, RG16F, RG32F:
-			var p = getBytesPerPixel(format);
+			var p = calcStride(1,format);
 			[0, p, -1, -1][channel.toInt()];
 		case RGB8, RGB16F, RGB32F:
-			var p = getBytesPerPixel(format);
+			var p = calcStride(1,format);
 			[0, p, p<<1, -1][channel.toInt()];
 		case ARGB:
 			[1, 2, 3, 0][channel.toInt()];
@@ -430,11 +442,13 @@ class Pixels {
 			channel.toInt() * 4;
 		case RGB10A2, RG11B10UF:
 			throw "Bit packed format";
+		case S3TC(_):
+			throw "Not supported";
 		}
 	}
 
 	public static function alloc( width, height, format : PixelFormat ) {
-		return new Pixels(width, height, haxe.io.Bytes.alloc(width * height * getBytesPerPixel(format)), format);
+		return new Pixels(width, height, haxe.io.Bytes.alloc(height * calcStride(width, format)), format);
 	}
 
 }

+ 1 - 1
hxd/fmt/hsd/Serializer.hx

@@ -41,7 +41,7 @@ class Serializer extends hxbit.Serializer {
 			for( face in 0...(t.flags.has(Cube) ? 6 : 1) ) {
 				var pix = t.capturePixels(face);
 				pix.convert(fmt);
-				addBytesSub(pix.bytes, 0, t.width * t.height * pix.bytesPerPixel);
+				addBytesSub(pix.bytes, 0, t.height * pix.stride);
 			}
 			return true;
 		}

+ 51 - 5
hxd/res/Image.hx

@@ -6,6 +6,7 @@ package hxd.res;
 	var Png = 1;
 	var Gif = 2;
 	var Tga = 3;
+	var Dds = 4;
 
 	/*
 		Tells if we might not be able to directly decode the image without going through a loadBitmap async call.
@@ -41,7 +42,7 @@ class Image extends Resource {
 	static var ENABLE_AUTO_WATCH = true;
 
 	var tex : h3d.mat.Texture;
-	var inf : { width : Int, height : Int, format : ImageFormat };
+	var inf : { width : Int, height : Int, format : ImageFormat, bc : Int };
 
 	public function getFormat() {
 		getSize();
@@ -52,7 +53,7 @@ class Image extends Resource {
 		if( inf != null )
 			return inf;
 		var f = new hxd.fs.FileInput(entry);
-		var width = 0, height = 0, format;
+		var width = 0, height = 0, format, bc = 0;
 		var head = try f.readUInt16() catch( e : haxe.io.Eof ) 0;
 		switch( head ) {
 		case 0xD8FF: // JPG
@@ -89,6 +90,45 @@ class Image extends Resource {
 			width = f.readUInt16();
 			height = f.readUInt16();
 
+		case 0x4444: // DDS
+			format = Dds;
+			f.skip(10);
+			width = f.readInt32();
+			height = f.readInt32();
+			f.skip(16*4);
+			var fourCC = f.readInt32();
+			switch( fourCC & 0xFFFFFF ) {
+			case 0x545844: /* DXT */
+				var dxt = (fourCC >>> 24) - "0".code;
+				bc = switch( dxt ) {
+				case 1: 1;
+				case 2,3: 2;
+				case 4,5: 3;
+				default: 0;
+				}
+			case 0x495441: /* ATI */
+				var v = (fourCC >>> 24) - "0".code;
+				bc = switch( v ) {
+				case 1: 4;
+				case 2: 5;
+				default: 0;
+				}
+			case _ if( fourCC == 0x30315844 /* DX10 */ ):
+				f.skip(40);
+				var dxgi = f.readInt32(); // DXGI_FORMAT_xxxx value
+				switch( dxgi ) {
+				case 95: // BC6H_UF16
+					bc = 6;
+				case 98: // BC7_UNORM
+					bc = 7;
+				default:
+					throw entry.path+" has unsupported DXGI format "+dxgi;
+				}
+			}
+
+			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 _ if( entry.extension == "tga" ):
 			format = Tga;
 			f.skip(10);
@@ -99,7 +139,7 @@ class Image extends Resource {
 			throw "Unsupported texture format " + entry.path;
 		}
 		f.close();
-		inf = { width : width, height : height, format : format };
+		inf = { width : width, height : height, format : format, bc : bc };
 		return inf;
 	}
 
@@ -164,6 +204,9 @@ class Image extends Resource {
 			case TopLeft: // nothing
 			default: throw "Not supported "+r.header.imageOrigin;
 			}
+		case Dds:
+			var bytes = entry.getBytes();
+			pixels = new hxd.Pixels(inf.width, inf.height, bytes, S3TC(inf.bc), 128 + (inf.bc >= 6 ? 20 : 0));
 		}
 		if( fmt != null ) pixels.convert(fmt);
 		if( flipY != null ) pixels.setFlip(flipY);
@@ -230,7 +273,7 @@ class Image extends Resource {
 			function load() {
 				// immediately loading the PNG is faster than going through loadBitmap
 				tex.alloc();
-				var pixels = getPixels(h3d.mat.Texture.nativeFormat);
+				var pixels = getPixels(tex.format);
 				if( pixels.width != tex.width || pixels.height != tex.height )
 					pixels.makeSquare();
 				tex.uploadPixels(pixels);
@@ -285,7 +328,10 @@ class Image extends Resource {
 			width = tw;
 			height = th;
 		}
-		tex = new h3d.mat.Texture(width, height, [NoAlloc]);
+		var format = h3d.mat.Texture.nativeFormat;
+		if( inf.format == Dds )
+			format = S3TC(inf.bc);
+		tex = new h3d.mat.Texture(width, height, [NoAlloc], format);
 		if( DEFAULT_FILTER != Linear ) tex.filter = DEFAULT_FILTER;
 		tex.setName(entry.path);
 		loadTexture();