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