Window.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. using Ryujinx.Common.Configuration;
  2. using Ryujinx.Graphics.GAL;
  3. using Ryujinx.Graphics.Vulkan.Effects;
  4. using Silk.NET.Vulkan;
  5. using Silk.NET.Vulkan.Extensions.KHR;
  6. using System;
  7. using System.Linq;
  8. using VkFormat = Silk.NET.Vulkan.Format;
  9. namespace Ryujinx.Graphics.Vulkan
  10. {
  11. class Window : WindowBase, IDisposable
  12. {
  13. private const int SurfaceWidth = 1280;
  14. private const int SurfaceHeight = 720;
  15. private readonly VulkanRenderer _gd;
  16. private readonly SurfaceKHR _surface;
  17. private readonly PhysicalDevice _physicalDevice;
  18. private readonly Device _device;
  19. private SwapchainKHR _swapchain;
  20. private Image[] _swapchainImages;
  21. private TextureView[] _swapchainImageViews;
  22. private Semaphore[] _imageAvailableSemaphores;
  23. private Semaphore[] _renderFinishedSemaphores;
  24. private int _frameIndex;
  25. private int _width;
  26. private int _height;
  27. private VSyncMode _vSyncMode;
  28. private bool _swapchainIsDirty;
  29. private VkFormat _format;
  30. private AntiAliasing _currentAntiAliasing;
  31. private bool _updateEffect;
  32. private IPostProcessingEffect _effect;
  33. private IScalingFilter _scalingFilter;
  34. private bool _isLinear;
  35. private float _scalingFilterLevel;
  36. private bool _updateScalingFilter;
  37. private ScalingFilter _currentScalingFilter;
  38. private bool _colorSpacePassthroughEnabled;
  39. public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
  40. {
  41. _gd = gd;
  42. _physicalDevice = physicalDevice;
  43. _device = device;
  44. _surface = surface;
  45. CreateSwapchain();
  46. }
  47. private void RecreateSwapchain()
  48. {
  49. var oldSwapchain = _swapchain;
  50. _swapchainIsDirty = false;
  51. for (int i = 0; i < _swapchainImageViews.Length; i++)
  52. {
  53. _swapchainImageViews[i].Dispose();
  54. }
  55. // Destroy old Swapchain.
  56. _gd.Api.DeviceWaitIdle(_device);
  57. unsafe
  58. {
  59. for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
  60. {
  61. _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
  62. }
  63. for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
  64. {
  65. _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
  66. }
  67. }
  68. _gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span<AllocationCallbacks>.Empty);
  69. CreateSwapchain();
  70. }
  71. private unsafe void CreateSwapchain()
  72. {
  73. _gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities);
  74. uint surfaceFormatsCount;
  75. _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null);
  76. var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
  77. fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
  78. {
  79. _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats);
  80. }
  81. uint presentModesCount;
  82. _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null);
  83. var presentModes = new PresentModeKHR[presentModesCount];
  84. fixed (PresentModeKHR* pPresentModes = presentModes)
  85. {
  86. _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes);
  87. }
  88. uint imageCount = capabilities.MinImageCount + 1;
  89. if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
  90. {
  91. imageCount = capabilities.MaxImageCount;
  92. }
  93. var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
  94. var extent = ChooseSwapExtent(capabilities);
  95. _width = (int)extent.Width;
  96. _height = (int)extent.Height;
  97. _format = surfaceFormat.Format;
  98. var oldSwapchain = _swapchain;
  99. var swapchainCreateInfo = new SwapchainCreateInfoKHR
  100. {
  101. SType = StructureType.SwapchainCreateInfoKhr,
  102. Surface = _surface,
  103. MinImageCount = imageCount,
  104. ImageFormat = surfaceFormat.Format,
  105. ImageColorSpace = surfaceFormat.ColorSpace,
  106. ImageExtent = extent,
  107. ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.StorageBit,
  108. ImageSharingMode = SharingMode.Exclusive,
  109. ImageArrayLayers = 1,
  110. PreTransform = capabilities.CurrentTransform,
  111. CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
  112. PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
  113. Clipped = true,
  114. };
  115. var textureCreateInfo = new TextureCreateInfo(
  116. _width,
  117. _height,
  118. 1,
  119. 1,
  120. 1,
  121. 1,
  122. 1,
  123. 1,
  124. FormatTable.GetFormat(surfaceFormat.Format),
  125. DepthStencilMode.Depth,
  126. Target.Texture2D,
  127. SwizzleComponent.Red,
  128. SwizzleComponent.Green,
  129. SwizzleComponent.Blue,
  130. SwizzleComponent.Alpha);
  131. _gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
  132. _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
  133. _swapchainImages = new Image[imageCount];
  134. fixed (Image* pSwapchainImages = _swapchainImages)
  135. {
  136. _gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
  137. }
  138. _swapchainImageViews = new TextureView[imageCount];
  139. for (int i = 0; i < _swapchainImageViews.Length; i++)
  140. {
  141. _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
  142. }
  143. var semaphoreCreateInfo = new SemaphoreCreateInfo
  144. {
  145. SType = StructureType.SemaphoreCreateInfo,
  146. };
  147. _imageAvailableSemaphores = new Semaphore[imageCount];
  148. for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
  149. {
  150. _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
  151. }
  152. _renderFinishedSemaphores = new Semaphore[imageCount];
  153. for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
  154. {
  155. _gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
  156. }
  157. }
  158. private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info)
  159. {
  160. var componentMapping = new ComponentMapping(
  161. ComponentSwizzle.R,
  162. ComponentSwizzle.G,
  163. ComponentSwizzle.B,
  164. ComponentSwizzle.A);
  165. var aspectFlags = ImageAspectFlags.ColorBit;
  166. var subresourceRange = new ImageSubresourceRange(aspectFlags, 0, 1, 0, 1);
  167. var imageCreateInfo = new ImageViewCreateInfo
  168. {
  169. SType = StructureType.ImageViewCreateInfo,
  170. Image = swapchainImage,
  171. ViewType = ImageViewType.Type2D,
  172. Format = format,
  173. Components = componentMapping,
  174. SubresourceRange = subresourceRange,
  175. };
  176. _gd.Api.CreateImageView(_device, in imageCreateInfo, null, out var imageView).ThrowOnError();
  177. return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
  178. }
  179. private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
  180. {
  181. if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
  182. {
  183. return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
  184. }
  185. var formatToReturn = availableFormats[0];
  186. if (colorSpacePassthroughEnabled)
  187. {
  188. foreach (var format in availableFormats)
  189. {
  190. if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.SpacePassThroughExt)
  191. {
  192. formatToReturn = format;
  193. break;
  194. }
  195. else if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
  196. {
  197. formatToReturn = format;
  198. }
  199. }
  200. }
  201. else
  202. {
  203. foreach (var format in availableFormats)
  204. {
  205. if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
  206. {
  207. formatToReturn = format;
  208. break;
  209. }
  210. }
  211. }
  212. return formatToReturn;
  213. }
  214. private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags)
  215. {
  216. if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr))
  217. {
  218. return CompositeAlphaFlagsKHR.OpaqueBitKhr;
  219. }
  220. else if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr))
  221. {
  222. return CompositeAlphaFlagsKHR.PreMultipliedBitKhr;
  223. }
  224. else
  225. {
  226. return CompositeAlphaFlagsKHR.InheritBitKhr;
  227. }
  228. }
  229. private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
  230. {
  231. if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
  232. {
  233. return PresentModeKHR.ImmediateKhr;
  234. }
  235. else if (availablePresentModes.Contains(PresentModeKHR.MailboxKhr))
  236. {
  237. return PresentModeKHR.MailboxKhr;
  238. }
  239. else
  240. {
  241. return PresentModeKHR.FifoKhr;
  242. }
  243. }
  244. public static Extent2D ChooseSwapExtent(SurfaceCapabilitiesKHR capabilities)
  245. {
  246. if (capabilities.CurrentExtent.Width != uint.MaxValue)
  247. {
  248. return capabilities.CurrentExtent;
  249. }
  250. uint width = Math.Max(capabilities.MinImageExtent.Width, Math.Min(capabilities.MaxImageExtent.Width, SurfaceWidth));
  251. uint height = Math.Max(capabilities.MinImageExtent.Height, Math.Min(capabilities.MaxImageExtent.Height, SurfaceHeight));
  252. return new Extent2D(width, height);
  253. }
  254. public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
  255. {
  256. _gd.PipelineInternal.AutoFlush.Present();
  257. uint nextImage = 0;
  258. int semaphoreIndex = _frameIndex++ % _imageAvailableSemaphores.Length;
  259. while (true)
  260. {
  261. var acquireResult = _gd.SwapchainApi.AcquireNextImage(
  262. _device,
  263. _swapchain,
  264. ulong.MaxValue,
  265. _imageAvailableSemaphores[semaphoreIndex],
  266. new Fence(),
  267. ref nextImage);
  268. if (acquireResult == Result.ErrorOutOfDateKhr ||
  269. acquireResult == Result.SuboptimalKhr ||
  270. _swapchainIsDirty)
  271. {
  272. RecreateSwapchain();
  273. semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
  274. }
  275. else
  276. {
  277. acquireResult.ThrowOnError();
  278. break;
  279. }
  280. }
  281. var swapchainImage = _swapchainImages[nextImage];
  282. _gd.FlushAllCommands();
  283. var cbs = _gd.CommandBufferPool.Rent();
  284. Transition(
  285. cbs.CommandBuffer,
  286. swapchainImage,
  287. 0,
  288. AccessFlags.TransferWriteBit,
  289. ImageLayout.Undefined,
  290. ImageLayout.General);
  291. var view = (TextureView)texture;
  292. UpdateEffect();
  293. if (_effect != null)
  294. {
  295. view = _effect.Run(view, cbs, _width, _height);
  296. }
  297. int srcX0, srcX1, srcY0, srcY1;
  298. if (crop.Left == 0 && crop.Right == 0)
  299. {
  300. srcX0 = 0;
  301. srcX1 = view.Width;
  302. }
  303. else
  304. {
  305. srcX0 = crop.Left;
  306. srcX1 = crop.Right;
  307. }
  308. if (crop.Top == 0 && crop.Bottom == 0)
  309. {
  310. srcY0 = 0;
  311. srcY1 = view.Height;
  312. }
  313. else
  314. {
  315. srcY0 = crop.Top;
  316. srcY1 = crop.Bottom;
  317. }
  318. if (ScreenCaptureRequested)
  319. {
  320. if (_effect != null)
  321. {
  322. _gd.CommandBufferPool.Return(
  323. cbs,
  324. null,
  325. stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
  326. null);
  327. _gd.FlushAllCommands();
  328. cbs.GetFence().Wait();
  329. cbs = _gd.CommandBufferPool.Rent();
  330. }
  331. CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
  332. ScreenCaptureRequested = false;
  333. }
  334. float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
  335. float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
  336. int dstWidth = (int)(_width * ratioX);
  337. int dstHeight = (int)(_height * ratioY);
  338. int dstPaddingX = (_width - dstWidth) / 2;
  339. int dstPaddingY = (_height - dstHeight) / 2;
  340. int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
  341. int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
  342. int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
  343. int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
  344. if (_scalingFilter != null)
  345. {
  346. _scalingFilter.Run(
  347. view,
  348. cbs,
  349. _swapchainImageViews[nextImage].GetImageViewForAttachment(),
  350. _format,
  351. _width,
  352. _height,
  353. new Extents2D(srcX0, srcY0, srcX1, srcY1),
  354. new Extents2D(dstX0, dstY0, dstX1, dstY1)
  355. );
  356. }
  357. else
  358. {
  359. _gd.HelperShader.BlitColor(
  360. _gd,
  361. cbs,
  362. view,
  363. _swapchainImageViews[nextImage],
  364. new Extents2D(srcX0, srcY0, srcX1, srcY1),
  365. new Extents2D(dstX0, dstY1, dstX1, dstY0),
  366. _isLinear,
  367. true);
  368. }
  369. Transition(
  370. cbs.CommandBuffer,
  371. swapchainImage,
  372. 0,
  373. 0,
  374. ImageLayout.General,
  375. ImageLayout.PresentSrcKhr);
  376. _gd.CommandBufferPool.Return(
  377. cbs,
  378. stackalloc[] { _imageAvailableSemaphores[semaphoreIndex] },
  379. stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
  380. stackalloc[] { _renderFinishedSemaphores[semaphoreIndex] });
  381. // TODO: Present queue.
  382. var semaphore = _renderFinishedSemaphores[semaphoreIndex];
  383. var swapchain = _swapchain;
  384. Result result;
  385. var presentInfo = new PresentInfoKHR
  386. {
  387. SType = StructureType.PresentInfoKhr,
  388. WaitSemaphoreCount = 1,
  389. PWaitSemaphores = &semaphore,
  390. SwapchainCount = 1,
  391. PSwapchains = &swapchain,
  392. PImageIndices = &nextImage,
  393. PResults = &result,
  394. };
  395. lock (_gd.QueueLock)
  396. {
  397. _gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
  398. }
  399. }
  400. public override void SetAntiAliasing(AntiAliasing effect)
  401. {
  402. if (_currentAntiAliasing == effect && _effect != null)
  403. {
  404. return;
  405. }
  406. _currentAntiAliasing = effect;
  407. _updateEffect = true;
  408. }
  409. public override void SetScalingFilter(ScalingFilter type)
  410. {
  411. if (_currentScalingFilter == type && _effect != null)
  412. {
  413. return;
  414. }
  415. _currentScalingFilter = type;
  416. _updateScalingFilter = true;
  417. }
  418. public override void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled)
  419. {
  420. _colorSpacePassthroughEnabled = colorSpacePassthroughEnabled;
  421. _swapchainIsDirty = true;
  422. }
  423. private void UpdateEffect()
  424. {
  425. if (_updateEffect)
  426. {
  427. _updateEffect = false;
  428. switch (_currentAntiAliasing)
  429. {
  430. case AntiAliasing.Fxaa:
  431. _effect?.Dispose();
  432. _effect = new FxaaPostProcessingEffect(_gd, _device);
  433. break;
  434. case AntiAliasing.None:
  435. _effect?.Dispose();
  436. _effect = null;
  437. break;
  438. case AntiAliasing.SmaaLow:
  439. case AntiAliasing.SmaaMedium:
  440. case AntiAliasing.SmaaHigh:
  441. case AntiAliasing.SmaaUltra:
  442. var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
  443. if (_effect is SmaaPostProcessingEffect smaa)
  444. {
  445. smaa.Quality = quality;
  446. }
  447. else
  448. {
  449. _effect?.Dispose();
  450. _effect = new SmaaPostProcessingEffect(_gd, _device, quality);
  451. }
  452. break;
  453. }
  454. }
  455. if (_updateScalingFilter)
  456. {
  457. _updateScalingFilter = false;
  458. switch (_currentScalingFilter)
  459. {
  460. case ScalingFilter.Bilinear:
  461. case ScalingFilter.Nearest:
  462. _scalingFilter?.Dispose();
  463. _scalingFilter = null;
  464. _isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
  465. break;
  466. case ScalingFilter.Fsr:
  467. if (_scalingFilter is not FsrScalingFilter)
  468. {
  469. _scalingFilter?.Dispose();
  470. _scalingFilter = new FsrScalingFilter(_gd, _device);
  471. }
  472. _scalingFilter.Level = _scalingFilterLevel;
  473. break;
  474. case ScalingFilter.Area:
  475. if (_scalingFilter is not AreaScalingFilter)
  476. {
  477. _scalingFilter?.Dispose();
  478. _scalingFilter = new AreaScalingFilter(_gd, _device);
  479. }
  480. break;
  481. }
  482. }
  483. }
  484. public override void SetScalingFilterLevel(float level)
  485. {
  486. _scalingFilterLevel = level;
  487. _updateScalingFilter = true;
  488. }
  489. private unsafe void Transition(
  490. CommandBuffer commandBuffer,
  491. Image image,
  492. AccessFlags srcAccess,
  493. AccessFlags dstAccess,
  494. ImageLayout srcLayout,
  495. ImageLayout dstLayout)
  496. {
  497. var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, 1, 0, 1);
  498. var barrier = new ImageMemoryBarrier
  499. {
  500. SType = StructureType.ImageMemoryBarrier,
  501. SrcAccessMask = srcAccess,
  502. DstAccessMask = dstAccess,
  503. OldLayout = srcLayout,
  504. NewLayout = dstLayout,
  505. SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
  506. DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
  507. Image = image,
  508. SubresourceRange = subresourceRange,
  509. };
  510. _gd.Api.CmdPipelineBarrier(
  511. commandBuffer,
  512. PipelineStageFlags.TopOfPipeBit,
  513. PipelineStageFlags.AllCommandsBit,
  514. 0,
  515. 0,
  516. null,
  517. 0,
  518. null,
  519. 1,
  520. in barrier);
  521. }
  522. private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
  523. {
  524. byte[] bitmap = texture.GetData(x, y, width, height);
  525. _gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
  526. }
  527. public override void SetSize(int width, int height)
  528. {
  529. // We don't need to use width and height as we can get the size from the surface.
  530. _swapchainIsDirty = true;
  531. }
  532. public override void ChangeVSyncMode(VSyncMode vSyncMode)
  533. {
  534. _vSyncMode = vSyncMode;
  535. //present mode may change, so mark the swapchain for recreation
  536. _swapchainIsDirty = true;
  537. }
  538. protected virtual void Dispose(bool disposing)
  539. {
  540. if (disposing)
  541. {
  542. unsafe
  543. {
  544. for (int i = 0; i < _swapchainImageViews.Length; i++)
  545. {
  546. _swapchainImageViews[i].Dispose();
  547. }
  548. for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
  549. {
  550. _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
  551. }
  552. for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
  553. {
  554. _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
  555. }
  556. _gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
  557. }
  558. _effect?.Dispose();
  559. _scalingFilter?.Dispose();
  560. }
  561. }
  562. public override void Dispose()
  563. {
  564. Dispose(true);
  565. }
  566. }
  567. }