Mercurial > love-experiments
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1419:edd946442f86 | 1420:2a5ef265e040 |
|---|---|
| 1 /** | |
| 2 * Simple DDS data parser for compressed 2D textures. | |
| 3 * | |
| 4 * Copyright (c) 2013 Alexander Szpakowski. | |
| 5 * | |
| 6 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 7 * of this software and associated documentation files (the "Software"), to deal | |
| 8 * in the Software without restriction, including without limitation the rights | |
| 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 10 * copies of the Software, and to permit persons to whom the Software is | |
| 11 * furnished to do so, subject to the following conditions: | |
| 12 * | |
| 13 * The above copyright notice and this permission notice shall be included in | |
| 14 * all copies or substantial portions of the Software. | |
| 15 * | |
| 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| 22 * THE SOFTWARE. | |
| 23 **/ | |
| 24 | |
| 25 #include "ddsparse.h" | |
| 26 #include "ddsinfo.h" | |
| 27 | |
| 28 #include <algorithm> | |
| 29 | |
| 30 namespace dds | |
| 31 { | |
| 32 | |
| 33 using namespace dds::dxinfo; | |
| 34 | |
| 35 // Creates a packed uint representation of a FourCC code. | |
| 36 static inline uint32_t FourCC(char a, char b, char c, char d) | |
| 37 { | |
| 38 uint32_t fcc = ((uint32_t) a) | |
| 39 | (((uint32_t) b) << 8) | |
| 40 | (((uint32_t) c) << 16) | |
| 41 | (((uint32_t) d) << 24); | |
| 42 | |
| 43 return fcc; | |
| 44 } | |
| 45 | |
| 46 Parser::Image::Image() | |
| 47 : width(0) | |
| 48 , height(0) | |
| 49 , dataSize(0) | |
| 50 , data(0) | |
| 51 { | |
| 52 } | |
| 53 | |
| 54 bool Parser::isDDS(const void *data, size_t dataSize) | |
| 55 { | |
| 56 const uint8_t *readData = (const uint8_t *) data; | |
| 57 ptrdiff_t offset = 0; | |
| 58 | |
| 59 // Is the data large enough to hold the DDS header? | |
| 60 if(dataSize < sizeof(uint32_t) + sizeof(DDSHeader)) | |
| 61 return false; | |
| 62 | |
| 63 // All DDS files start with "DDS ". | |
| 64 if((*(uint32_t *) readData) != FourCC('D','D','S',' ')) | |
| 65 return false; | |
| 66 | |
| 67 offset += sizeof(uint32_t); | |
| 68 | |
| 69 DDSHeader *header = (DDSHeader *) &readData[offset]; | |
| 70 | |
| 71 | |
| 72 // Verify header to validate DDS data. | |
| 73 if (header->size != sizeof(DDSHeader) || header->format.size != sizeof(DDSPixelFormat)) | |
| 74 return false; | |
| 75 | |
| 76 offset += sizeof(DDSHeader); | |
| 77 | |
| 78 // Check for DX10 extension. | |
| 79 if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == FourCC('D','X','1','0'))) | |
| 80 { | |
| 81 // Data must be big enough for both headers plus the magic value. | |
| 82 if (dataSize < (sizeof(uint32_t) + sizeof(DDSHeader) + sizeof(DDSHeader10))) | |
| 83 return false; | |
| 84 | |
| 85 } | |
| 86 | |
| 87 return true; | |
| 88 } | |
| 89 | |
| 90 Parser::Parser(const void *data, size_t dataSize, Options opts) | |
| 91 : format(FORMAT_UNKNOWN) | |
| 92 , options(opts) | |
| 93 { | |
| 94 parseData(data, dataSize); | |
| 95 } | |
| 96 | |
| 97 Parser::Parser(const Parser &other) | |
| 98 : format(other.format) | |
| 99 , options(other.options) | |
| 100 { | |
| 101 std::vector<Image>::const_iterator it; | |
| 102 for (it = other.texData.begin(); it != other.texData.end(); ++it) | |
| 103 { | |
| 104 Image img = *it; | |
| 105 | |
| 106 if ((options & OPTIONS_COPY_DATA) && img.dataSize > 0) | |
| 107 { | |
| 108 uint8_t *data = 0; | |
| 109 | |
| 110 try | |
| 111 { | |
| 112 data = new uint8_t[img.dataSize]; | |
| 113 } | |
| 114 catch (std::exception &) | |
| 115 { | |
| 116 clearData(); | |
| 117 throw; | |
| 118 } | |
| 119 | |
| 120 img.data = data; | |
| 121 } | |
| 122 | |
| 123 texData.push_back(img); | |
| 124 } | |
| 125 } | |
| 126 | |
| 127 Parser::~Parser() | |
| 128 { | |
| 129 // Delete any data we created. | |
| 130 clearData(); | |
| 131 } | |
| 132 | |
| 133 Format Parser::getFormat() const | |
| 134 { | |
| 135 return format; | |
| 136 } | |
| 137 | |
| 138 const Parser::Image *Parser::getImageData(size_t miplevel) const | |
| 139 { | |
| 140 if (miplevel >= texData.size()) | |
| 141 return 0; | |
| 142 | |
| 143 return &texData[miplevel]; | |
| 144 } | |
| 145 | |
| 146 size_t Parser::getNumMipmaps() const | |
| 147 { | |
| 148 return texData.size(); | |
| 149 } | |
| 150 | |
| 151 bool Parser::ownsData() const | |
| 152 { | |
| 153 return (options & OPTIONS_COPY_DATA) != 0; | |
| 154 } | |
| 155 | |
| 156 Format Parser::parseDDSFormat(const DDSPixelFormat &fmt) const | |
| 157 { | |
| 158 if (fmt.flags & DDPF_FOURCC) | |
| 159 { | |
| 160 if (fmt.fourCC == FourCC('D','X','T','1')) | |
| 161 return FORMAT_DXT1; | |
| 162 else if (fmt.fourCC == FourCC('D','X','T','3')) | |
| 163 return FORMAT_DXT3; | |
| 164 else if (fmt.fourCC == FourCC('D','X','T','5')) | |
| 165 return FORMAT_DXT5; | |
| 166 else if (fmt.fourCC == FourCC('A','T','I','2')) | |
| 167 return FORMAT_BC5u; | |
| 168 } | |
| 169 | |
| 170 return FORMAT_UNKNOWN; | |
| 171 } | |
| 172 | |
| 173 Format Parser::parseDX10Format(DXGIFormat fmt) const | |
| 174 { | |
| 175 Format f = FORMAT_UNKNOWN; | |
| 176 | |
| 177 switch (fmt) | |
| 178 { | |
| 179 case DXGI_FORMAT_BC1_TYPELESS: | |
| 180 case DXGI_FORMAT_BC1_UNORM: | |
| 181 case DXGI_FORMAT_BC1_UNORM_SRGB: | |
| 182 f = FORMAT_DXT1; | |
| 183 break; | |
| 184 case DXGI_FORMAT_BC2_TYPELESS: | |
| 185 case DXGI_FORMAT_BC2_UNORM: | |
| 186 case DXGI_FORMAT_BC2_UNORM_SRGB: | |
| 187 f = FORMAT_DXT3; | |
| 188 break; | |
| 189 case DXGI_FORMAT_BC3_TYPELESS: | |
| 190 case DXGI_FORMAT_BC3_UNORM: | |
| 191 case DXGI_FORMAT_BC3_UNORM_SRGB: | |
| 192 f = FORMAT_DXT5; | |
| 193 break; | |
| 194 case DXGI_FORMAT_BC5_SNORM: | |
| 195 f = FORMAT_BC5s; | |
| 196 break; | |
| 197 case DXGI_FORMAT_BC5_TYPELESS: | |
| 198 case DXGI_FORMAT_BC5_UNORM: | |
| 199 f = FORMAT_BC5u; | |
| 200 break; | |
| 201 case DXGI_FORMAT_BC7_TYPELESS: | |
| 202 case DXGI_FORMAT_BC7_UNORM: | |
| 203 f = FORMAT_BC7; | |
| 204 break; | |
| 205 case DXGI_FORMAT_BC7_UNORM_SRGB: | |
| 206 f = FORMAT_BC7srgb; | |
| 207 default: | |
| 208 break; | |
| 209 } | |
| 210 | |
| 211 return f; | |
| 212 } | |
| 213 | |
| 214 size_t Parser::parseImageSize(Format fmt, int width, int height) const | |
| 215 { | |
| 216 size_t size = 0; | |
| 217 | |
| 218 switch (fmt) | |
| 219 { | |
| 220 case FORMAT_DXT1: | |
| 221 case FORMAT_DXT3: | |
| 222 case FORMAT_DXT5: | |
| 223 case FORMAT_BC5s: | |
| 224 case FORMAT_BC5u: | |
| 225 case FORMAT_BC7: | |
| 226 case FORMAT_BC7srgb: | |
| 227 { | |
| 228 int numBlocksWide = 0; | |
| 229 if (width > 0) | |
| 230 numBlocksWide = std::max(1, (width + 3) / 4); | |
| 231 | |
| 232 int numBlocksHigh = 0; | |
| 233 if (height > 0) | |
| 234 numBlocksHigh = std::max(1, (height + 3) / 4); | |
| 235 | |
| 236 int numBytesPerBlock = (fmt == FORMAT_DXT1 ? 8 : 16); | |
| 237 | |
| 238 size = numBlocksWide * numBytesPerBlock * numBlocksHigh; | |
| 239 } | |
| 240 break; | |
| 241 default: | |
| 242 break; | |
| 243 } | |
| 244 | |
| 245 return size; | |
| 246 } | |
| 247 | |
| 248 bool Parser::parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips) | |
| 249 { | |
| 250 size_t offset = 0; | |
| 251 | |
| 252 for (int i = 0; i < mips; i++) | |
| 253 { | |
| 254 Image img; | |
| 255 img.width = w; | |
| 256 img.height = h; | |
| 257 | |
| 258 img.dataSize = parseImageSize(fmt, img.width, img.height); | |
| 259 | |
| 260 // Make sure the data size is valid. | |
| 261 if (img.dataSize == 0 || (offset + img.dataSize) > dataSize) | |
| 262 { | |
| 263 // Clean up any data we allocated previously. | |
| 264 clearData(); | |
| 265 return false; | |
| 266 } | |
| 267 | |
| 268 // Create our own copy of the data, if requested. | |
| 269 if (options & OPTIONS_COPY_DATA) | |
| 270 { | |
| 271 uint8_t *newData = 0; | |
| 272 | |
| 273 try | |
| 274 { | |
| 275 newData = new uint8_t[img.dataSize]; | |
| 276 } | |
| 277 catch (std::exception &) | |
| 278 { | |
| 279 // Clean up before throwing. | |
| 280 clearData(); | |
| 281 throw; | |
| 282 } | |
| 283 | |
| 284 memcpy(newData, &data[offset], img.dataSize); | |
| 285 img.data = newData; | |
| 286 } | |
| 287 else | |
| 288 img.data = &data[offset]; | |
| 289 | |
| 290 texData.push_back(img); | |
| 291 | |
| 292 // Move to the next mip level. | |
| 293 offset += img.dataSize; | |
| 294 | |
| 295 w = std::max(w / 2, 1); | |
| 296 h = std::max(h / 2, 1); | |
| 297 } | |
| 298 | |
| 299 return true; | |
| 300 } | |
| 301 | |
| 302 bool Parser::parseData(const void *data, size_t dataSize) | |
| 303 { | |
| 304 if (!isDDS(data, dataSize)) | |
| 305 return false; | |
| 306 | |
| 307 const uint8_t *readData = (const uint8_t *) data; | |
| 308 ptrdiff_t offset = sizeof(uint32_t); | |
| 309 | |
| 310 DDSHeader *header = (DDSHeader *) &readData[offset]; | |
| 311 offset += sizeof(DDSHeader); | |
| 312 | |
| 313 | |
| 314 // Check for DX10 extension. | |
| 315 if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == FourCC('D','X','1','0'))) | |
| 316 { | |
| 317 DDSHeader10 *header10 = (DDSHeader10 *) &readData[offset]; | |
| 318 offset += sizeof(DDSHeader10); | |
| 319 | |
| 320 // We can't deal with 1D/3D textures. | |
| 321 switch (header10->resourceDimension) | |
| 322 { | |
| 323 case D3D10_RESOURCE_DIMENSION_TEXTURE2D: | |
| 324 case D3D10_RESOURCE_DIMENSION_UNKNOWN: | |
| 325 break; | |
| 326 default: | |
| 327 return false; | |
| 328 } | |
| 329 | |
| 330 // We also can't deal with texture arrays and cubemaps. | |
| 331 if (header10->arraySize > 1) | |
| 332 return false; | |
| 333 | |
| 334 format = parseDX10Format(header10->dxgiFormat); | |
| 335 } | |
| 336 else | |
| 337 format = parseDDSFormat(header->format); | |
| 338 | |
| 339 | |
| 340 if (format == FORMAT_UNKNOWN) | |
| 341 return false; | |
| 342 | |
| 343 | |
| 344 int w = header->width; | |
| 345 int h = header->height; | |
| 346 | |
| 347 int mips = std::max((int) header->mipMapCount, 1); | |
| 348 | |
| 349 return parseTexData(&readData[offset], dataSize - offset, format, w, h, mips); | |
| 350 } | |
| 351 | |
| 352 void Parser::clearData() | |
| 353 { | |
| 354 if (options & OPTIONS_COPY_DATA) | |
| 355 { | |
| 356 // Delete any data we created. | |
| 357 std::vector<Image>::iterator it; | |
| 358 for (it = texData.begin(); it != texData.end(); ++it) | |
| 359 { | |
| 360 delete[] it->data; | |
| 361 it->data = 0; | |
| 362 } | |
| 363 } | |
| 364 | |
| 365 texData.clear(); | |
| 366 } | |
| 367 | |
| 368 } // dds |
