Skip to content

Instantly share code, notes, and snippets.

@aras-p
Last active March 24, 2024 08:59
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aras-p/0f0b02aa193346be18e5f130f2704782 to your computer and use it in GitHub Desktop.
Save aras-p/0f0b02aa193346be18e5f130f2704782 to your computer and use it in GitHub Desktop.
Unity DDS file exporter for compressed textures
// Adds context menu to TextureImporter objects, saves .dds next to input texture, including mipmaps.
// Tested with Unity 2021.3.4
using System;
using UnityEngine;
using UnityEditor;
using System.IO;
using Unity.Collections.LowLevel.Unsafe;
struct DDSHeader
{
public static uint FourCC(char a, char b, char c, char d)
{
return (uint)a | ((uint)b<<8) | ((uint)c<<16) | ((uint)d<<24);
}
public const uint DDSD_CAPS = 0x00000001;
public const uint DDSD_PIXELFORMAT = 0x00001000;
public const uint DDSD_WIDTH = 0x00000004;
public const uint DDSD_HEIGHT = 0x00000002;
public const uint DDSD_MIPMAPCOUNT = 0x00020000;
public const uint DDSD_LINEARSIZE = 0x00080000;
public const uint DDPF_FOURCC = 0x00000004;
public const uint DDSCAPS_TEXTURE = 0x00001000;
public const uint DDSCAPS_COMPLEX = 0x8;
public const uint DDSCAPS_MIPMAP = 0x400000;
public uint fourcc;
public uint size;
public uint flags;
public uint height;
public uint width;
public uint linearSize;
public uint depth;
public uint mipmapcount;
public uint unused0, unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10;
public uint pf_size;
public uint pf_flags;
public uint pf_fourcc;
public uint pf_bitcount;
public uint pf_rmask;
public uint pf_gmask;
public uint pf_bmask;
public uint pf_amask;
public uint caps1;
public uint caps2;
public uint caps3;
public uint caps4;
public uint unused11;
public static DDSHeader Create(int width, int height, int mip0DataSize, int mipCount, TextureFormat fmt)
{
uint fourcc = 0;
switch (fmt)
{
case TextureFormat.DXT1: fourcc = DDSHeader.FourCC('D', 'X', 'T', '1'); break;
case TextureFormat.DXT5: fourcc = DDSHeader.FourCC('D', 'X', 'T', '5'); break;
default: fourcc = DDSHeader.FourCC('D', 'X', '1', '0'); break;
}
var dds = new DDSHeader
{
fourcc = DDSHeader.FourCC('D', 'D', 'S', ' '),
size = 124,
flags = DDSHeader.DDSD_CAPS | DDSHeader.DDSD_PIXELFORMAT | DDSHeader.DDSD_WIDTH |
DDSHeader.DDSD_HEIGHT | DDSHeader.DDSD_LINEARSIZE | (mipCount > 1 ? DDSHeader.DDSD_MIPMAPCOUNT : 0),
width = (uint)width,
height = (uint)height,
linearSize = (uint)mip0DataSize,
mipmapcount = (uint)mipCount,
pf_size = 32,
pf_flags = DDSHeader.DDPF_FOURCC,
pf_fourcc = fourcc,
caps1 = DDSHeader.DDSCAPS_TEXTURE | (mipCount > 0 ? DDSHeader.DDSCAPS_MIPMAP | DDSCAPS_COMPLEX : 0)
};
return dds;
}
}
public class ExportDDS : MonoBehaviour
{
[MenuItem("CONTEXT/TextureImporter/Export DDS")]
static unsafe void ExportTextureDDS(MenuCommand command)
{
var imp = (TextureImporter)command.context;
if (imp == null) return;
var inPath = imp.assetPath;
if (!imp.isReadable)
{
imp.isReadable = true;
AssetDatabase.ImportAsset(inPath);
}
var outPath = Path.ChangeExtension(inPath, "dds");
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(inPath);
if (tex == null) return;
var width = tex.width;
var height = tex.height;
var fmt = tex.format;
var mipCount = tex.mipmapCount;
var data = tex.GetPixelData<byte>(0);
var header = DDSHeader.Create(width, height, data.Length, mipCount, fmt);
var headerPtr = UnsafeUtility.AddressOf(ref header);
var headerSize = UnsafeUtility.SizeOf<DDSHeader>();
if (headerSize != 128)
return;
uint dxgiFormat = fmt switch
{
TextureFormat.BC4 => 80,
TextureFormat.BC5 => 83,
TextureFormat.BC6H => 95,
TextureFormat.BC7 => 98,
_ => 0
};
using (var stream = File.Open(outPath, FileMode.Create))
{
using (var writer = new BinaryWriter(stream))
{
writer.Write(new Span<byte>(headerPtr, headerSize));
if (dxgiFormat != 0)
{
writer.Write(dxgiFormat);
writer.Write(3); // resource dimension: 2D
writer.Write(0); // misc flag
writer.Write(1); // array size
writer.Write(0); // misc flags 2
}
writer.Write(new Span<byte>(data.GetUnsafeReadOnlyPtr(), data.Length));
for (int mip = 1; mip < mipCount; ++mip)
{
var dataMip = tex.GetPixelData<byte>(mip);
writer.Write(new Span<byte>(dataMip.GetUnsafeReadOnlyPtr(), dataMip.Length));
}
}
}
AssetDatabase.ImportAsset(outPath);
Debug.Log($"Texture {inPath} exported to {outPath}, {width}x{height}, {fmt}");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment