A game about forced loneliness, made by TACStudios
1using System.Collections.Generic; 2using UnityEngine.Experimental.Rendering; 3using System; 4 5namespace UnityEngine.Rendering 6{ 7 class AtlasAllocator 8 { 9 private class AtlasNode 10 { 11 public AtlasNode m_RightChild = null; 12 public AtlasNode m_BottomChild = null; 13 public Vector4 m_Rect = new Vector4(0, 0, 0, 0); // x,y is width and height (scale) z,w offset into atlas (offset) 14 15 public AtlasNode Allocate(ref ObjectPool<AtlasNode> pool, int width, int height, bool powerOfTwoPadding) 16 { 17 // not a leaf node, try children 18 if (m_RightChild != null) 19 { 20 AtlasNode node = m_RightChild.Allocate(ref pool, width, height, powerOfTwoPadding); 21 if (node == null) 22 { 23 node = m_BottomChild.Allocate(ref pool, width, height, powerOfTwoPadding); 24 } 25 return node; 26 } 27 28 int wPadd = 0; 29 int hPadd = 0; 30 31 if (powerOfTwoPadding) 32 { 33 wPadd = (int)m_Rect.x % width; 34 hPadd = (int)m_Rect.y % height; 35 } 36 37 //leaf node, check for fit 38 if ((width <= m_Rect.x - wPadd) && (height <= m_Rect.y - hPadd)) 39 { 40 // perform the split 41 m_RightChild = pool.Get(); 42 m_BottomChild = pool.Get(); 43 44 m_Rect.z += wPadd; 45 m_Rect.w += hPadd; 46 m_Rect.x -= wPadd; 47 m_Rect.y -= hPadd; 48 49 if (width > height) // logic to decide which way to split 50 { 51 // +--------+------+ 52 m_RightChild.m_Rect.z = m_Rect.z + width; // | | | 53 m_RightChild.m_Rect.w = m_Rect.w; // +--------+------+ 54 m_RightChild.m_Rect.x = m_Rect.x - width; // | | 55 m_RightChild.m_Rect.y = height; // | | 56 // +---------------+ 57 m_BottomChild.m_Rect.z = m_Rect.z; 58 m_BottomChild.m_Rect.w = m_Rect.w + height; 59 m_BottomChild.m_Rect.x = m_Rect.x; 60 m_BottomChild.m_Rect.y = m_Rect.y - height; 61 } 62 else 63 { // +---+-----------+ 64 m_RightChild.m_Rect.z = m_Rect.z + width; // | | | 65 m_RightChild.m_Rect.w = m_Rect.w; // | | | 66 m_RightChild.m_Rect.x = m_Rect.x - width; // +---+ + 67 m_RightChild.m_Rect.y = m_Rect.y; // | | | 68 // +---+-----------+ 69 m_BottomChild.m_Rect.z = m_Rect.z; 70 m_BottomChild.m_Rect.w = m_Rect.w + height; 71 m_BottomChild.m_Rect.x = width; 72 m_BottomChild.m_Rect.y = m_Rect.y - height; 73 } 74 m_Rect.x = width; 75 m_Rect.y = height; 76 return this; 77 } 78 return null; 79 } 80 81 public void Release(ref ObjectPool<AtlasNode> pool) 82 { 83 if (m_RightChild != null) 84 { 85 m_RightChild.Release(ref pool); 86 m_BottomChild.Release(ref pool); 87 pool.Release(m_RightChild); 88 pool.Release(m_BottomChild); 89 } 90 91 m_RightChild = null; 92 m_BottomChild = null; 93 m_Rect = Vector4.zero; 94 } 95 } 96 97 private AtlasNode m_Root; 98 private int m_Width; 99 private int m_Height; 100 private bool powerOfTwoPadding; 101 private ObjectPool<AtlasNode> m_NodePool; 102 103 public AtlasAllocator(int width, int height, bool potPadding) 104 { 105 m_Root = new AtlasNode(); 106 m_Root.m_Rect.Set(width, height, 0, 0); 107 m_Width = width; 108 m_Height = height; 109 powerOfTwoPadding = potPadding; 110 m_NodePool = new ObjectPool<AtlasNode>(_ => { }, _ => { }); 111 } 112 113 public bool Allocate(ref Vector4 result, int width, int height) 114 { 115 AtlasNode node = m_Root.Allocate(ref m_NodePool, width, height, powerOfTwoPadding); 116 if (node != null) 117 { 118 result = node.m_Rect; 119 return true; 120 } 121 else 122 { 123 result = Vector4.zero; 124 return false; 125 } 126 } 127 128 public void Reset() 129 { 130 m_Root.Release(ref m_NodePool); 131 m_Root.m_Rect.Set(m_Width, m_Height, 0, 0); 132 } 133 } 134 135 /// <summary> 136 /// A generic Atlas texture of 2D textures. 137 /// An atlas texture is a texture collection that collects multiple sub-textures into a single big texture. 138 /// Sub-texture allocation for Texture2DAtlas is static and will not change after initial allocation. 139 /// Does not add mipmap padding for sub-textures. 140 /// </summary> 141 public class Texture2DAtlas 142 { 143 private enum BlitType 144 { 145 Default, 146 CubeTo2DOctahedral, 147 SingleChannel, 148 CubeTo2DOctahedralSingleChannel, 149 } 150 151 /// <summary> 152 /// Texture is not on the GPU or is not up to date. 153 /// </summary> 154 private protected const int kGPUTexInvalid = 0; 155 /// <summary> 156 /// Texture Mip0 is on the GPU and up to date. 157 /// </summary> 158 private protected const int kGPUTexValidMip0 = 1; 159 /// <summary> 160 /// Texture and all mips are on the GPU and up to date. 161 /// </summary> 162 private protected const int kGPUTexValidMipAll = 2; 163 164 /// <summary> 165 /// The texture for the atlas. 166 /// </summary> 167 private protected RTHandle m_AtlasTexture = null; 168 /// <summary> 169 /// Width of the atlas. 170 /// </summary> 171 private protected int m_Width; 172 /// <summary> 173 /// Height of the atlas. 174 /// </summary> 175 private protected int m_Height; 176 /// <summary> 177 /// Format of the atlas. 178 /// </summary> 179 private protected GraphicsFormat m_Format; 180 /// <summary> 181 /// Atlas uses mip maps. 182 /// </summary> 183 private protected bool m_UseMipMaps; 184 bool m_IsAtlasTextureOwner = false; 185 private AtlasAllocator m_AtlasAllocator = null; 186 private Dictionary<int, (Vector4 scaleOffset, Vector2Int size)> m_AllocationCache = new Dictionary<int, (Vector4, Vector2Int)>(); 187 private Dictionary<int, int> m_IsGPUTextureUpToDate = new Dictionary<int, int>(); 188 private Dictionary<int, int> m_TextureHashes = new Dictionary<int, int>(); 189 190 static readonly Vector4 fullScaleOffset = new Vector4(1, 1, 0, 0); 191 192 // Maximum mip padding that can be applied to the textures in the atlas (1 << 10 = 1024 pixels) 193 static readonly int s_MaxMipLevelPadding = 10; 194 195 /// <summary> 196 /// Maximum mip padding (pow2) that can be applied to the textures in the atlas 197 /// </summary> 198 public static int maxMipLevelPadding => s_MaxMipLevelPadding; 199 200 /// <summary> 201 /// Handle to the texture of the atlas. 202 /// </summary> 203 public RTHandle AtlasTexture 204 { 205 get 206 { 207 return m_AtlasTexture; 208 } 209 } 210 211 /// <summary> 212 /// Creates a new empty texture atlas. 213 /// </summary> 214 /// <param name="width">Width of the atlas in pixels.</param> 215 /// <param name="height">Height of atlas in pixels.</param> 216 /// <param name="format">GraphicsFormat of the atlas.</param> 217 /// <param name="filterMode">Filtering mode of the atlas.</param> 218 /// <param name="powerOfTwoPadding">Power of two padding.</param> 219 /// <param name="name">Name of the atlas</param> 220 /// <param name="useMipMap">Use mip maps</param> 221 public Texture2DAtlas(int width, int height, GraphicsFormat format, FilterMode filterMode = FilterMode.Point, bool powerOfTwoPadding = false, string name = "", bool useMipMap = true) 222 { 223 m_Width = width; 224 m_Height = height; 225 m_Format = format; 226 m_UseMipMaps = useMipMap; 227 m_AtlasTexture = RTHandles.Alloc( 228 width: m_Width, 229 height: m_Height, 230 format: m_Format, 231 filterMode: filterMode, 232 wrapMode: TextureWrapMode.Clamp, 233 useMipMap: useMipMap, 234 autoGenerateMips: false, 235 name: name 236 ); 237 m_IsAtlasTextureOwner = true; 238 239 // We clear on create to avoid garbage data to be present in the atlas 240 int mipCount = useMipMap ? GetTextureMipmapCount(m_Width, m_Height) : 1; 241 for (int mipIdx = 0; mipIdx < mipCount; ++mipIdx) 242 { 243 Graphics.SetRenderTarget(m_AtlasTexture, mipIdx); 244 GL.Clear(false, true, Color.clear); 245 } 246 247 m_AtlasAllocator = new AtlasAllocator(width, height, powerOfTwoPadding); 248 } 249 250 /// <summary> 251 /// Release atlas resources. 252 /// </summary> 253 public void Release() 254 { 255 ResetAllocator(); 256 if (m_IsAtlasTextureOwner) { RTHandles.Release(m_AtlasTexture); } 257 } 258 259 /// <summary> 260 /// Clear atlas sub-texture allocations. 261 /// </summary> 262 public void ResetAllocator() 263 { 264 m_AtlasAllocator.Reset(); 265 m_AllocationCache.Clear(); 266 267 m_IsGPUTextureUpToDate.Clear(); // mark all GPU textures as invalid. 268 } 269 270 /// <summary> 271 /// Clear atlas texture. 272 /// </summary> 273 /// <param name="cmd">Target command buffer for graphics commands.</param> 274 public void ClearTarget(CommandBuffer cmd) 275 { 276 int mipCount = (m_UseMipMaps) ? GetTextureMipmapCount(m_Width, m_Height) : 1; 277 278 // clear the atlas by blitting a black texture at every mips 279 for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) 280 { 281 cmd.SetRenderTarget(m_AtlasTexture, mipLevel); 282 Blitter.BlitQuad(cmd, Texture2D.blackTexture, fullScaleOffset, fullScaleOffset, mipLevel, true); 283 } 284 285 m_IsGPUTextureUpToDate.Clear(); // mark all GPU textures as invalid. 286 } 287 288 /// <summary> 289 /// Return texture mip map count based on the width and height. 290 /// </summary> 291 /// <param name="width">The texture width in pixels.</param> 292 /// <param name="height">The texture height in pixels.</param> 293 /// <returns>The number of mip maps.</returns> 294 private protected int GetTextureMipmapCount(int width, int height) 295 { 296 if (!m_UseMipMaps) 297 return 1; 298 299 // We don't care about the real mipmap count in the texture because they are generated by the atlas 300 float maxSize = Mathf.Max(width, height); 301 return CoreUtils.GetMipCount(maxSize); 302 } 303 304 /// <summary> 305 /// Test if a texture is a 2D texture. 306 /// </summary> 307 /// <param name="texture">Source texture.</param> 308 /// <returns>True if texture is 2D, false otherwise.</returns> 309 private protected bool Is2D(Texture texture) 310 { 311 RenderTexture rt = texture as RenderTexture; 312 313 return (texture is Texture2D || rt?.dimension == TextureDimension.Tex2D); 314 } 315 316 /// <summary> 317 /// Checks if single/multi/single channel format conversion is required. 318 /// </summary> 319 /// <param name="source">Blit source texture</param> 320 /// <param name="destination">Blit destination texture</param> 321 /// <returns>true on single channel conversion false otherwise</returns> 322 private protected bool IsSingleChannelBlit(Texture source, Texture destination) 323 { 324 var srcCount = GraphicsFormatUtility.GetComponentCount(source.graphicsFormat); 325 var dstCount = GraphicsFormatUtility.GetComponentCount(destination.graphicsFormat); 326 if (srcCount == 1 || dstCount == 1) 327 { 328 // One to many, many to one 329 if (srcCount != dstCount) 330 return true; 331 332 // Single channel swizzle 333 var srcSwizzle = 334 ((1 << ((int)GraphicsFormatUtility.GetSwizzleA(source.graphicsFormat) & 0x7)) << 24) | 335 ((1 << ((int)GraphicsFormatUtility.GetSwizzleB(source.graphicsFormat) & 0x7)) << 16) | 336 ((1 << ((int)GraphicsFormatUtility.GetSwizzleG(source.graphicsFormat) & 0x7)) << 8) | 337 ((1 << ((int)GraphicsFormatUtility.GetSwizzleR(source.graphicsFormat) & 0x7))); 338 var dstSwizzle = 339 ((1 << ((int)GraphicsFormatUtility.GetSwizzleA(destination.graphicsFormat) & 0x7)) << 24) | 340 ((1 << ((int)GraphicsFormatUtility.GetSwizzleB(destination.graphicsFormat) & 0x7)) << 16) | 341 ((1 << ((int)GraphicsFormatUtility.GetSwizzleG(destination.graphicsFormat) & 0x7)) << 8) | 342 ((1 << ((int)GraphicsFormatUtility.GetSwizzleR(destination.graphicsFormat) & 0x7))); 343 if (srcSwizzle != dstSwizzle) 344 return true; 345 } 346 347 return false; 348 } 349 350 private void Blit2DTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips, BlitType blitType) 351 { 352 int mipCount = GetTextureMipmapCount(texture.width, texture.height); 353 354 if (!blitMips) 355 mipCount = 1; 356 357 for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) 358 { 359 cmd.SetRenderTarget(m_AtlasTexture, mipLevel); 360 switch (blitType) 361 { 362 case BlitType.Default: Blitter.BlitQuad(cmd, texture, sourceScaleOffset, scaleOffset, mipLevel, true); break; 363 case BlitType.CubeTo2DOctahedral: Blitter.BlitCubeToOctahedral2DQuad(cmd, texture, scaleOffset, mipLevel); break; 364 case BlitType.SingleChannel: Blitter.BlitQuadSingleChannel(cmd, texture, sourceScaleOffset, scaleOffset, mipLevel); break; 365 case BlitType.CubeTo2DOctahedralSingleChannel: Blitter.BlitCubeToOctahedral2DQuadSingleChannel(cmd, texture, scaleOffset, mipLevel); break; 366 } 367 } 368 } 369 370 /// <summary> 371 /// Mark texture valid on the GPU. 372 /// </summary> 373 /// <param name="instanceId">Texture instance ID.</param> 374 /// <param name="mipAreValid">Texture has valid mip maps.</param> 375 private protected void MarkGPUTextureValid(int instanceId, bool mipAreValid = false) 376 { 377 m_IsGPUTextureUpToDate[instanceId] = (mipAreValid) ? kGPUTexValidMipAll : kGPUTexValidMip0; 378 } 379 380 /// <summary> 381 /// Mark texture invalid on the GPU. 382 /// </summary> 383 /// <param name="instanceId">Texture instance ID.</param> 384 private protected void MarkGPUTextureInvalid(int instanceId) => m_IsGPUTextureUpToDate[instanceId] = kGPUTexInvalid; 385 386 /// <summary> 387 /// Blit 2D texture into the atlas. 388 /// </summary> 389 /// <param name="cmd">Target command buffer for graphics commands.</param> 390 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 391 /// <param name="texture">Source Texture</param> 392 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 393 /// <param name="blitMips">Blit mip maps.</param> 394 /// <param name="overrideInstanceID">Override texture instance ID.</param> 395 public virtual void BlitTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 396 { 397 // This atlas only support 2D texture so we only blit 2D textures 398 if (Is2D(texture)) 399 { 400 BlitType blitType = BlitType.Default; 401 if (IsSingleChannelBlit(texture, m_AtlasTexture.m_RT)) 402 blitType = BlitType.SingleChannel; 403 404 Blit2DTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, blitType); 405 var instanceID = overrideInstanceID != -1 ? overrideInstanceID : GetTextureID(texture); 406 MarkGPUTextureValid(instanceID, blitMips); 407 m_TextureHashes[instanceID] = CoreUtils.GetTextureHash(texture); 408 } 409 } 410 411 /// <summary> 412 /// Blit octahedral texture into the atlas. 413 /// </summary> 414 /// <param name="cmd">Target command buffer for graphics commands.</param> 415 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 416 /// <param name="texture">Source Texture</param> 417 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 418 /// <param name="blitMips">Blit mip maps.</param> 419 /// <param name="overrideInstanceID">Override texture instance ID.</param> 420 public virtual void BlitOctahedralTexture(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, Vector4 sourceScaleOffset, bool blitMips = true, int overrideInstanceID = -1) 421 { 422 // Default implementation. No padding in Texture2DAtlas, no need to handle specially. 423 BlitTexture(cmd, scaleOffset, texture, sourceScaleOffset, blitMips, overrideInstanceID); 424 } 425 426 /// <summary> 427 /// Blit and project Cube texture into a 2D texture in the atlas. 428 /// </summary> 429 /// <param name="cmd">Target command buffer for graphics commands.</param> 430 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 431 /// <param name="texture">Source Texture</param> 432 /// <param name="blitMips">Blit mip maps.</param> 433 /// <param name="overrideInstanceID">Override texture instance ID.</param> 434 public virtual void BlitCubeTexture2D(CommandBuffer cmd, Vector4 scaleOffset, Texture texture, bool blitMips = true, int overrideInstanceID = -1) 435 { 436 Debug.Assert(texture.dimension == TextureDimension.Cube); 437 438 // This atlas only support 2D texture so we map Cube into set of 2D textures 439 if (texture.dimension == TextureDimension.Cube) 440 { 441 BlitType blitType = BlitType.CubeTo2DOctahedral; 442 if (IsSingleChannelBlit(texture, m_AtlasTexture.m_RT)) 443 blitType = BlitType.CubeTo2DOctahedralSingleChannel; 444 445 // By default blit cube into a single octahedral 2D texture quad 446 Blit2DTexture(cmd, scaleOffset, texture, new Vector4(1.0f, 1.0f, 0.0f, 0.0f), blitMips, blitType); 447 448 var instanceID = overrideInstanceID != -1 ? overrideInstanceID : GetTextureID(texture); 449 MarkGPUTextureValid(instanceID, blitMips); 450 m_TextureHashes[instanceID] = CoreUtils.GetTextureHash(texture); 451 } 452 } 453 454 /// <summary> 455 /// Allocate space from the atlas for a texture and copy texture contents into the atlas. 456 /// </summary> 457 /// <param name="cmd">Target command buffer for graphics commands.</param> 458 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 459 /// <param name="texture">Source Texture</param> 460 /// <param name="width">Request width in pixels.</param> 461 /// <param name="height">Request height in pixels.</param> 462 /// <param name="overrideInstanceID">Override texture instance ID.</param> 463 /// <returns>True if the texture was successfully allocated and copied; false otherwise.</returns> 464 public virtual bool AllocateTexture(CommandBuffer cmd, ref Vector4 scaleOffset, Texture texture, int width, int height, int overrideInstanceID = -1) 465 { 466 var instanceID = overrideInstanceID != -1 ? overrideInstanceID : GetTextureID(texture); 467 bool allocated = AllocateTextureWithoutBlit(instanceID, width, height, ref scaleOffset); 468 469 if (allocated) 470 { 471 if (Is2D(texture)) 472 BlitTexture(cmd, scaleOffset, texture, fullScaleOffset); 473 else 474 BlitCubeTexture2D(cmd, scaleOffset, texture, true); 475 476 // texture is up to date 477 MarkGPUTextureValid(instanceID, true); 478 m_TextureHashes[instanceID] = CoreUtils.GetTextureHash(texture); 479 } 480 481 return allocated; 482 } 483 484 /// <summary> 485 /// Allocate space from the atlas for a texture. 486 /// </summary> 487 /// <param name="texture">Source texture.</param> 488 /// <param name="width">Request width in pixels.</param> 489 /// <param name="height">Request height in pixels.</param> 490 /// <param name="scaleOffset">Allocated scale (.xy) and offset (.zw).</param> 491 /// <returns>True on success, false otherwise.</returns> 492 public bool AllocateTextureWithoutBlit(Texture texture, int width, int height, ref Vector4 scaleOffset) 493 => AllocateTextureWithoutBlit(texture.GetInstanceID(), width, height, ref scaleOffset); 494 495 /// <summary> 496 /// Allocate space from the atlas for a texture. 497 /// </summary> 498 /// <param name="instanceId">Source texture instance ID.</param> 499 /// <param name="width">Request width in pixels.</param> 500 /// <param name="height">Request height in pixels.</param> 501 /// <param name="scaleOffset">Allocated scale (.xy) and offset (.zw).</param> 502 /// <returns>True on success, false otherwise.</returns> 503 public virtual bool AllocateTextureWithoutBlit(int instanceId, int width, int height, ref Vector4 scaleOffset) 504 { 505 scaleOffset = Vector4.zero; 506 507 if (m_AtlasAllocator.Allocate(ref scaleOffset, width, height)) 508 { 509 scaleOffset.Scale(new Vector4(1.0f / m_Width, 1.0f / m_Height, 1.0f / m_Width, 1.0f / m_Height)); 510 m_AllocationCache[instanceId] = (scaleOffset, new Vector2Int(width, height)); 511 MarkGPUTextureInvalid(instanceId); // the texture data haven't been uploaded 512 m_TextureHashes[instanceId] = -1; 513 return true; 514 } 515 else 516 { 517 return false; 518 } 519 } 520 521 /// <summary> 522 /// Compute hash from texture properties. 523 /// </summary> 524 /// <param name="textureA">Source texture A.</param> 525 /// <param name="textureB">Source texture B.</param> 526 /// <returns>Hash of texture porperties.</returns> 527 private protected int GetTextureHash(Texture textureA, Texture textureB) 528 { 529 int hash = CoreUtils.GetTextureHash(textureA) + 23 * CoreUtils.GetTextureHash(textureB); 530 return hash; 531 } 532 533 /// <summary> 534 /// Get sub-texture ID for the atlas. 535 /// </summary> 536 /// <param name="texture">Source texture.</param> 537 /// <returns>Texture instance ID.</returns> 538 public int GetTextureID(Texture texture) 539 { 540 return texture.GetInstanceID(); 541 } 542 543 /// <summary> 544 /// Get sub-texture ID for the atlas. 545 /// </summary> 546 /// <param name="textureA">Source texture A.</param> 547 /// <param name="textureB">Source texture B.</param> 548 /// <returns>Combined texture instance ID.</returns> 549 public int GetTextureID(Texture textureA, Texture textureB) 550 { 551 return GetTextureID(textureA) + 23 * GetTextureID(textureB); 552 } 553 554 /// <summary> 555 /// Check if the atlas contains the textures. 556 /// </summary> 557 /// <param name="scaleOffset">Texture scale (.xy) and offset (.zw).</param> 558 /// <param name="textureA">Source texture A.</param> 559 /// <param name="textureB">Source texture B.</param> 560 /// <returns>True if the texture is in the atlas, false otherwise.</returns> 561 public bool IsCached(out Vector4 scaleOffset, Texture textureA, Texture textureB) 562 => IsCached(out scaleOffset, GetTextureID(textureA, textureB)); 563 564 /// <summary> 565 /// Check if the atlas contains the textures. 566 /// </summary> 567 /// <param name="scaleOffset">Texture scale (.xy) and offset (.zw).</param> 568 /// <param name="texture">Source texture</param> 569 /// <returns>True if the texture is in the atlas, false otherwise.</returns> 570 public bool IsCached(out Vector4 scaleOffset, Texture texture) 571 => IsCached(out scaleOffset, GetTextureID(texture)); 572 573 /// <summary> 574 /// Check if the atlas contains the texture. 575 /// </summary> 576 /// <param name="scaleOffset">Texture scale (.xy) and offset (.zw).</param> 577 /// <param name="id">Source texture instance ID.</param> 578 /// <returns>True if the texture is in the atlas, false otherwise</returns> 579 public bool IsCached(out Vector4 scaleOffset, int id) 580 { 581 bool cached = m_AllocationCache.TryGetValue(id, out var value); 582 scaleOffset = value.scaleOffset; 583 return cached; 584 } 585 586 /// <summary> 587 /// Get cached texture size. 588 /// </summary> 589 /// <param name="id">Source texture instance ID.</param> 590 /// <returns>Texture size.</returns> 591 internal Vector2Int GetCachedTextureSize(int id) 592 { 593 m_AllocationCache.TryGetValue(id, out var value); 594 return value.size; 595 } 596 597 /// <summary> 598 /// Check if contents of a texture needs to be updated in the atlas. 599 /// </summary> 600 /// <param name="texture">Source texture.</param> 601 /// <param name="needMips">Texture uses mips.</param> 602 /// <returns>True if texture needs update, false otherwise.</returns> 603 public virtual bool NeedsUpdate(Texture texture, bool needMips = false) 604 { 605 RenderTexture rt = texture as RenderTexture; 606 int key = GetTextureID(texture); 607 int textureHash = CoreUtils.GetTextureHash(texture); 608 609 // Update the render texture if needed 610 if (rt != null) 611 { 612 int updateCount; 613 if (m_IsGPUTextureUpToDate.TryGetValue(key, out updateCount)) 614 { 615 if (rt.updateCount != updateCount) 616 { 617 m_IsGPUTextureUpToDate[key] = (int)rt.updateCount; 618 return true; 619 } 620 } 621 else 622 { 623 m_IsGPUTextureUpToDate[key] = (int)rt.updateCount; 624 } 625 } 626 // In case the texture settings/import settings have changed, we need to update it 627 else if (m_TextureHashes.TryGetValue(key, out int hash) && hash != textureHash) 628 { 629 m_TextureHashes[key] = textureHash; 630 return true; 631 } 632 // For regular textures, values == 0 means that their GPU data needs to be updated (either because 633 // the atlas have been re-layouted or the texture have never been uploaded. We also check if the mips 634 // are valid for the texture if we need them 635 else if (m_IsGPUTextureUpToDate.TryGetValue(key, out var value)) 636 return value == kGPUTexInvalid || (needMips && value == kGPUTexValidMip0); 637 638 return false; 639 } 640 641 /// <summary> 642 /// Check if a slot needs to be updated in the atlas. 643 /// </summary> 644 /// <param name="id">The id.</param> 645 /// <param name="updateCount">The update count.</param> 646 /// <param name="needMips">Texture uses mips.</param> 647 /// <returns>True if slot needs update, false otherwise.</returns> 648 public virtual bool NeedsUpdate(int id, int updateCount, bool needMips = false) 649 { 650 int atlasUpdateCount; 651 if (m_IsGPUTextureUpToDate.TryGetValue(id, out atlasUpdateCount)) 652 { 653 if (updateCount != atlasUpdateCount) 654 { 655 m_IsGPUTextureUpToDate[id] = updateCount; 656 return true; 657 } 658 } 659 else 660 { 661 m_IsGPUTextureUpToDate[id] = updateCount; 662 } 663 664 return false; 665 } 666 667 /// <summary> 668 /// Check if contents of a texture needs to be updated in the atlas. 669 /// </summary> 670 /// <param name="textureA">Source texture A.</param> 671 /// <param name="textureB">Source texture B.</param> 672 /// <param name="needMips">Texture uses mips.</param> 673 /// <returns>True if texture needs update, false otherwise.</returns> 674 public virtual bool NeedsUpdate(Texture textureA, Texture textureB, bool needMips = false) 675 { 676 RenderTexture rtA = textureA as RenderTexture; 677 RenderTexture rtB = textureB as RenderTexture; 678 int key = GetTextureID(textureA, textureB); 679 int textureHash = GetTextureHash(textureA, textureB); 680 681 // Update the render texture if needed 682 if (rtA != null || rtB != null) 683 { 684 int updateCount; 685 if (m_IsGPUTextureUpToDate.TryGetValue(key, out updateCount)) 686 { 687 if (rtA != null && rtB != null && Math.Min(rtA.updateCount, rtB.updateCount) != updateCount) 688 { 689 m_IsGPUTextureUpToDate[key] = (int)Math.Min(rtA.updateCount, rtB.updateCount); 690 return true; 691 } 692 else if (rtA != null && rtA.updateCount != updateCount) 693 { 694 m_IsGPUTextureUpToDate[key] = (int)rtA.updateCount; 695 return true; 696 } 697 else if (rtB != null && rtB.updateCount != updateCount) 698 { 699 m_IsGPUTextureUpToDate[key] = (int)rtB.updateCount; 700 return true; 701 } 702 } 703 else 704 { 705 m_IsGPUTextureUpToDate[key] = textureHash; 706 } 707 } 708 // In case the texture settings/import settings have changed, we need to update it 709 else if (m_TextureHashes.TryGetValue(key, out int hash) && hash != textureHash) 710 { 711 m_TextureHashes[key] = key; 712 return true; 713 } 714 // For regular textures, values == 0 means that their GPU data needs to be updated (either because 715 // the atlas have been re-layouted or the texture have never been uploaded. We also check if the mips 716 // are valid for the texture if we need them 717 else if (m_IsGPUTextureUpToDate.TryGetValue(key, out var value)) 718 return value == kGPUTexInvalid || (needMips && value == kGPUTexValidMip0); 719 720 return false; 721 } 722 723 /// <summary> 724 /// Add a texture into the atlas. 725 /// </summary> 726 /// <param name="cmd">Command buffer used for texture copy.</param> 727 /// <param name="scaleOffset">Sub-texture rectangle for the added texture. Scale in .xy, offset int .zw</param> 728 /// <param name="texture">The texture to be added.</param> 729 /// <returns>True if the atlas contains the texture, false otherwise.</returns> 730 public virtual bool AddTexture(CommandBuffer cmd, ref Vector4 scaleOffset, Texture texture) 731 { 732 if (IsCached(out scaleOffset, texture)) 733 return true; 734 735 return AllocateTexture(cmd, ref scaleOffset, texture, texture.width, texture.height); 736 } 737 738 /// <summary> 739 /// Update a texture in the atlas. 740 /// </summary> 741 /// <param name="cmd">Target command buffer for graphics commands.</param> 742 /// <param name="oldTexture">Texture in atlas.</param> 743 /// <param name="newTexture">Replacement source texture.</param> 744 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 745 /// <param name="sourceScaleOffset">Source scale (.xy) and offset(.zw).</param> 746 /// <param name="updateIfNeeded">Enable texture blit.</param> 747 /// <param name="blitMips">Blit mip maps.</param> 748 /// <returns>True on success, false otherwise.</returns> 749 public virtual bool UpdateTexture(CommandBuffer cmd, Texture oldTexture, Texture newTexture, ref Vector4 scaleOffset, Vector4 sourceScaleOffset, bool updateIfNeeded = true, bool blitMips = true) 750 { 751 // In case the old texture is here, we Blit the new one at the scale offset of the old one 752 if (IsCached(out scaleOffset, oldTexture)) 753 { 754 if (updateIfNeeded && NeedsUpdate(newTexture)) 755 { 756 if (Is2D(newTexture)) 757 BlitTexture(cmd, scaleOffset, newTexture, sourceScaleOffset, blitMips); 758 else 759 BlitCubeTexture2D(cmd, scaleOffset, newTexture, blitMips); 760 MarkGPUTextureValid(GetTextureID(newTexture), blitMips); // texture is up to date 761 } 762 return true; 763 } 764 else // else we try to allocate the updated texture 765 { 766 return AllocateTexture(cmd, ref scaleOffset, newTexture, newTexture.width, newTexture.height); 767 } 768 } 769 770 /// <summary> 771 /// Update a texture in the atlas. 772 /// </summary> 773 /// <param name="cmd">Target command buffer for graphics commands.</param> 774 /// <param name="texture">Texture in atlas.</param> 775 /// <param name="scaleOffset">Destination scale (.xy) and offset (.zw)</param> 776 /// <param name="updateIfNeeded">Enable texture blit.</param> 777 /// <param name="blitMips">Blit mip maps.</param> 778 /// <returns>True on success, false otherwise.</returns> 779 public virtual bool UpdateTexture(CommandBuffer cmd, Texture texture, ref Vector4 scaleOffset, bool updateIfNeeded = true, bool blitMips = true) 780 => UpdateTexture(cmd, texture, texture, ref scaleOffset, fullScaleOffset, updateIfNeeded, blitMips); 781 782 internal bool EnsureTextureSlot(out bool isUploadNeeded, ref Vector4 scaleBias, int key, int width, int height) 783 { 784 isUploadNeeded = false; 785 if (m_AllocationCache.TryGetValue(key, out var value)) 786 { 787 scaleBias = value.scaleOffset; 788 return true; 789 } 790 if (!m_AtlasAllocator.Allocate(ref scaleBias, width, height)) 791 return false; 792 isUploadNeeded = true; 793 scaleBias.Scale(new Vector4(1.0f / m_Width, 1.0f / m_Height, 1.0f / m_Width, 1.0f / m_Height)); 794 m_AllocationCache.Add(key, (scaleBias, new Vector2Int(width, height))); 795 return true; 796 } 797 } 798}