Window.cs 23 KB

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