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}