summaryrefslogtreecommitdiff
path: root/Runtime/GfxDevice/TextureUploadUtils.h
blob: 85435528f5c1c9be6317910e61eb343d9cb1a331 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
#pragma once

#include "External/ProphecySDK/include/prcore/surface.hpp"
#include "Runtime/Graphics/Image.h"
#include "Runtime/Graphics/S3Decompression.h"
#include "Runtime/Graphics/FlashATFDecompression.h"
#include "Runtime/Shaders/GraphicsCaps.h"
#include "GfxDevice.h"
#include "VramLimits.h"

extern bool IsNPOTTextureAllowed(bool hasMipMap);

const int kDoubleLDRAlpha = 255 * 0.25f;


inline bool SkipLevelsForMasterTextureLimit (
											 int masterTextureLimit,
											 TextureFormat dataFormat, TextureFormat uploadFormat,
											 int mipCount,
											 bool uploadIsCompressed,
											 UInt8** srcData, int* width, int* height,
											 int* baseLevel, int* maxLevel, int* texWidth, int* texHeight, size_t* textureSize)
{
	bool wasScaledDown = false;
	
	// For compressed textures, stop applying masterTextureLimit if texture size drops below 4
	if( uploadIsCompressed )
	{
		while( masterTextureLimit > 0 && ((*width >> masterTextureLimit) < 4 || (*height >> masterTextureLimit) < 4) )
		{
			--masterTextureLimit;
		}
	}
	
	// skip several levels in data based on masterTextureLimit
	*maxLevel = mipCount - 1;
	*baseLevel = std::min( masterTextureLimit, *maxLevel );
	int level;
	for( level = 0; level < *baseLevel; ++level )
	{
		*srcData += CalculateImageSize (*width, *height, dataFormat);
		AssertIf( *width == 1 && *height == 1 && level != *maxLevel );
		*width = std::max( *width / 2, 1 );
		*height = std::max( *height / 2, 1 );
	}
	
	// Now estimate VRAM usage for the texture
	*textureSize = CalculateImageSize (*width, *height, uploadFormat);
	if (mipCount > 1)
		*textureSize += *textureSize / 3;
	int textureSizeKB = *textureSize / 1024;
	
	const int vramSizeKB = gGraphicsCaps.videoMemoryMB * 1024;
	const GfxDeviceStats::MemoryStats& memoryStats = GetRealGfxDevice().GetFrameStats().GetMemoryStats();
	const int currentVramUsageKB = (memoryStats.screenBytes + memoryStats.renderTextureBytes) / 1024;
	const int allowedVramUsageKB = std::max( (vramSizeKB - currentVramUsageKB) * kVRAMMaxFreePortionForTexture, 1.0f );
	
	// While texture is too large for hardware limits, or too large to sanely fit into VRAM, skip
	// mip levels. If it's non-mipmapped one, we have to reduce it and scale it into size that fits.
	// Don't do fitting into VRAM for Alpha8 textures (those are used for dynamic fonts, and
	// everything can break down if we scale them down).
	*texWidth = *width;
	*texHeight = *height;
	while (
		   *texWidth > gGraphicsCaps.maxTextureSize ||
		   *texHeight > gGraphicsCaps.maxTextureSize ||
		   (textureSizeKB > allowedVramUsageKB && dataFormat != kTexFormatAlpha8))
	{
		printf_console("Texture2D %ix%i won't fit, reducing size (needed mem=%i used mem=%i allowedmem=%i)\n", *texWidth, *texHeight, textureSizeKB, currentVramUsageKB, allowedVramUsageKB);
		if (*baseLevel < *maxLevel)
		{
			*srcData += CalculateImageSize (*texWidth, *texHeight, dataFormat);
			*width = std::max (*width / 2, 1);
			*height = std::max (*height / 2, 1);
			++*baseLevel;
		}
		*texWidth = std::max( *texWidth / 2, 1 );
		*texHeight = std::max( *texHeight / 2, 1 );
		textureSizeKB /= 4;
		*textureSize /= 4;
		wasScaledDown = true;
		if( *texWidth <= 4 && *texHeight <= 4 )
			break;
	}
	
	return wasScaledDown;
}


inline void HandleFormatDecompression (
									   TextureFormat format,
									   TextureUsageMode* usageMode,
									   TextureColorSpace colorSpace,
									   bool* uploadIsCompressed,
									   bool* decompressOnTheFly
									   )
{
	// Figure out whether we'll upload compressed or decompress on the fly
	
	///@TODO: This looks wrong. What the fuck???
	*uploadIsCompressed = IsCompressedDXTTextureFormat(format);
	
	switch (*usageMode)
	{
		case kTexUsageLightmapRGBM:
			// No special processing for RGBM if we support it
			if (*usageMode == kTexUsageLightmapRGBM && gGraphicsCaps.SupportsRGBM())
				*usageMode = kTexUsageNone;
			break;
		case kTexUsageLightmapDoubleLDR:
			// Never any special processing of DoubleLDR in players.
			//
			// In the editor, we'll add a dummy 0.25f alpha channel to the doubleLDR lightmap.
			// When we're in GLES20 emulation mode in the editor we're using pixel shaders compiled
			// for the platform the editor is currently running on and that forces RGBM decoding.
			// So we get (8.0 * 0.25) * color.rgb -- which is doubleLDR decoding.
#if !UNITY_EDITOR
			*usageMode = kTexUsageNone;
#endif
			break;
		case kTexUsageNormalmapPlain:
			// Never any special processing of plain normal maps in players.
			//
			// In the editor, we'll put .r into .a to make it work with shaders that expect DXT5nm
			// encoding.
#if !UNITY_EDITOR
			*usageMode = kTexUsageNone;
#endif
			break;
		default:
			*usageMode = kTexUsageNone;
	}
	
	////@TODO: BIG WTF??? We always decompress on the fly for pvrtc etc atc and wii formats????
	
	// Decompress on the fly when the device cannot handle
	// the compressed format or we have to do any special processing based on usage mode.
	*decompressOnTheFly = (*uploadIsCompressed && (!gGraphicsCaps.hasS3TCCompression || *usageMode != kTexUsageNone)) ||
		IsCompressedPVRTCTextureFormat(format) ||
		IsCompressedETCTextureFormat(format) ||
		IsCompressedATCTextureFormat(format) ||
		IsCompressedETC2TextureFormat(format) ||
		IsCompressedASTCTextureFormat(format) ||
		IsCompressedFlashATFTextureFormat(format);

# if !UNITY_XENON
	//If we are not on Xenon and the texture color space is xenon decompress on the fly
	if (*uploadIsCompressed && colorSpace == kTexColorSpaceSRGBXenon)
	{
		*decompressOnTheFly |= *uploadIsCompressed && colorSpace == kTexColorSpaceSRGBXenon;
	}
#endif
}


