Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

Texture2DAtlas.cs 34KB

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