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 | |