Skip to content

Instantly share code, notes, and snippets.

@aras-p
Created June 21, 2022 08:47
Embed
What would you like to do?
Unity DDS file exporter for compressed textures
// Adds context menu to TextureImporter objects, saves .dds next to input texture.
// 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_LINEARSIZE = 0x00080000;
public const uint DDPF_FOURCC = 0x00000004;
public const uint DDSCAPS_TEXTURE = 0x00001000;
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 dataSize, 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,
width = (uint)width,
height = (uint)height,
linearSize = (uint)dataSize,
mipmapcount = 1,
pf_size = 32,
pf_flags = DDSHeader.DDPF_FOURCC,
pf_fourcc = fourcc,
caps1 = DDSHeader.DDSCAPS_TEXTURE
};
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 data = tex.GetPixelData<byte>(0);
var header = DDSHeader.Create(width, height, data.Length, 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));
}
}
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