A game about forced loneliness, made by TACStudios
at master 339 lines 18 kB view raw
1using System.Collections.Generic; 2using UnityEngine.Experimental.Rendering; 3 4namespace UnityEngine.Rendering 5{ 6 /// <summary> 7 /// Texture atlas with rectangular power of two size. 8 /// </summary> 9 public class PowerOfTwoTextureAtlas : Texture2DAtlas 10 { 11 readonly int m_MipPadding; 12 const float k_MipmapFactorApprox = 1.33f; 13 14 private Dictionary<int, Vector2Int> m_RequestedTextures = new Dictionary<int, Vector2Int>(); 15 16 /// <summary> 17 /// Create a new texture atlas, must have power of two size. 18 /// </summary> 19 /// <param name="size">The size of the atlas in pixels. Must be power of two.</param> 20 /// <param name="mipPadding">Amount of mip padding in power of two.</param> 21 /// <param name="format">Atlas texture format</param> 22 /// <param name="filterMode">Atlas texture filter mode.</param> 23 /// <param name="name">Name of the atlas</param> 24 /// <param name="useMipMap">Use mip maps</param> 25 public PowerOfTwoTextureAtlas(int size, int mipPadding, GraphicsFormat format, FilterMode filterMode = FilterMode.Point, string name = "", bool useMipMap = true) 26 : base(size, size, format, filterMode, true, name, useMipMap) 27 { 28 this.m_MipPadding = mipPadding; 29 30 // Check if size is a power of two 31 if ((size & (size - 1)) != 0) 32 Debug.Assert(false, "Power of two atlas was constructed with non power of two size: " + size); 33 } 34 35 /// <summary> 36 /// Used mipmap padding size in power of two. 37 /// </summary> 38 public int mipPadding => m_MipPadding; 39 40 int GetTexturePadding() => (int)Mathf.Pow(2, m_MipPadding) * 2; 41 42 /// <summary> 43 /// Get location of the actual texture data without padding in the atlas. 44 /// </summary> 45 /// <param name="texture">The source texture cached in the atlas.</param> 46 /// <param name="scaleOffset">Cached atlas location (scale and offset) for the source texture.</param> 47 /// <returns>Scale and offset for the source texture without padding.</returns> 48 public Vector4 GetPayloadScaleOffset(Texture texture, in Vector4 scaleOffset) 49 { 50 int pixelPadding = GetTexturePadding(); 51 Vector2 paddingSize = Vector2.one * pixelPadding; 52 Vector2 textureSize = GetPowerOfTwoTextureSize(texture); 53 return GetPayloadScaleOffset(textureSize, paddingSize, scaleOffset); 54 } 55 56 /// <summary> 57 /// Get location of the actual texture data without padding in the atlas. 58 /// </summary> 59 /// <param name="textureSize">Size of the source texture</param> 60 /// <param name="paddingSize">Padding size used for the source texture. </param> 61 /// <param name="scaleOffset">Cached atlas location (scale and offset) for the source texture.</param> 62 /// <returns>Scale and offset for the source texture without padding.</returns> 63 static public Vector4 GetPayloadScaleOffset(in Vector2 textureSize, in Vector2 paddingSize, in Vector4 scaleOffset) 64 { 65 // Scale, Offset is a padded atlas sub-texture rectangle. 66 // Actual texture data (payload) is inset, i.e. padded inwards. 67 Vector2 subTexScale = new Vector2(scaleOffset.x, scaleOffset.y); 68 Vector2 subTexOffset = new Vector2(scaleOffset.z, scaleOffset.w); 69 70 // NOTE: Should match Blit() padding calculations. 71 Vector2 scalePadding = ((textureSize + paddingSize) / textureSize); // Size of padding (sampling) rectangle relative to the payload texture. 72 Vector2 offsetPadding = (paddingSize / 2.0f) / (textureSize + paddingSize); // Padding offset in the padding rectangle 73 74 Vector2 insetScale = subTexScale / scalePadding; // Size of payload rectangle in sub-tex 75 Vector2 insetOffset = subTexOffset + subTexScale * offsetPadding; // Offset of payload rectangle in sub-tex 76 77 return new Vector4(insetScale.x, insetScale.y, insetOffset.x, insetOffset.y); 78 } 79 80 private enum BlitType 81 { 82 Padding, 83 PaddingMultiply, 84 OctahedralPadding, 85 OctahedralPaddingMultiply, 86 } 87 88 private void Blit2DTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips, BlitType blitType) 89 { 90 int mipCount = GetTextureMipmapCount(texture.width, texture.height); 91 int pixelPadding = GetTexturePadding(); 92 Vector2 textureSize = GetPowerOfTwoTextureSize(texture); 93 bool bilinear = texture.filterMode != FilterMode.Point; 94 95 if (!blitMips) 96 mipCount = 1; 97 98 using (new ProfilingScope(cmd, ProfilingSampler.Get(CoreProfileId.BlitTextureInPotAtlas))) 99 { 100 for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) 101 { 102 cmd.SetRenderTarget(m_AtlasTexture, mipLevel); 103 switch (blitType) 104 { 105 case BlitType.Padding: Blitter.BlitQuadWithPadding(cmd, texture, textureSize, sourceScaleOffset, scaleOffset, mipLevel, bilinear, pixelPadding); break; 106 case BlitType.PaddingMultiply: Blitter.BlitQuadWithPaddingMultiply(cmd, texture, textureSize, sourceScaleOffset, scaleOffset, mipLevel, bilinear, pixelPadding); break; 107 case BlitType.OctahedralPadding: Blitter.BlitOctahedralWithPadding(cmd, texture, textureSize, sourceScaleOffset, scaleOffset, mipLevel, bilinear, pixelPadding); break; 108 case BlitType.OctahedralPaddingMultiply: Blitter.BlitOctahedralWithPaddingMultiply(cmd, texture, textureSize, sourceScaleOffset, scaleOffset, mipLevel, bilinear, pixelPadding); break; 109 } 110 } 111 } 112 } 113 114 /// <summary> 115 /// Blit texture into the atlas with padding. 116 /// </summary> 117 /// <param name="cmd">Target command buffer for graphics commands.</param> 118 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 119 /// <param name="texture">Source Texture</param> 120 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 121 /// <param name="blitMips">Blit mip maps.</param> 122 /// <param name="overrideInstanceID">Override texture instance ID.</param> 123 public override void BlitTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 124 { 125 // We handle ourself the 2D blit because cookies needs mipPadding for trilinear filtering 126 if (Is2D(texture)) 127 { 128 Blit2DTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, BlitType.Padding); 129 MarkGPUTextureValid(overrideInstanceID != -1 ? overrideInstanceID : texture.GetInstanceID(), blitMips); 130 } 131 } 132 133 /// <summary> 134 /// Blit texture into the atlas with padding and blending. 135 /// </summary> 136 /// <param name="cmd">Target command buffer for graphics commands.</param> 137 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 138 /// <param name="texture">Source Texture</param> 139 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 140 /// <param name="blitMips">Blit mip maps.</param> 141 /// <param name="overrideInstanceID">Override texture instance ID.</param> 142 public void BlitTextureMultiply(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 143 { 144 // We handle ourself the 2D blit because cookies needs mipPadding for trilinear filtering 145 if (Is2D(texture)) 146 { 147 Blit2DTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, BlitType.PaddingMultiply); 148 MarkGPUTextureValid(overrideInstanceID != -1 ? overrideInstanceID : texture.GetInstanceID(), blitMips); 149 } 150 } 151 152 /// <summary> 153 /// Blit octahedral texture into the atlas with padding. 154 /// </summary> 155 /// <param name="cmd">Target command buffer for graphics commands.</param> 156 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 157 /// <param name="texture">Source Texture</param> 158 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 159 /// <param name="blitMips">Blit mip maps.</param> 160 /// <param name="overrideInstanceID">Override texture instance ID.</param> 161 public override void BlitOctahedralTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 162 { 163 // We handle ourself the 2D blit because cookies needs mipPadding for trilinear filtering 164 if (Is2D(texture)) 165 { 166 Blit2DTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, BlitType.OctahedralPadding); 167 MarkGPUTextureValid(overrideInstanceID != -1 ? overrideInstanceID : texture.GetInstanceID(), blitMips); 168 } 169 } 170 171 /// <summary> 172 /// Blit octahedral texture into the atlas with padding. 173 /// </summary> 174 /// <param name="cmd">Target command buffer for graphics commands.</param> 175 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 176 /// <param name="texture">Source Texture</param> 177 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 178 /// <param name="blitMips">Blit mip maps.</param> 179 /// <param name="overrideInstanceID">Override texture instance ID.</param> 180 public void BlitOctahedralTextureMultiply(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 181 { 182 // We handle ourself the 2D blit because cookies needs mipPadding for trilinear filtering 183 if (Is2D(texture)) 184 { 185 Blit2DTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, BlitType.OctahedralPaddingMultiply); 186 MarkGPUTextureValid(overrideInstanceID != -1 ? overrideInstanceID : texture.GetInstanceID(), blitMips); 187 } 188 } 189 190 void TextureSizeToPowerOfTwo(Texture texture, ref int width, ref int height) 191 { 192 // Change the width and height of the texture to be power of two 193 width = Mathf.NextPowerOfTwo(width); 194 height = Mathf.NextPowerOfTwo(height); 195 } 196 197 Vector2 GetPowerOfTwoTextureSize(Texture texture) 198 { 199 int width = texture.width, height = texture.height; 200 201 TextureSizeToPowerOfTwo(texture, ref width, ref height); 202 return new Vector2(width, height); 203 } 204 205 // Override the behavior when we add a texture so all non-pot textures are blitted to a pot target zone 206 /// <summary> 207 /// Allocate space from the atlas for a texture and copy texture contents into the atlas. 208 /// </summary> 209 /// <param name="cmd">Target command buffer for graphics commands.</param> 210 /// <param name="scaleOffset">Allocated scale (.xy) and offset (.zw)</param> 211 /// <param name="texture">Source Texture</param> 212 /// <param name="width">Request width in pixels.</param> 213 /// <param name="height">Request height in pixels.</param> 214 /// <param name="overrideInstanceID">Override texture instance ID.</param> 215 /// <returns>True on success, false otherwise.</returns> 216 public override bool AllocateTexture(CommandBuffer cmd, ref Vector4 scaleOffset, Texture texture, int width, int height, int overrideInstanceID = -1) 217 { 218 // This atlas only supports square textures 219 if (height != width) 220 { 221 Debug.LogError("Can't place " + texture + " in the atlas " + m_AtlasTexture.name + ": Only squared texture are allowed in this atlas."); 222 return false; 223 } 224 225 TextureSizeToPowerOfTwo(texture, ref height, ref width); 226 227 return base.AllocateTexture(cmd, ref scaleOffset, texture, width, height); 228 } 229 230 /// <summary> 231 /// Clear tracked requested textures. 232 /// </summary> 233 public void ResetRequestedTexture() => m_RequestedTextures.Clear(); 234 235 /// <summary> 236 /// Reserves the space on the texture atlas 237 /// </summary> 238 /// <param name="texture">The source texture</param> 239 /// <returns>True if the space is reserved</returns> 240 public bool ReserveSpace(Texture texture) => ReserveSpace(texture, texture.width, texture.height); 241 242 /// <summary> 243 /// Reserves the space on the texture atlas 244 /// </summary> 245 /// <param name="texture">The source texture</param> 246 /// <param name="width">The width</param> 247 /// <param name="height">The height</param> 248 /// <returns>True if the space is reserved</returns> 249 public bool ReserveSpace(Texture texture, int width, int height) 250 => ReserveSpace(GetTextureID(texture), width, height); 251 252 /// <summary> 253 /// Reserves the space on the texture atlas 254 /// </summary> 255 /// <param name="textureA">The source texture A</param> 256 /// <param name="textureB">The source texture B</param> 257 /// <param name="width">The width</param> 258 /// <param name="height">The height</param> 259 /// <returns>True if the space is reserved</returns> 260 public bool ReserveSpace(Texture textureA, Texture textureB, int width, int height) 261 => ReserveSpace(GetTextureID(textureA, textureB), width, height); 262 263 /// <summary> 264 /// Reserves the space on the texture atlas 265 /// </summary> 266 /// <param name="id">The id</param> 267 /// <param name="width">The width</param> 268 /// <param name="height">The height</param> 269 /// <returns>True if the space is reserved</returns> 270 public bool ReserveSpace(int id, int width, int height) 271 { 272 m_RequestedTextures[id] = new Vector2Int(width, height); 273 274 // Cookie texture resolution changing between frame is a special case, so we handle it here. 275 // The texture will be re-allocated and may cause holes in the atlas texture, which is fine 276 // because when it doesn't have any more space, it will re-layout the texture correctly. 277 var cachedSize = GetCachedTextureSize(id); 278 if (!IsCached(out _, id) || cachedSize.x != width || cachedSize.y != height) 279 { 280 Vector4 scaleBias = Vector4.zero; 281 if (!AllocateTextureWithoutBlit(id, width, height, ref scaleBias)) 282 return false; 283 } 284 return true; 285 } 286 287 /// <summary> 288 /// sort all the requested allocation from biggest to smallest and re-insert them. 289 /// This function does not moves the textures in the atlas, it only changes their coordinates 290 /// </summary> 291 /// <returns>True if all textures have successfully been re-inserted in the atlas</returns> 292 public bool RelayoutEntries() 293 { 294 var entries = new List<(int instanceId, Vector2Int size)>(); 295 296 foreach (var entry in m_RequestedTextures) 297 entries.Add((entry.Key, entry.Value)); 298 ResetAllocator(); 299 300 // Sort entries from biggest to smallest 301 entries.Sort((c1, c2) => 302 { 303 return c2.size.magnitude.CompareTo(c1.size.magnitude); 304 }); 305 306 bool success = true; 307 Vector4 newScaleOffset = Vector4.zero; 308 foreach (var e in entries) 309 success &= AllocateTextureWithoutBlit(e.instanceId, e.size.x, e.size.y, ref newScaleOffset); 310 311 return success; 312 } 313 314 /// <summary> 315 /// Get cache size in bytes. 316 /// </summary> 317 /// <param name="nbElement">The number of elements stored in the cache.</param> 318 /// <param name="resolution">Atlas resolution (square).</param> 319 /// <param name="hasMipmap">Atlas uses mip maps.</param> 320 /// <param name="format">Atlas format.</param> 321 /// <returns>The approximate size of the cache in bytes.</returns> 322 public static long GetApproxCacheSizeInByte(int nbElement, int resolution, bool hasMipmap, GraphicsFormat format) 323 => (long)(nbElement * resolution * resolution * (double)((hasMipmap ? k_MipmapFactorApprox : 1.0f) * GraphicsFormatUtility.GetBlockSize(format))); 324 325 /// <summary> 326 /// Compute the max size of a power of two atlas for a given size in byte (weight). 327 /// </summary> 328 /// <param name="weight">Atlas size in bytes.</param> 329 /// <param name="hasMipmap">Atlas uses mip maps.</param> 330 /// <param name="format">Atlas format.</param> 331 /// <returns>The largest possible resolution for a square atlas, constrained by weight, as a power of two value.</returns> 332 public static int GetMaxCacheSizeForWeightInByte(int weight, bool hasMipmap, GraphicsFormat format) 333 { 334 float bytePerPixel = (float)GraphicsFormatUtility.GetBlockSize(format) * (hasMipmap ? k_MipmapFactorApprox : 1.0f); 335 var maxAtlasSquareSize = Mathf.Sqrt((float)weight / bytePerPixel); 336 return CoreUtils.PreviousPowerOfTwo((int)maxAtlasSquareSize); 337 } 338 } 339}