diff src/libraries/ddsparse/ddsparse.cpp @ 1420:2a5ef265e040 image-CompressedData

Added support for DXT1/3/5 and BC5/7 compressed textures via love.image.CompressedData. Some internals are still iffy. - added a new CompressedData type to love.image - added love.image.isCompressed(file or data) - love.graphics.newImage automatically tests for compression when loading a file - added love.graphics.isSupported strings for DXT, BC5, and BC7 Compressed textures meant to be used on the GPU offer huge performance gains when - loading the texture from a file - loading the texture from RAM to VRAM - loading mipmaps - rendering
author Alex Szpakowski <slime73@gmail.com>
date Thu, 04 Apr 2013 19:09:18 -0300
parents
children aa603d1b03bc
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/libraries/ddsparse/ddsparse.cpp	Thu Apr 04 19:09:18 2013 -0300
@@ -0,0 +1,368 @@
+/**
+ * Simple DDS data parser for compressed 2D textures.
+ *
+ * Copyright (c) 2013 Alexander Szpakowski.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ **/
+
+#include "ddsparse.h"
+#include "ddsinfo.h"
+
+#include <algorithm>
+
+namespace dds
+{
+
+using namespace dds::dxinfo;
+
+// Creates a packed uint representation of a FourCC code.
+static inline uint32_t FourCC(char a, char b, char c, char d)
+{
+	uint32_t fcc = ((uint32_t) a)
+	| (((uint32_t) b) << 8)
+	| (((uint32_t) c) << 16)
+	| (((uint32_t) d) << 24);
+
+	return fcc;
+}
+
+Parser::Image::Image()
+	: width(0)
+	, height(0)
+	, dataSize(0)
+	, data(0)
+{
+}
+
+bool Parser::isDDS(const void *data, size_t dataSize)
+{
+	const uint8_t *readData = (const uint8_t *) data;
+	ptrdiff_t offset = 0;
+
+	// Is the data large enough to hold the DDS header?
+	if(dataSize < sizeof(uint32_t) + sizeof(DDSHeader))
+		return false;
+
+	// All DDS files start with "DDS ".
+	if((*(uint32_t *) readData) != FourCC('D','D','S',' '))
+		return false;
+
+	offset += sizeof(uint32_t);
+
+	DDSHeader *header = (DDSHeader *) &readData[offset];
+
+
+	// Verify header to validate DDS data.
+	if (header->size != sizeof(DDSHeader) || header->format.size != sizeof(DDSPixelFormat))
+		return false;
+
+	offset += sizeof(DDSHeader);
+
+	// Check for DX10 extension.
+	if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == FourCC('D','X','1','0')))
+	{
+		// Data must be big enough for both headers plus the magic value.
+		if (dataSize < (sizeof(uint32_t) + sizeof(DDSHeader) + sizeof(DDSHeader10)))
+			return false;
+
+	}
+
+	return true;
+}
+
+Parser::Parser(const void *data, size_t dataSize, Options opts)
+	: format(FORMAT_UNKNOWN)
+	, options(opts)
+{
+	parseData(data, dataSize);
+}
+
+Parser::Parser(const Parser &other)
+	: format(other.format)
+	, options(other.options)
+{
+	std::vector<Image>::const_iterator it;
+	for (it = other.texData.begin(); it != other.texData.end(); ++it)
+	{
+		Image img = *it;
+
+		if ((options & OPTIONS_COPY_DATA) && img.dataSize > 0)
+		{
+			uint8_t *data = 0;
+
+			try
+			{
+				data = new uint8_t[img.dataSize];
+			}
+			catch (std::exception &)
+			{
+				clearData();
+				throw;
+			}
+
+			img.data = data;
+		}
+
+		texData.push_back(img);
+	}
+}
+
+Parser::~Parser()
+{
+	// Delete any data we created.
+	clearData();
+}
+
+Format Parser::getFormat() const
+{
+	return format;
+}
+
+const Parser::Image *Parser::getImageData(size_t miplevel) const
+{
+	if (miplevel >= texData.size())
+		return 0;
+
+	return &texData[miplevel];
+}
+
+size_t Parser::getNumMipmaps() const
+{
+	return texData.size();
+}
+
+bool Parser::ownsData() const
+{
+	return (options & OPTIONS_COPY_DATA) != 0;
+}
+
+Format Parser::parseDDSFormat(const DDSPixelFormat &fmt) const
+{
+	if (fmt.flags & DDPF_FOURCC)
+	{
+		if (fmt.fourCC == FourCC('D','X','T','1'))
+			return FORMAT_DXT1;
+		else if (fmt.fourCC == FourCC('D','X','T','3'))
+			return FORMAT_DXT3;
+		else if (fmt.fourCC == FourCC('D','X','T','5'))
+			return FORMAT_DXT5;
+		else if (fmt.fourCC == FourCC('A','T','I','2'))
+			return FORMAT_BC5u;
+	}
+
+	return FORMAT_UNKNOWN;
+}
+
+Format Parser::parseDX10Format(DXGIFormat fmt) const
+{
+	Format f = FORMAT_UNKNOWN;
+
+	switch (fmt)
+	{
+	case DXGI_FORMAT_BC1_TYPELESS:
+	case DXGI_FORMAT_BC1_UNORM:
+	case DXGI_FORMAT_BC1_UNORM_SRGB:
+		f = FORMAT_DXT1;
+		break;
+	case DXGI_FORMAT_BC2_TYPELESS:
+	case DXGI_FORMAT_BC2_UNORM:
+	case DXGI_FORMAT_BC2_UNORM_SRGB:
+		f = FORMAT_DXT3;
+		break;
+	case DXGI_FORMAT_BC3_TYPELESS:
+	case DXGI_FORMAT_BC3_UNORM:
+	case DXGI_FORMAT_BC3_UNORM_SRGB:
+		f = FORMAT_DXT5;
+		break;
+	case DXGI_FORMAT_BC5_SNORM:
+		f = FORMAT_BC5s;
+		break;
+	case DXGI_FORMAT_BC5_TYPELESS:
+	case DXGI_FORMAT_BC5_UNORM:
+		f = FORMAT_BC5u;
+		break;
+	case DXGI_FORMAT_BC7_TYPELESS:
+	case DXGI_FORMAT_BC7_UNORM:
+		f = FORMAT_BC7;
+		break;
+	case DXGI_FORMAT_BC7_UNORM_SRGB:
+		f = FORMAT_BC7srgb;
+	default:
+		break;
+	}
+
+	return f;
+}
+
+size_t Parser::parseImageSize(Format fmt, int width, int height) const
+{
+	size_t size = 0;
+
+	switch (fmt)
+	{
+	case FORMAT_DXT1:
+	case FORMAT_DXT3:
+	case FORMAT_DXT5:
+	case FORMAT_BC5s:
+	case FORMAT_BC5u:
+	case FORMAT_BC7:
+	case FORMAT_BC7srgb:
+		{
+			int numBlocksWide = 0;
+			if (width > 0)
+				numBlocksWide = std::max(1, (width + 3) / 4);
+
+			int numBlocksHigh = 0;
+			if (height > 0)
+				numBlocksHigh = std::max(1, (height + 3) / 4);
+
+			int numBytesPerBlock = (fmt == FORMAT_DXT1 ? 8 : 16);
+
+			size = numBlocksWide * numBytesPerBlock * numBlocksHigh;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return size;
+}
+
+bool Parser::parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips)
+{
+	size_t offset = 0;
+
+	for (int i = 0; i < mips; i++)
+	{
+		Image img;
+		img.width = w;
+		img.height = h;
+
+		img.dataSize = parseImageSize(fmt, img.width, img.height);
+
+		// Make sure the data size is valid.
+		if (img.dataSize == 0 || (offset + img.dataSize) > dataSize)
+		{
+			// Clean up any data we allocated previously.
+			clearData();
+			return false;
+		}
+
+		// Create our own copy of the data, if requested.
+		if (options & OPTIONS_COPY_DATA)
+		{
+			uint8_t *newData = 0;
+
+			try
+			{
+				newData = new uint8_t[img.dataSize];
+			}
+			catch (std::exception &)
+			{
+				// Clean up before throwing.
+				clearData();
+				throw;
+			}
+
+			memcpy(newData, &data[offset], img.dataSize);
+			img.data = newData;
+		}
+		else
+			img.data = &data[offset];
+
+		texData.push_back(img);
+
+		// Move to the next mip level.
+		offset += img.dataSize;
+
+		w = std::max(w / 2, 1);
+		h = std::max(h / 2, 1);
+	}
+
+	return true;
+}
+
+bool Parser::parseData(const void *data, size_t dataSize)
+{
+	if (!isDDS(data, dataSize))
+		return false;
+
+	const uint8_t *readData = (const uint8_t *) data;
+	ptrdiff_t offset = sizeof(uint32_t);
+
+	DDSHeader *header = (DDSHeader *) &readData[offset];
+	offset += sizeof(DDSHeader);
+
+
+	// Check for DX10 extension.
+	if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == FourCC('D','X','1','0')))
+	{
+		DDSHeader10 *header10 = (DDSHeader10 *) &readData[offset];
+		offset += sizeof(DDSHeader10);
+
+		// We can't deal with 1D/3D textures.
+		switch (header10->resourceDimension)
+		{
+		case D3D10_RESOURCE_DIMENSION_TEXTURE2D:
+		case D3D10_RESOURCE_DIMENSION_UNKNOWN:
+			break;
+		default:
+			return false;
+		}
+
+		// We also can't deal with texture arrays and cubemaps.
+		if (header10->arraySize > 1)
+			return false;
+
+		format = parseDX10Format(header10->dxgiFormat);
+	}
+	else
+		format = parseDDSFormat(header->format);
+
+
+	if (format == FORMAT_UNKNOWN)
+		return false;
+
+
+	int w = header->width;
+	int h = header->height;
+
+	int mips = std::max((int) header->mipMapCount, 1);
+
+	return parseTexData(&readData[offset], dataSize - offset, format, w, h, mips);
+}
+
+void Parser::clearData()
+{
+	if (options & OPTIONS_COPY_DATA)
+	{
+		// Delete any data we created.
+		std::vector<Image>::iterator it;
+		for (it = texData.begin(); it != texData.end(); ++it)
+		{
+			delete[] it->data;
+			it->data = 0;
+		}
+	}
+
+	texData.clear();
+}
+
+} // dds