| 1 | // tTexture.h  |
| 2 | //  |
| 3 | // A tTexture is a 'hardware-ready' format. tTextures contain functionality for creating mipmap layers in a variety of  |
| 4 | // block-compressed and uncompressed formats. A tTexture stores each mipmap layer in a tLayer. A tTexture can be  |
| 5 | // created from either a tPicture or a dds file. The purpose of a dds file is so that content-creators have control  |
| 6 | // over the authoring of each mipmap level and the exact pixel format used. Basically if you've created a dds file,  |
| 7 | // you're saying you want the final hardware to use the image data unchanged and as authored -- same mip levels, same  |
| 8 | // pixel format, same dimensions. For this reason, dds files should not be loaded into tPictures where image  |
| 9 | // manipulation occurs and possibly lossy block-compressed dds images would be decompressed. A dds file may contain more  |
| 10 | // than one image if it is a cubemap, but a tTexture only ever represents a single image. The tTexture dds constructor  |
| 11 | // allows you to decide which one gets loaded. tTextures can save and load to a tChunk-based format, and are therefore  |
| 12 | // useful at both pipeline and for runtime loading. To save to a tChunk file format a tTexture will call the Save  |
| 13 | // method of all the tLayers.  |
| 14 | //  |
| 15 | // Copyright (c) 2006, 2016, 2017, 2019, 2020 Tristan Grimmer.  |
| 16 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 17 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 18 | //  |
| 19 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 21 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 22 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 23 | // PERFORMANCE OF THIS SOFTWARE.  |
| 24 |   |
| 25 | #pragma once  |
| 26 | #include <Foundation/tList.h>  |
| 27 | #include <Foundation/tString.h>  |
| 28 | #include <System/tChunk.h>  |
| 29 | #include "Image/tImageDDS.h"  |
| 30 | #include "Image/tPicture.h"  |
| 31 | #include "Image/tResample.h"  |
| 32 | namespace tImage  |
| 33 | {  |
| 34 |   |
| 35 |   |
| 36 | class tTexture : public tLink<tTexture>  |
| 37 | {  |
| 38 | public:  |
| 39 | // Creates an empty and initially invalid tTexture. You must manually call Set or Load.  |
| 40 | tTexture() { }  |
| 41 |   |
| 42 | // This constructor is for cases where you just have a list of layers that you want to give the tTexture. After  |
| 43 | // construction the layer list will be empty. This is used by the tCubemap class so it can grab all 6 sides of a  |
| 44 | // cubemap dds and give them to 6 different tTextures.  |
| 45 | tTexture(tList<tLayer>& layers) { Set(layers); }  |
| 46 |   |
| 47 | // Constructs from a dds file or dds object. Since dds files can have up to 6 images in them, these are explicitly  |
| 48 | // different constructors than the ones that load other image types. Additionally, the dds constructors don't mess  |
| 49 | // with compression or mip layers. What you put in the dds you get. correctRowOrder should normally be left set to  |
| 50 | // true unless you are loading a cubemap surface. Essentially dds files are upside down in terms of row order, but  |
| 51 | // only the cubemap loader know about it (and they use left handed side ordering).  |
| 52 | tTexture  |
| 53 | (  |
| 54 | const tString& ddsFile, tImageDDS::tSurfIndex surface = tImageDDS::tSurfIndex_Default,  |
| 55 | bool correctRowOrder = true  |
| 56 | ) { Load(ddsFile, surface, correctRowOrder); }  |
| 57 |   |
| 58 | // Similar to above but accepts an in-memory object. The ddsObject will be invalid after as the layers are stolen  |
| 59 | // from it. If necessary, a constructor can easily be added that does a copy and keeps it valid, but it will be  |
| 60 | // less efficient.  |
| 61 | tTexture(tImageDDS& ddsObject, tImageDDS::tSurfIndex surface = tImageDDS::tSurfIndex_Default) { Set(ddsObject, surface); }  |
| 62 |   |
| 63 | // The other constructors require you to know a quality setting for resampling (mipmap generation) and compression.  |
| 64 | // For simplicity there is only Fast and Production quality settings, and it affects resampling _and_ compression.  |
| 65 | enum class tQuality  |
| 66 | {  |
| 67 | Fast, // Bilinear resample filter. Fast BCn compress mode.  |
| 68 | Development, // Bicubic resample filter. High quality BCn compression.  |
| 69 | Production // Lanczos sinc-based resample filter. High quality BCn compression.  |
| 70 | };  |
| 71 |   |
| 72 | // This constructor creates a texture from an image file such as a jpg, gif, tga, or bmp. It does this by creating  |
| 73 | // a temporary tPicture object. If tPixelFormat is Auto, the constructor automatically chooses the most appropriate  |
| 74 | // format for the texture based on the image's properties. DXT1/BC1 is chosen if the image is perfectly opaque, and  |
| 75 | // DXT5/BC3 if it has alphas. This constructor forces power-of-2 texture dimensions and will resample to the nearest  |
| 76 | // power-of-2 if required. If forceWidth is > 0, the image will be resampled if necessary to have that width. The  |
| 77 | // same logic applies to forceHeight. Both forceWidth and forceHeight, if supplied, must be powers of 2.  |
| 78 | tTexture  |
| 79 | (  |
| 80 | const tString& imageFile, bool generateMipMaps, tPixelFormat pixelFormat = tPixelFormat::Auto,  |
| 81 | tQuality quality = tQuality::Production, int forceWidth = 0, int forceHeight = 0  |
| 82 | ) { Load(imageFile, generateMipMaps, pixelFormat, quality, forceWidth, forceHeight); }  |
| 83 |   |
| 84 | // Same as above except that an in-memory tPicture is used instead of a filename. The supplied tPicture will be  |
| 85 | // invalid after this constructor. This is because resampling may occur on the tPicture.  |
| 86 | tTexture  |
| 87 | (  |
| 88 | tPicture& imageObject, bool generateMipMaps, tPixelFormat pixelFormat = tPixelFormat::Auto,  |
| 89 | tQuality quality = tQuality::Production, int forceWidth = 0, int forceHeight = 0  |
| 90 | ) { Set(imageObject, generateMipMaps, pixelFormat, quality, forceWidth, forceHeight); }  |
| 91 |   |
| 92 | virtual ~tTexture() { Clear(); }  |
| 93 |   |
| 94 | // If any constructor fails, you will be left with an invalid object.  |
| 95 | bool IsValid() const { return (Layers.GetNumItems() > 0) ? true : false; }  |
| 96 |   |
| 97 | // See the corresponding constructors for a description of the behaviour of these functions. These functions all  |
| 98 | // return true on success. On failure, the tTexture is left invalid and in the case of the layer list Set, the list  |
| 99 | // is emptied.  |
| 100 | bool Set(tList<tLayer>&);  |
| 101 | bool Load  |
| 102 | (  |
| 103 | const tString& ddsFile, tImageDDS::tSurfIndex surface = tImageDDS::tSurfIndex_Default,  |
| 104 | bool correctRowOrder = true  |
| 105 | );  |
| 106 | bool Set(tImageDDS& ddsObject, tImageDDS::tSurfIndex = tImageDDS::tSurfIndex_Default);  |
| 107 | bool Load  |
| 108 | (  |
| 109 | const tString& imageFile, bool generateMipMaps, tPixelFormat = tPixelFormat::Auto,  |
| 110 | tQuality = tQuality::Production, int forceWidth = 0, int forceHeight = 0  |
| 111 | );  |
| 112 | bool Set  |
| 113 | (  |
| 114 | tPicture& imageObject, bool generateMipMaps, tPixelFormat = tPixelFormat::Auto,  |
| 115 | tQuality = tQuality::Production, int forceWidth = 0, int forceHeight = 0  |
| 116 | );  |
| 117 |   |
| 118 | void Clear() { Layers.Clear(); Opaque = true; }  |
| 119 |   |
| 120 | int GetWidth() const /* Returns width of the main layer. */ { return IsValid() ? Layers.First()->Width : 0; }  |
| 121 | int GetHeight() const /* Returns width of the main layer. */ { return IsValid() ? Layers.First()->Height : 0; }  |
| 122 | tPixelFormat GetPixelFormat() const { return IsValid() ? Layers.First()->PixelFormat : tPixelFormat::Invalid; }  |
| 123 | bool IsMipmapped() const { return (Layers.GetNumItems() > 1) ? true : false; }  |
| 124 | void RemoveMipmaps();  |
| 125 | bool IsOpaque() const { return Opaque; }  |
| 126 | int GetNumLayers() const { return Layers.GetNumItems(); }  |
| 127 | int GetNumMipmaps() const { return Layers.GetNumItems(); }  |
| 128 | tLayer* GetFirstLayer() const { return Layers.First(); }  |
| 129 | tLayer* GetMainLayer() const { return Layers.First(); }  |
| 130 | void StealLayers(tList<tLayer>&); // Leaves the object invalid.  |
| 131 | const tList<tLayer>& GetLayers() { return Layers; }  |
| 132 | int GetTotalPixelDataSize() const;  |
| 133 |   |
| 134 | // Save and Load to tChunk format.  |
| 135 | void Save(tChunkWriter&) const;  |
| 136 | void Load(const tChunk&);  |
| 137 |   |
| 138 | // Returns 1 + log2( max(width, height) ). The returned number is how many mipmaps it would take to make the  |
| 139 | // smallest a 1x1 square. Some pipelines may care about this and require all of them if mipmapping at all.  |
| 140 | int ComputeMaxNumberOfMipmaps() const;  |
| 141 |   |
| 142 | // Textures are considered equal if the pixel format, opacity, and layers are the same. Invalid textures are always  |
| 143 | // considered not equal to other textures, even other invalid textures.  |
| 144 | bool operator==(const tTexture&) const;  |
| 145 | bool operator!=(const tTexture& src) const { return !(*this == src); }  |
| 146 |   |
| 147 | private:  |
| 148 | tPixelFormat DeterminePixelFormat(const tPicture&);  |
| 149 | tResampleFilter DetermineFilter(tQuality);  |
| 150 | int DetermineBlockEncodeQualityLevel(tQuality);  |
| 151 |   |
| 152 | void ProcessImageTo_R8G8B8_Or_R8G8B8A8(tPicture&, tPixelFormat, bool generateMipmaps, tQuality);  |
| 153 | void ProcessImageTo_G3B5R5G3(tPicture&, bool generateMipmaps, tQuality);  |
| 154 | void ProcessImageTo_BCTC(tPicture&, tPixelFormat, bool generateMipmaps, tQuality);  |
| 155 |   |
| 156 | bool Opaque = true; // Only true if the texture is completely opaque.  |
| 157 |   |
| 158 | // The tTexture is only valid if there is at least one layer. The texture is considered to have mipmaps if the  |
| 159 | // number of layers is > 1.  |
| 160 | tList<tLayer> Layers;  |
| 161 |   |
| 162 | static bool BC7EncInitialized;  |
| 163 | };  |
| 164 |   |
| 165 |   |
| 166 | // Implementation below this line.  |
| 167 |   |
| 168 |   |
| 169 | inline int tTexture::GetTotalPixelDataSize() const  |
| 170 | {  |
| 171 | int total = 0;  |
| 172 | for (tLayer* layer = Layers.First(); layer; layer = layer->Next())  |
| 173 | total += layer->GetDataSize();  |
| 174 |   |
| 175 | return total;  |
| 176 | }  |
| 177 |   |
| 178 |   |
| 179 | inline tPixelFormat tTexture::DeterminePixelFormat(const tPicture& image)  |
| 180 | {  |
| 181 | if (Opaque)  |
| 182 | return tPixelFormat::BC1_DXT1;  |
| 183 | else  |
| 184 | return tPixelFormat::BC3_DXT5;  |
| 185 | }  |
| 186 |   |
| 187 |   |
| 188 | inline tResampleFilter tTexture::DetermineFilter(tQuality quality)  |
| 189 | {  |
| 190 | switch (quality)  |
| 191 | {  |
| 192 | case tQuality::Fast: return tResampleFilter::Bilinear;  |
| 193 | case tQuality::Development: return tResampleFilter::Bicubic;  |
| 194 | case tQuality::Production: return tResampleFilter::Lanczos;  |
| 195 | }  |
| 196 | return tResampleFilter::Bicubic;  |
| 197 | }  |
| 198 |   |
| 199 |   |
| 200 | inline int tTexture::DetermineBlockEncodeQualityLevel(tQuality quality)  |
| 201 | {  |
| 202 | switch (quality)  |
| 203 | {  |
| 204 | case tQuality::Fast: return 4;  |
| 205 | case tQuality::Development: return 10;  |
| 206 | case tQuality::Production: return 10;  |
| 207 | }  |
| 208 | return 4;  |
| 209 | }  |
| 210 |   |
| 211 |   |
| 212 | inline void tTexture::RemoveMipmaps()  |
| 213 | {  |
| 214 | if (!IsMipmapped())  |
| 215 | return;  |
| 216 |   |
| 217 | tLayer* main = Layers.Remove();  |
| 218 | Layers.Empty();  |
| 219 | Layers.Append(main);  |
| 220 | }  |
| 221 |   |
| 222 |   |
| 223 | inline void tTexture::StealLayers(tList<tLayer>& layers)  |
| 224 | {  |
| 225 | while (!Layers.IsEmpty())  |
| 226 | layers.Append(Layers.Remove());  |
| 227 |   |
| 228 | Clear();  |
| 229 | }  |
| 230 |   |
| 231 |   |
| 232 | }  |
| 233 | |