TextureGroup.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  1. using Ryujinx.Common;
  2. using Ryujinx.Cpu.Tracking;
  3. using Ryujinx.Graphics.GAL;
  4. using Ryujinx.Graphics.Texture;
  5. using Ryujinx.Memory.Range;
  6. using System;
  7. using System.Collections.Generic;
  8. namespace Ryujinx.Graphics.Gpu.Image
  9. {
  10. /// <summary>
  11. /// A texture group represents a group of textures that belong to the same storage.
  12. /// When views are created, this class will track memory accesses for them separately.
  13. /// The group iteratively adds more granular tracking as views of different kinds are added.
  14. /// Note that a texture group can be absorbed into another when it becomes a view parent.
  15. /// </summary>
  16. class TextureGroup : IDisposable
  17. {
  18. private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
  19. /// <summary>
  20. /// The storage texture associated with this group.
  21. /// </summary>
  22. public Texture Storage { get; }
  23. /// <summary>
  24. /// Indicates if the texture has copy dependencies. If true, then all modifications
  25. /// must be signalled to the group, rather than skipping ones still to be flushed.
  26. /// </summary>
  27. public bool HasCopyDependencies { get; set; }
  28. private GpuContext _context;
  29. private int[] _allOffsets;
  30. private int[] _sliceSizes;
  31. private bool _is3D;
  32. private bool _hasMipViews;
  33. private bool _hasLayerViews;
  34. private int _layers;
  35. private int _levels;
  36. private MultiRange TextureRange => Storage.Range;
  37. /// <summary>
  38. /// The views list from the storage texture.
  39. /// </summary>
  40. private List<Texture> _views;
  41. private TextureGroupHandle[] _handles;
  42. private bool[] _loadNeeded;
  43. /// <summary>
  44. /// Create a new texture group.
  45. /// </summary>
  46. /// <param name="context">GPU context that the texture group belongs to</param>
  47. /// <param name="storage">The storage texture for this group</param>
  48. public TextureGroup(GpuContext context, Texture storage)
  49. {
  50. Storage = storage;
  51. _context = context;
  52. _is3D = storage.Info.Target == Target.Texture3D;
  53. _layers = storage.Info.GetSlices();
  54. _levels = storage.Info.Levels;
  55. }
  56. /// <summary>
  57. /// Initialize a new texture group's dirty regions and offsets.
  58. /// </summary>
  59. /// <param name="size">Size info for the storage texture</param>
  60. /// <param name="hasLayerViews">True if the storage will have layer views</param>
  61. /// <param name="hasMipViews">True if the storage will have mip views</param>
  62. public void Initialize(ref SizeInfo size, bool hasLayerViews, bool hasMipViews)
  63. {
  64. _allOffsets = size.AllOffsets;
  65. _sliceSizes = size.SliceSizes;
  66. (_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
  67. RecalculateHandleRegions();
  68. }
  69. /// <summary>
  70. /// Consume the dirty flags for a given texture. The state is shared between views of the same layers and levels.
  71. /// </summary>
  72. /// <param name="texture">The texture being used</param>
  73. /// <returns>True if a flag was dirty, false otherwise</returns>
  74. public bool ConsumeDirty(Texture texture)
  75. {
  76. bool dirty = false;
  77. EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
  78. {
  79. for (int i = 0; i < regionCount; i++)
  80. {
  81. TextureGroupHandle group = _handles[baseHandle + i];
  82. foreach (CpuRegionHandle handle in group.Handles)
  83. {
  84. if (handle.Dirty)
  85. {
  86. handle.Reprotect();
  87. dirty = true;
  88. }
  89. }
  90. }
  91. });
  92. return dirty;
  93. }
  94. /// <summary>
  95. /// Synchronize memory for a given texture.
  96. /// If overlapping tracking handles are dirty, fully or partially synchronize the texture data.
  97. /// </summary>
  98. /// <param name="texture">The texture being used</param>
  99. public void SynchronizeMemory(Texture texture)
  100. {
  101. EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
  102. {
  103. bool dirty = false;
  104. bool anyModified = false;
  105. bool anyUnmapped = false;
  106. for (int i = 0; i < regionCount; i++)
  107. {
  108. TextureGroupHandle group = _handles[baseHandle + i];
  109. bool modified = group.Modified;
  110. bool handleDirty = false;
  111. bool handleModified = false;
  112. bool handleUnmapped = false;
  113. foreach (CpuRegionHandle handle in group.Handles)
  114. {
  115. if (handle.Dirty)
  116. {
  117. handle.Reprotect();
  118. handleDirty = true;
  119. }
  120. else
  121. {
  122. handleUnmapped |= handle.Unmapped;
  123. handleModified |= modified;
  124. }
  125. }
  126. // Evaluate if any copy dependencies need to be fulfilled. A few rules:
  127. // If the copy handle needs to be synchronized, prefer our own state.
  128. // If we need to be synchronized and there is a copy present, prefer the copy.
  129. if (group.NeedsCopy && group.Copy())
  130. {
  131. anyModified |= true; // The copy target has been modified.
  132. handleDirty = false;
  133. }
  134. else
  135. {
  136. anyModified |= handleModified;
  137. dirty |= handleDirty;
  138. }
  139. anyUnmapped |= handleUnmapped;
  140. if (group.NeedsCopy)
  141. {
  142. // The texture we copied from is still being written to. Copy from it again the next time this texture is used.
  143. texture.SignalGroupDirty();
  144. }
  145. _loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
  146. }
  147. if (dirty)
  148. {
  149. if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
  150. {
  151. // Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
  152. SynchronizePartial(baseHandle, regionCount);
  153. }
  154. else
  155. {
  156. // Full texture invalidation.
  157. texture.SynchronizeFull();
  158. }
  159. }
  160. });
  161. }
  162. /// <summary>
  163. /// Synchronize part of the storage texture, represented by a given range of handles.
  164. /// Only handles marked by the _loadNeeded array will be synchronized.
  165. /// </summary>
  166. /// <param name="baseHandle">The base index of the range of handles</param>
  167. /// <param name="regionCount">The number of handles to synchronize</param>
  168. private void SynchronizePartial(int baseHandle, int regionCount)
  169. {
  170. for (int i = 0; i < regionCount; i++)
  171. {
  172. if (_loadNeeded[baseHandle + i])
  173. {
  174. var info = GetHandleInformation(baseHandle + i);
  175. int offsetIndex = info.Index;
  176. // Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
  177. for (int layer = 0; layer < info.Layers; layer++)
  178. {
  179. for (int level = 0; level < info.Levels; level++)
  180. {
  181. int offset = _allOffsets[offsetIndex];
  182. int endOffset = (offsetIndex + 1 == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[offsetIndex + 1];
  183. int size = endOffset - offset;
  184. ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
  185. data = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel, true);
  186. Storage.SetData(data, info.BaseLayer, info.BaseLevel);
  187. offsetIndex++;
  188. }
  189. }
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// Signal that a texture in the group has been modified by the GPU.
  195. /// </summary>
  196. /// <param name="texture">The texture that has been modified</param>
  197. /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
  198. public void SignalModified(Texture texture, bool registerAction)
  199. {
  200. EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
  201. {
  202. for (int i = 0; i < regionCount; i++)
  203. {
  204. TextureGroupHandle group = _handles[baseHandle + i];
  205. group.SignalModified();
  206. if (registerAction)
  207. {
  208. RegisterAction(group);
  209. }
  210. }
  211. });
  212. }
  213. /// <summary>
  214. /// Signal that a texture in the group is actively bound, or has been unbound by the GPU.
  215. /// </summary>
  216. /// <param name="texture">The texture that has been modified</param>
  217. /// <param name="bound">True if this texture is being bound, false if unbound</param>
  218. /// <param name="registerAction">True if the flushing read action should be registered, false otherwise</param>
  219. public void SignalModifying(Texture texture, bool bound, bool registerAction)
  220. {
  221. EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) =>
  222. {
  223. for (int i = 0; i < regionCount; i++)
  224. {
  225. TextureGroupHandle group = _handles[baseHandle + i];
  226. group.SignalModifying(bound);
  227. if (registerAction)
  228. {
  229. RegisterAction(group);
  230. }
  231. }
  232. });
  233. }
  234. /// <summary>
  235. /// Register a read/write action to flush for a texture group.
  236. /// </summary>
  237. /// <param name="group">The group to register an action for</param>
  238. public void RegisterAction(TextureGroupHandle group)
  239. {
  240. foreach (CpuRegionHandle handle in group.Handles)
  241. {
  242. handle.RegisterAction((address, size) => FlushAction(group, address, size));
  243. }
  244. }
  245. /// <summary>
  246. /// Propagates the mip/layer view flags depending on the texture type.
  247. /// When the most granular type of subresource has views, the other type of subresource must be segmented granularly too.
  248. /// </summary>
  249. /// <param name="hasLayerViews">True if the storage has layer views</param>
  250. /// <param name="hasMipViews">True if the storage has mip views</param>
  251. /// <returns>The input values after propagation</returns>
  252. private (bool HasLayerViews, bool HasMipViews) PropagateGranularity(bool hasLayerViews, bool hasMipViews)
  253. {
  254. if (_is3D)
  255. {
  256. hasMipViews |= hasLayerViews;
  257. }
  258. else
  259. {
  260. hasLayerViews |= hasMipViews;
  261. }
  262. return (hasLayerViews, hasMipViews);
  263. }
  264. /// <summary>
  265. /// Evaluate the range of tracking handles which a view texture overlaps with.
  266. /// </summary>
  267. /// <param name="texture">The texture to get handles for</param>
  268. /// <param name="callback">
  269. /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
  270. /// This can be called for multiple disjoint ranges, if required.
  271. /// </param>
  272. private void EvaluateRelevantHandles(Texture texture, HandlesCallbackDelegate callback)
  273. {
  274. if (texture == Storage || !(_hasMipViews || _hasLayerViews))
  275. {
  276. callback(0, _handles.Length);
  277. return;
  278. }
  279. EvaluateRelevantHandles(texture.FirstLayer, texture.FirstLevel, texture.Info.GetSlices(), texture.Info.Levels, callback);
  280. }
  281. /// <summary>
  282. /// Evaluate the range of tracking handles which a view texture overlaps with,
  283. /// using the view's position and slice/level counts.
  284. /// </summary>
  285. /// <param name="firstLayer">The first layer of the texture</param>
  286. /// <param name="firstLevel">The first level of the texture</param>
  287. /// <param name="slices">The slice count of the texture</param>
  288. /// <param name="levels">The level count of the texture</param>
  289. /// <param name="callback">
  290. /// A function to be called with the base index of the range of handles for the given texture, and the number of handles it covers.
  291. /// This can be called for multiple disjoint ranges, if required.
  292. /// </param>
  293. private void EvaluateRelevantHandles(int firstLayer, int firstLevel, int slices, int levels, HandlesCallbackDelegate callback)
  294. {
  295. int targetLayerHandles = _hasLayerViews ? slices : 1;
  296. int targetLevelHandles = _hasMipViews ? levels : 1;
  297. if (_is3D)
  298. {
  299. // Future mip levels come after all layers of the last mip level. Each mipmap has less layers (depth) than the last.
  300. if (!_hasLayerViews)
  301. {
  302. // When there are no layer views, the mips are at a consistent offset.
  303. callback(firstLevel, targetLevelHandles);
  304. }
  305. else
  306. {
  307. (int levelIndex, int layerCount) = Get3DLevelRange(firstLevel);
  308. if (levels > 1 && slices < _layers)
  309. {
  310. // The given texture only covers some of the depth of multiple mips. (a "depth slice")
  311. // Callback with each mip's range separately.
  312. // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
  313. while (levels-- > 1)
  314. {
  315. callback(firstLayer + levelIndex, slices);
  316. levelIndex += layerCount;
  317. layerCount = Math.Max(layerCount >> 1, 1);
  318. slices = Math.Max(layerCount >> 1, 1);
  319. }
  320. }
  321. else
  322. {
  323. int totalSize = Math.Min(layerCount, slices);
  324. while (levels-- > 1)
  325. {
  326. layerCount = Math.Max(layerCount >> 1, 1);
  327. totalSize += layerCount;
  328. }
  329. callback(firstLayer + levelIndex, totalSize);
  330. }
  331. }
  332. }
  333. else
  334. {
  335. // Future layers come after all mipmaps of the last.
  336. int levelHandles = _hasMipViews ? _levels : 1;
  337. if (slices > 1 && levels < _levels)
  338. {
  339. // The given texture only covers some of the mipmaps of multiple slices. (a "mip slice")
  340. // Callback with each layer's range separately.
  341. // Can assume that the group is fully subdivided (both slices and levels > 1 for storage)
  342. for (int i = 0; i < slices; i++)
  343. {
  344. callback(firstLevel + (firstLayer + i) * levelHandles, targetLevelHandles, true);
  345. }
  346. }
  347. else
  348. {
  349. callback(firstLevel + firstLayer * levelHandles, targetLevelHandles + (targetLayerHandles - 1) * levelHandles);
  350. }
  351. }
  352. }
  353. /// <summary>
  354. /// Get the range of offsets for a given mip level of a 3D texture.
  355. /// </summary>
  356. /// <param name="level">The level to return</param>
  357. /// <returns>Start index and count of offsets for the given level</returns>
  358. private (int Index, int Count) Get3DLevelRange(int level)
  359. {
  360. int index = 0;
  361. int count = _layers; // Depth. Halves with each mip level.
  362. while (level-- > 0)
  363. {
  364. index += count;
  365. count = Math.Max(count >> 1, 1);
  366. }
  367. return (index, count);
  368. }
  369. /// <summary>
  370. /// Get view information for a single tracking handle.
  371. /// </summary>
  372. /// <param name="handleIndex">The index of the handle</param>
  373. /// <returns>The layers and levels that the handle covers, and its index in the offsets array</returns>
  374. private (int BaseLayer, int BaseLevel, int Levels, int Layers, int Index) GetHandleInformation(int handleIndex)
  375. {
  376. int baseLayer;
  377. int baseLevel;
  378. int levels = _hasMipViews ? 1 : _levels;
  379. int layers = _hasLayerViews ? 1 : _layers;
  380. int index;
  381. if (_is3D)
  382. {
  383. if (_hasLayerViews)
  384. {
  385. // NOTE: Will also have mip views, or only one level in storage.
  386. index = handleIndex;
  387. baseLevel = 0;
  388. int layerLevels = _levels;
  389. while (handleIndex >= layerLevels)
  390. {
  391. handleIndex -= layerLevels;
  392. baseLevel++;
  393. layerLevels = Math.Max(layerLevels >> 1, 1);
  394. }
  395. baseLayer = handleIndex;
  396. }
  397. else
  398. {
  399. baseLayer = 0;
  400. baseLevel = handleIndex;
  401. (index, _) = Get3DLevelRange(baseLevel);
  402. }
  403. }
  404. else
  405. {
  406. baseLevel = _hasMipViews ? handleIndex % _levels : 0;
  407. baseLayer = _hasMipViews ? handleIndex / _levels : handleIndex;
  408. index = baseLevel + baseLayer * _levels;
  409. }
  410. return (baseLayer, baseLevel, levels, layers, index);
  411. }
  412. /// <summary>
  413. /// Gets the layer and level for a given view.
  414. /// </summary>
  415. /// <param name="index">The index of the view</param>
  416. /// <returns>The layer and level of the specified view</returns>
  417. private (int BaseLayer, int BaseLevel) GetLayerLevelForView(int index)
  418. {
  419. if (_is3D)
  420. {
  421. int baseLevel = 0;
  422. int layerLevels = _layers;
  423. while (index >= layerLevels)
  424. {
  425. index -= layerLevels;
  426. baseLevel++;
  427. layerLevels = Math.Max(layerLevels >> 1, 1);
  428. }
  429. return (index, baseLevel);
  430. }
  431. else
  432. {
  433. return (index / _levels, index % _levels);
  434. }
  435. }
  436. /// <summary>
  437. /// Find the byte offset of a given texture relative to the storage.
  438. /// </summary>
  439. /// <param name="texture">The texture to locate</param>
  440. /// <returns>The offset of the texture in bytes</returns>
  441. public int FindOffset(Texture texture)
  442. {
  443. return _allOffsets[GetOffsetIndex(texture.FirstLayer, texture.FirstLevel)];
  444. }
  445. /// <summary>
  446. /// Find the offset index of a given layer and level.
  447. /// </summary>
  448. /// <param name="layer">The view layer</param>
  449. /// <param name="level">The view level</param>
  450. /// <returns>The offset index of the given layer and level</returns>
  451. public int GetOffsetIndex(int layer, int level)
  452. {
  453. if (_is3D)
  454. {
  455. return layer + Get3DLevelRange(level).Index;
  456. }
  457. else
  458. {
  459. return level + layer * _levels;
  460. }
  461. }
  462. /// <summary>
  463. /// The action to perform when a memory tracking handle is flipped to dirty.
  464. /// This notifies overlapping textures that the memory needs to be synchronized.
  465. /// </summary>
  466. /// <param name="groupHandle">The handle that a dirty flag was set on</param>
  467. private void DirtyAction(TextureGroupHandle groupHandle)
  468. {
  469. // Notify all textures that belong to this handle.
  470. Storage.SignalGroupDirty();
  471. lock (groupHandle.Overlaps)
  472. {
  473. foreach (Texture overlap in groupHandle.Overlaps)
  474. {
  475. overlap.SignalGroupDirty();
  476. }
  477. }
  478. }
  479. /// <summary>
  480. /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
  481. /// </summary>
  482. /// <param name="address">The start address of the tracked region</param>
  483. /// <param name="size">The size of the tracked region</param>
  484. /// <returns>A CpuRegionHandle covering the given range</returns>
  485. private CpuRegionHandle GenerateHandle(ulong address, ulong size)
  486. {
  487. return _context.PhysicalMemory.BeginTracking(address, size);
  488. }
  489. /// <summary>
  490. /// Generate a TextureGroupHandle covering a specified range of views.
  491. /// </summary>
  492. /// <param name="viewStart">The start view of the handle</param>
  493. /// <param name="views">The number of views to cover</param>
  494. /// <returns>A TextureGroupHandle covering the given views</returns>
  495. private TextureGroupHandle GenerateHandles(int viewStart, int views)
  496. {
  497. int offset = _allOffsets[viewStart];
  498. int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
  499. int size = endOffset - offset;
  500. var result = new List<CpuRegionHandle>();
  501. for (int i = 0; i < TextureRange.Count; i++)
  502. {
  503. MemoryRange item = TextureRange.GetSubRange(i);
  504. int subRangeSize = (int)item.Size;
  505. int sliceStart = Math.Clamp(offset, 0, subRangeSize);
  506. int sliceEnd = Math.Clamp(endOffset, 0, subRangeSize);
  507. if (sliceStart != sliceEnd)
  508. {
  509. result.Add(GenerateHandle(item.Address + (ulong)sliceStart, (ulong)(sliceEnd - sliceStart)));
  510. }
  511. offset -= subRangeSize;
  512. endOffset -= subRangeSize;
  513. if (endOffset <= 0)
  514. {
  515. break;
  516. }
  517. }
  518. (int firstLayer, int firstLevel) = GetLayerLevelForView(viewStart);
  519. if (_hasLayerViews && _hasMipViews)
  520. {
  521. size = _sliceSizes[firstLevel];
  522. }
  523. var groupHandle = new TextureGroupHandle(this, _allOffsets[viewStart], (ulong)size, _views, firstLayer, firstLevel, result.ToArray());
  524. foreach (CpuRegionHandle handle in result)
  525. {
  526. handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
  527. }
  528. return groupHandle;
  529. }
  530. /// <summary>
  531. /// Update the views in this texture group, rebuilding the memory tracking if required.
  532. /// </summary>
  533. /// <param name="views">The views list of the storage texture</param>
  534. public void UpdateViews(List<Texture> views)
  535. {
  536. // This is saved to calculate overlapping views for each handle.
  537. _views = views;
  538. bool layerViews = _hasLayerViews;
  539. bool mipViews = _hasMipViews;
  540. bool regionsRebuilt = false;
  541. if (!(layerViews && mipViews))
  542. {
  543. foreach (Texture view in views)
  544. {
  545. if (view.Info.GetSlices() < _layers)
  546. {
  547. layerViews = true;
  548. }
  549. if (view.Info.Levels < _levels)
  550. {
  551. mipViews = true;
  552. }
  553. }
  554. (layerViews, mipViews) = PropagateGranularity(layerViews, mipViews);
  555. if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
  556. {
  557. _hasLayerViews = layerViews;
  558. _hasMipViews = mipViews;
  559. RecalculateHandleRegions();
  560. regionsRebuilt = true;
  561. }
  562. }
  563. if (!regionsRebuilt)
  564. {
  565. // Must update the overlapping views on all handles, but only if they were not just recreated.
  566. foreach (TextureGroupHandle handle in _handles)
  567. {
  568. handle.RecalculateOverlaps(this, views);
  569. }
  570. }
  571. Storage.SignalGroupDirty();
  572. foreach (Texture texture in views)
  573. {
  574. texture.SignalGroupDirty();
  575. }
  576. }
  577. /// <summary>
  578. /// Inherit handle state from an old set of handles, such as modified and dirty flags.
  579. /// </summary>
  580. /// <param name="oldHandles">The set of handles to inherit state from</param>
  581. /// <param name="handles">The set of handles inheriting the state</param>
  582. private void InheritHandles(TextureGroupHandle[] oldHandles, TextureGroupHandle[] handles)
  583. {
  584. foreach (var group in handles)
  585. {
  586. foreach (var handle in group.Handles)
  587. {
  588. bool dirty = false;
  589. foreach (var oldGroup in oldHandles)
  590. {
  591. if (group.OverlapsWith(oldGroup.Offset, oldGroup.Size))
  592. {
  593. foreach (var oldHandle in oldGroup.Handles)
  594. {
  595. if (handle.OverlapsWith(oldHandle.Address, oldHandle.Size))
  596. {
  597. dirty |= oldHandle.Dirty;
  598. }
  599. }
  600. group.Inherit(oldGroup);
  601. }
  602. }
  603. if (dirty && !handle.Dirty)
  604. {
  605. handle.Reprotect(true);
  606. }
  607. if (group.Modified)
  608. {
  609. handle.RegisterAction((address, size) => FlushAction(group, address, size));
  610. }
  611. }
  612. }
  613. }
  614. /// <summary>
  615. /// Inherit state from another texture group.
  616. /// </summary>
  617. /// <param name="other">The texture group to inherit from</param>
  618. public void Inherit(TextureGroup other)
  619. {
  620. bool layerViews = _hasLayerViews || other._hasLayerViews;
  621. bool mipViews = _hasMipViews || other._hasMipViews;
  622. if (layerViews != _hasLayerViews || mipViews != _hasMipViews)
  623. {
  624. _hasLayerViews = layerViews;
  625. _hasMipViews = mipViews;
  626. RecalculateHandleRegions();
  627. }
  628. InheritHandles(other._handles, _handles);
  629. }
  630. /// <summary>
  631. /// Replace the current handles with the new handles. It is assumed that the new handles start dirty.
  632. /// The dirty flags from the previous handles will be kept.
  633. /// </summary>
  634. /// <param name="handles">The handles to replace the current handles with</param>
  635. private void ReplaceHandles(TextureGroupHandle[] handles)
  636. {
  637. if (_handles != null)
  638. {
  639. // When replacing handles, they should start as non-dirty.
  640. foreach (TextureGroupHandle groupHandle in handles)
  641. {
  642. foreach (CpuRegionHandle handle in groupHandle.Handles)
  643. {
  644. handle.Reprotect();
  645. }
  646. }
  647. InheritHandles(_handles, handles);
  648. foreach (var oldGroup in _handles)
  649. {
  650. foreach (var oldHandle in oldGroup.Handles)
  651. {
  652. oldHandle.Dispose();
  653. }
  654. }
  655. }
  656. _handles = handles;
  657. _loadNeeded = new bool[_handles.Length];
  658. }
  659. /// <summary>
  660. /// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
  661. /// </summary>
  662. private void RecalculateHandleRegions()
  663. {
  664. TextureGroupHandle[] handles;
  665. if (!(_hasMipViews || _hasLayerViews))
  666. {
  667. // Single dirty region.
  668. var cpuRegionHandles = new CpuRegionHandle[TextureRange.Count];
  669. for (int i = 0; i < TextureRange.Count; i++)
  670. {
  671. var currentRange = TextureRange.GetSubRange(i);
  672. cpuRegionHandles[i] = GenerateHandle(currentRange.Address, currentRange.Size);
  673. }
  674. var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, cpuRegionHandles);
  675. foreach (CpuRegionHandle handle in cpuRegionHandles)
  676. {
  677. handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
  678. }
  679. handles = new TextureGroupHandle[] { groupHandle };
  680. }
  681. else
  682. {
  683. // Get views for the host texture.
  684. // It's worth noting that either the texture has layer views or mip views when getting to this point, which simplifies the logic a little.
  685. // Depending on if the texture is 3d, either the mip views imply that layer views are present (2d) or the other way around (3d).
  686. // This is enforced by the way the texture matched as a view, so we don't need to check.
  687. int layerHandles = _hasLayerViews ? _layers : 1;
  688. int levelHandles = _hasMipViews ? _levels : 1;
  689. int handleIndex = 0;
  690. if (_is3D)
  691. {
  692. var handlesList = new List<TextureGroupHandle>();
  693. for (int i = 0; i < levelHandles; i++)
  694. {
  695. for (int j = 0; j < layerHandles; j++)
  696. {
  697. (int viewStart, int views) = Get3DLevelRange(i);
  698. viewStart += j;
  699. views = _hasLayerViews ? 1 : views; // A layer view is also a mip view.
  700. handlesList.Add(GenerateHandles(viewStart, views));
  701. }
  702. layerHandles = Math.Max(1, layerHandles >> 1);
  703. }
  704. handles = handlesList.ToArray();
  705. }
  706. else
  707. {
  708. handles = new TextureGroupHandle[layerHandles * levelHandles];
  709. for (int i = 0; i < layerHandles; i++)
  710. {
  711. for (int j = 0; j < levelHandles; j++)
  712. {
  713. int viewStart = j + i * _levels;
  714. int views = _hasMipViews ? 1 : _levels; // A mip view is also a layer view.
  715. handles[handleIndex++] = GenerateHandles(viewStart, views);
  716. }
  717. }
  718. }
  719. }
  720. ReplaceHandles(handles);
  721. }
  722. /// <summary>
  723. /// Ensure that there is a handle for each potential texture view. Required for copy dependencies to work.
  724. /// </summary>
  725. private void EnsureFullSubdivision()
  726. {
  727. if (!(_hasLayerViews && _hasMipViews))
  728. {
  729. _hasLayerViews = true;
  730. _hasMipViews = true;
  731. RecalculateHandleRegions();
  732. }
  733. }
  734. /// <summary>
  735. /// Create a copy dependency between this texture group, and a texture at a given layer/level offset.
  736. /// </summary>
  737. /// <param name="other">The view compatible texture to create a dependency to</param>
  738. /// <param name="firstLayer">The base layer of the given texture relative to the storage</param>
  739. /// <param name="firstLevel">The base level of the given texture relative to the storage</param>
  740. /// <param name="copyTo">True if this texture is first copied to the given one, false for the opposite direction</param>
  741. public void CreateCopyDependency(Texture other, int firstLayer, int firstLevel, bool copyTo)
  742. {
  743. TextureGroup otherGroup = other.Group;
  744. EnsureFullSubdivision();
  745. otherGroup.EnsureFullSubdivision();
  746. // Get the location of each texture within its storage, so we can find the handles to apply the dependency to.
  747. // This can consist of multiple disjoint regions, for example if this is a mip slice of an array texture.
  748. var targetRange = new List<(int BaseHandle, int RegionCount)>();
  749. var otherRange = new List<(int BaseHandle, int RegionCount)>();
  750. EvaluateRelevantHandles(firstLayer, firstLevel, other.Info.GetSlices(), other.Info.Levels, (baseHandle, regionCount, split) => targetRange.Add((baseHandle, regionCount)));
  751. otherGroup.EvaluateRelevantHandles(other, (baseHandle, regionCount, split) => otherRange.Add((baseHandle, regionCount)));
  752. int targetIndex = 0;
  753. int otherIndex = 0;
  754. (int Handle, int RegionCount) targetRegion = (0, 0);
  755. (int Handle, int RegionCount) otherRegion = (0, 0);
  756. while (true)
  757. {
  758. if (targetRegion.RegionCount == 0)
  759. {
  760. if (targetIndex >= targetRange.Count)
  761. {
  762. break;
  763. }
  764. targetRegion = targetRange[targetIndex++];
  765. }
  766. if (otherRegion.RegionCount == 0)
  767. {
  768. if (otherIndex >= otherRange.Count)
  769. {
  770. break;
  771. }
  772. otherRegion = otherRange[otherIndex++];
  773. }
  774. TextureGroupHandle handle = _handles[targetRegion.Handle++];
  775. TextureGroupHandle otherHandle = other.Group._handles[otherRegion.Handle++];
  776. targetRegion.RegionCount--;
  777. otherRegion.RegionCount--;
  778. handle.CreateCopyDependency(otherHandle, copyTo);
  779. // If "copyTo" is true, this texture must copy to the other.
  780. // Otherwise, it must copy to this texture.
  781. if (copyTo)
  782. {
  783. otherHandle.Copy(handle);
  784. }
  785. else
  786. {
  787. handle.Copy(otherHandle);
  788. }
  789. }
  790. }
  791. /// <summary>
  792. /// A flush has been requested on a tracked region. Find an appropriate view to flush.
  793. /// </summary>
  794. /// <param name="handle">The handle this flush action is for</param>
  795. /// <param name="address">The address of the flushing memory access</param>
  796. /// <param name="size">The size of the flushing memory access</param>
  797. public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
  798. {
  799. Storage.ExternalFlush(address, size);
  800. lock (handle.Overlaps)
  801. {
  802. foreach (Texture overlap in handle.Overlaps)
  803. {
  804. overlap.ExternalFlush(address, size);
  805. }
  806. }
  807. handle.Modified = false;
  808. }
  809. /// <summary>
  810. /// Dispose this texture group, disposing all related memory tracking handles.
  811. /// </summary>
  812. public void Dispose()
  813. {
  814. foreach (TextureGroupHandle group in _handles)
  815. {
  816. group.Dispose();
  817. }
  818. }
  819. }
  820. }