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 }