using System.ComponentModel;
using System.Runtime.InteropServices;
namespace CompressShaders
{
/// Lossless data compressor implemented by Cabinet.dll Windows component
///
/// That compression API was introduced in Windows 8.0, and is the only reason why the library won’t build for Windows 7 OS.
/// Whisper.dll consumes that component in runtime, to decompress these shader binaries
/// If you wonder why not gzip — because the OS doesn’t include an API for that, at least not an API usable from C or C++.
/// .NET standard library includes gzip algorithm, but we don't want Whisper.dll to depend on .NET.
///
static class Cabinet
{
/// Compression algorithm
///
enum eCompressionAlgorithm: uint
{
MSZIP = 2,
XPRESS = 3,
XPRESS_HUFF = 4,
LZMS = 5,
}
/// The value should match constexpr DWORD compressionAlgorithm constant,
in Whisper/D3D/shaders.cpp source file
const eCompressionAlgorithm algo = eCompressionAlgorithm.MSZIP;
[DllImport( "Cabinet.dll", SetLastError = true )]
static extern bool CreateCompressor( eCompressionAlgorithm Algorithm, IntPtr AllocationRoutines, out IntPtr CompressorHandle );
[DllImport( "Cabinet.dll", SetLastError = true )]
static extern bool CloseCompressor( IntPtr CompressorHandle );
[DllImport( "Cabinet.dll", SetLastError = true )]
static extern bool Compress( IntPtr CompressorHandle, [In] byte[] UncompressedData, IntPtr UncompressedDataSize, [Out] byte[] CompressedBuffer, IntPtr CompressedBufferSize, out IntPtr CompressedDataSize );
/// Compress an array of bytes into another, smaller array of bytes
/// In practice, the compression ratio is about 7.1 for the shader binaries in Release configuration.
public static byte[] compressBuffer( byte[] src )
{
if( src.Length <= 0 )
throw new ArgumentException( "The source buffer is empty" );
IntPtr hCompressor;
if( !CreateCompressor( algo, IntPtr.Zero, out hCompressor ) )
throw new Win32Exception( "Unable to create the compressor" );
try
{
byte[] dest = new byte[ src.Length * 2 ];
IntPtr srcSize = new IntPtr( src.Length );
IntPtr destSize = new IntPtr( src.Length * 2 );
if( !Compress( hCompressor, src, srcSize, dest, destSize, out destSize ) )
throw new Win32Exception( "Compress failed" );
Array.Resize( ref dest, (int)destSize );
return dest;
}
finally
{
CloseCompressor( hCompressor );
}
}
}
}