inline void InitImageBuffer (int width, int height, UInt8*& buffer, TextureFormat textureFormat)
{
	int imageSize = CalculateImageSize( width, height, textureFormat );
	if( buffer == NULL )
		buffer = new UInt8[imageSize];
}


inline void PerformUploadConversions (
									  int width, int height,
									  UInt8* dstBuffer, int dstPitch,
									  TextureUsageMode usageMode,
									  TextureColorSpace colorSpace,
									  const prcore::PixelFormat& pf
									  )
{
	if (usageMode == kTexUsageLightmapRGBM)
		DecodeRGBM (width, height, dstBuffer, dstPitch, pf);
#if UNITY_EDITOR
	if (usageMode == kTexUsageLightmapDoubleLDR)
		SetAlphaChannel (width, height, dstBuffer, dstPitch, pf, kDoubleLDRAlpha);
	if (usageMode == kTexUsageNormalmapPlain)
		SetAlphaToRedChannel (width, height, dstBuffer, dstPitch, pf);
	if (colorSpace == kTexColorSpaceSRGBXenon)
		XenonToNormalSRGBTexture(width, height, dstBuffer, dstPitch, pf);
#endif
}


inline void ConvertCompressedTextureUpload (
											int width, int height,
											TextureFormat format,
											const UInt8* srcData,
											UInt8*& decompressBuffer, int& tempBufferPitch,
											TextureUsageMode usageMode,
											TextureColorSpace colorSpace,
											int mipLevel)
{
	int dstWidth = std::max( width, 4 );
	int dstHeight = std::max( height, 4 );
	
	InitImageBuffer (dstWidth, dstHeight, decompressBuffer, kTexFormatRGBA32);
	tempBufferPitch = GetRowBytesFromWidthAndFormat(width, kTexFormatRGBA32);
	
	DecompressNativeTextureFormatWithMipLevel (format, width, height, mipLevel, (UInt32*)srcData, dstWidth, dstHeight, (UInt32*)decompressBuffer);
	
	PerformUploadConversions (width, height, decompressBuffer, tempBufferPitch, usageMode, colorSpace, GetProphecyPixelFormat(kTexFormatRGBA32));
}


inline bool ConvertUncompressedTextureUpload (
											  prcore::Surface& srcSurface,
											  prcore::Surface& dstSurface,
											  prcore::Surface::BlitMode blitMode,
											  TextureFormat uploadFormat,
											  TextureUsageMode usageMode,
											  TextureColorSpace colorSpace,
											  int width, int height, UInt8* inplaceData, int pitch, const prcore::PixelFormat& pf,
											  UInt8*& tempBuffer, int& tempBufferPitch
											  )
{
	if (usageMode != kTexUsageNone || colorSpace == kTexColorSpaceSRGBXenon)
	{
		if (uploadFormat == kTexFormatRGBA32 || uploadFormat == kTexFormatARGB32)
		{
			// Copy to locked rect
			dstSurface.BlitImage( srcSurface, blitMode );
			// Process in place
			PerformUploadConversions (width, height, inplaceData, pitch, usageMode, colorSpace, pf);
		}
		else
		{
			InitImageBuffer (width, height, tempBuffer, kTexFormatRGBA32);
			tempBufferPitch = GetRowBytesFromWidthAndFormat(width, kTexFormatRGBA32);
			
			// Copy to a temporary buffer so we can process the texture in place
			prcore::Surface tempSurface( width, height, tempBufferPitch, pf, tempBuffer );
			tempSurface.BlitImage( srcSurface, blitMode );
			
			// Process in place in the temp surface
			PerformUploadConversions (width, height, tempBuffer, tempBufferPitch, usageMode, colorSpace, pf);
			
			// Copy to locked rect
			dstSurface.BlitImage( tempSurface, blitMode );
		}
		return true;
	}
	return false;
}


inline void AdvanceToNextMipLevel (TextureFormat format, UInt8*& srcData, int& width, int& height, int& texWidth, int& texHeight)
{
	srcData += CalculateImageSize (width, height, format);
	width = std::max( width / 2, 1 );
	height = std::max( height / 2, 1 );
	texWidth = std::max( texWidth / 2, 1 );
	texHeight = std::max( texHeight / 2, 1 );
}