mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-21 02:01:14 +02:00
968 lines
34 KiB
C#
968 lines
34 KiB
C#
using Ryujinx.Common;
|
||
using Ryujinx.Graphics.GAL;
|
||
using Ryujinx.Graphics.Vulkan.Effects;
|
||
using Silk.NET.Vulkan;
|
||
using Silk.NET.Vulkan.Extensions.KHR;
|
||
using System;
|
||
using VkFormat = Silk.NET.Vulkan.Format;
|
||
using System.Linq;
|
||
|
||
namespace Ryujinx.Graphics.Vulkan
|
||
{
|
||
class Window : WindowBase, IDisposable
|
||
{
|
||
private const int SurfaceWidth = 1280;
|
||
private const int SurfaceHeight = 720;
|
||
|
||
private readonly VulkanRenderer _gd;
|
||
private readonly PhysicalDevice _physicalDevice;
|
||
private readonly Device _device;
|
||
private SurfaceKHR _surface;
|
||
private SwapchainKHR _swapchain;
|
||
|
||
private Image[] _swapchainImages;
|
||
private TextureView[] _swapchainImageViews;
|
||
|
||
private Semaphore[] _imageAvailableSemaphores;
|
||
private Semaphore[] _renderFinishedSemaphores;
|
||
|
||
private int _frameIndex;
|
||
|
||
private int _width;
|
||
private int _height;
|
||
private VSyncMode _vSyncMode;
|
||
private bool _swapchainIsDirty;
|
||
private VkFormat _format;
|
||
private AntiAliasing _currentAntiAliasing;
|
||
private bool _updateEffect;
|
||
private IPostProcessingEffect _effect;
|
||
private IScalingFilter _scalingFilter;
|
||
private bool _isLinear;
|
||
private float _scalingFilterLevel;
|
||
private bool _updateScalingFilter;
|
||
private ScalingFilter _currentScalingFilter;
|
||
private bool _colorSpacePassthroughEnabled;
|
||
|
||
// Gateway for all vk*Surface* queries
|
||
private volatile bool _allowSurfaceQueries = true;
|
||
|
||
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
|
||
{
|
||
_gd = gd;
|
||
_physicalDevice = physicalDevice;
|
||
_device = device;
|
||
_surface = surface;
|
||
|
||
if (_gd.PresentAllowed && _surface.Handle != 0)
|
||
{
|
||
CreateSwapchain();
|
||
}
|
||
else
|
||
{
|
||
_swapchainIsDirty = true;
|
||
}
|
||
}
|
||
|
||
public void SetSurfaceQueryAllowed(bool allowed) => _allowSurfaceQueries = allowed;
|
||
private bool CanQuerySurface() => _allowSurfaceQueries && _gd.PresentAllowed && _surface.Handle != 0;
|
||
|
||
private unsafe bool TryGetSurfaceCapabilities(out SurfaceCapabilitiesKHR caps)
|
||
{
|
||
caps = default;
|
||
if (!CanQuerySurface()) return false;
|
||
Result res = _gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out caps);
|
||
return res == Result.Success;
|
||
}
|
||
|
||
private unsafe bool TryGetSurfaceFormats(out SurfaceFormatKHR[] formats)
|
||
{
|
||
formats = Array.Empty<SurfaceFormatKHR>();
|
||
if (!CanQuerySurface()) return false;
|
||
|
||
uint count = 0;
|
||
Result res = _gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &count, null);
|
||
if (res != Result.Success || count == 0) return false;
|
||
|
||
formats = new SurfaceFormatKHR[count];
|
||
fixed (SurfaceFormatKHR* p = formats)
|
||
{
|
||
if (_gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &count, p) != Result.Success)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private unsafe bool TryGetPresentModes(out PresentModeKHR[] modes)
|
||
{
|
||
modes = Array.Empty<PresentModeKHR>();
|
||
if (!CanQuerySurface()) return false;
|
||
|
||
uint count = 0;
|
||
Result res = _gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &count, null);
|
||
if (res != Result.Success || count == 0) return false;
|
||
|
||
modes = new PresentModeKHR[count];
|
||
fixed (PresentModeKHR* p = modes)
|
||
{
|
||
if (_gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &count, p) != Result.Success)
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private void RecreateSwapchain()
|
||
{
|
||
if (!_gd.PresentAllowed || _surface.Handle == 0 || !CanQuerySurface())
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
lock (_gd.SurfaceLock)
|
||
{
|
||
SwapchainKHR oldSwapchain = _swapchain;
|
||
_swapchainIsDirty = false;
|
||
|
||
if (_swapchainImageViews != null)
|
||
{
|
||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||
{
|
||
_swapchainImageViews[i]?.Dispose();
|
||
}
|
||
}
|
||
|
||
_gd.Api.DeviceWaitIdle(_device);
|
||
|
||
unsafe
|
||
{
|
||
if (_imageAvailableSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||
{
|
||
if (_imageAvailableSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_renderFinishedSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||
{
|
||
if (_renderFinishedSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (oldSwapchain.Handle != 0)
|
||
{
|
||
_gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span<AllocationCallbacks>.Empty);
|
||
}
|
||
|
||
CreateSwapchain();
|
||
}
|
||
}
|
||
|
||
internal void SetSurface(SurfaceKHR surface)
|
||
{
|
||
lock (_gd.SurfaceLock)
|
||
{
|
||
_surface = surface;
|
||
|
||
if (!_gd.PresentAllowed || _surface.Handle == 0)
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
SetSurfaceQueryAllowed(true);
|
||
RecreateSwapchain();
|
||
}
|
||
}
|
||
|
||
private unsafe void CreateSwapchain()
|
||
{
|
||
if (!_gd.PresentAllowed || _surface.Handle == 0 || !CanQuerySurface())
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
lock (_gd.SurfaceLock)
|
||
{
|
||
if (!TryGetSurfaceCapabilities(out SurfaceCapabilitiesKHR capabilities))
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
if (!TryGetSurfaceFormats(out SurfaceFormatKHR[] surfaceFormats))
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
if (!TryGetPresentModes(out PresentModeKHR[] presentModes))
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
uint imageCount = capabilities.MinImageCount + 1;
|
||
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
|
||
{
|
||
imageCount = capabilities.MaxImageCount;
|
||
}
|
||
|
||
SurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
||
Extent2D extent = ChooseSwapExtent(capabilities);
|
||
|
||
// Guard against 0x0 extent immediately after resume
|
||
if (extent.Width == 0 || extent.Height == 0)
|
||
{
|
||
_swapchainIsDirty = true;
|
||
return;
|
||
}
|
||
|
||
_width = (int)extent.Width;
|
||
_height = (int)extent.Height;
|
||
_format = surfaceFormat.Format;
|
||
|
||
SwapchainKHR oldSwapchain = _swapchain;
|
||
|
||
CurrentTransform = capabilities.CurrentTransform;
|
||
|
||
ImageUsageFlags usage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit;
|
||
if (!PlatformInfo.IsBionic)
|
||
{
|
||
usage |= ImageUsageFlags.StorageBit; // Only desktop allows storage for swapchain
|
||
}
|
||
|
||
// On Android: Identity; otherwise, the CurrentTransform recommended by the driver
|
||
SurfaceTransformFlagsKHR preTransform = PlatformInfo.IsBionic
|
||
? SurfaceTransformFlagsKHR.IdentityBitKhr
|
||
: capabilities.CurrentTransform;
|
||
|
||
SwapchainCreateInfoKHR swapchainCreateInfo = new()
|
||
{
|
||
SType = StructureType.SwapchainCreateInfoKhr,
|
||
Surface = _surface,
|
||
MinImageCount = imageCount,
|
||
ImageFormat = surfaceFormat.Format,
|
||
ImageColorSpace = surfaceFormat.ColorSpace,
|
||
ImageExtent = extent,
|
||
ImageUsage = usage,
|
||
ImageSharingMode = SharingMode.Exclusive,
|
||
ImageArrayLayers = 1,
|
||
PreTransform = preTransform,
|
||
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
||
PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
|
||
Clipped = true,
|
||
};
|
||
|
||
TextureCreateInfo textureCreateInfo = new(
|
||
_width,
|
||
_height,
|
||
1,
|
||
1,
|
||
1,
|
||
1,
|
||
1,
|
||
1,
|
||
FormatTable.GetFormat(surfaceFormat.Format),
|
||
DepthStencilMode.Depth,
|
||
Target.Texture2D,
|
||
SwizzleComponent.Red,
|
||
SwizzleComponent.Green,
|
||
SwizzleComponent.Blue,
|
||
SwizzleComponent.Alpha);
|
||
|
||
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||
|
||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
||
|
||
_swapchainImages = new Image[imageCount];
|
||
|
||
fixed (Image* pSwapchainImages = _swapchainImages)
|
||
{
|
||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
|
||
}
|
||
|
||
_swapchainImageViews = new TextureView[imageCount];
|
||
|
||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||
{
|
||
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
|
||
}
|
||
|
||
SemaphoreCreateInfo semaphoreCreateInfo = new()
|
||
{
|
||
SType = StructureType.SemaphoreCreateInfo,
|
||
};
|
||
|
||
_imageAvailableSemaphores = new Semaphore[imageCount];
|
||
|
||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||
{
|
||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
|
||
}
|
||
|
||
_renderFinishedSemaphores = new Semaphore[imageCount];
|
||
|
||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||
{
|
||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
|
||
}
|
||
}
|
||
}
|
||
|
||
private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info)
|
||
{
|
||
ComponentMapping componentMapping = new(
|
||
ComponentSwizzle.R,
|
||
ComponentSwizzle.G,
|
||
ComponentSwizzle.B,
|
||
ComponentSwizzle.A);
|
||
|
||
ImageAspectFlags aspectFlags = ImageAspectFlags.ColorBit;
|
||
|
||
ImageSubresourceRange subresourceRange = new(aspectFlags, 0, 1, 0, 1);
|
||
|
||
ImageViewCreateInfo imageCreateInfo = new()
|
||
{
|
||
SType = StructureType.ImageViewCreateInfo,
|
||
Image = swapchainImage,
|
||
ViewType = ImageViewType.Type2D,
|
||
Format = format,
|
||
Components = componentMapping,
|
||
SubresourceRange = subresourceRange,
|
||
};
|
||
|
||
_gd.Api.CreateImageView(_device, in imageCreateInfo, null, out ImageView imageView).ThrowOnError();
|
||
|
||
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
|
||
}
|
||
|
||
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
|
||
{
|
||
if (availableFormats == null || availableFormats.Length == 0)
|
||
{
|
||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, 0);
|
||
}
|
||
|
||
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
|
||
{
|
||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, availableFormats[0].ColorSpace);
|
||
}
|
||
|
||
foreach (SurfaceFormatKHR f in availableFormats)
|
||
{
|
||
if (f.Format == VkFormat.B8G8R8A8Unorm)
|
||
{
|
||
return f;
|
||
}
|
||
}
|
||
|
||
return availableFormats[0];
|
||
}
|
||
|
||
private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags)
|
||
{
|
||
if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr))
|
||
{
|
||
return CompositeAlphaFlagsKHR.OpaqueBitKhr;
|
||
}
|
||
else
|
||
{
|
||
return supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr)
|
||
? CompositeAlphaFlagsKHR.PreMultipliedBitKhr
|
||
: CompositeAlphaFlagsKHR.InheritBitKhr;
|
||
}
|
||
}
|
||
|
||
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
|
||
{
|
||
if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
|
||
{
|
||
return PresentModeKHR.ImmediateKhr;
|
||
}
|
||
else
|
||
{
|
||
return availablePresentModes.Contains(PresentModeKHR.MailboxKhr) ? PresentModeKHR.MailboxKhr : PresentModeKHR.FifoKhr;
|
||
}
|
||
}
|
||
|
||
public static Extent2D ChooseSwapExtent(SurfaceCapabilitiesKHR capabilities)
|
||
{
|
||
if (capabilities.CurrentExtent.Width != uint.MaxValue)
|
||
{
|
||
return capabilities.CurrentExtent;
|
||
}
|
||
|
||
uint width = Math.Max(capabilities.MinImageExtent.Width, Math.Min(capabilities.MaxImageExtent.Width, SurfaceWidth));
|
||
uint height = Math.Max(capabilities.MinImageExtent.Height, Math.Min(capabilities.MaxImageExtent.Height, SurfaceHeight));
|
||
|
||
return new Extent2D(width, height);
|
||
}
|
||
|
||
public unsafe override void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback)
|
||
{
|
||
// If Surface is already new, but queries are still blocked → unblock them.
|
||
if (!_allowSurfaceQueries && _surface.Handle != 0)
|
||
{
|
||
_allowSurfaceQueries = true;
|
||
}
|
||
|
||
if (!_gd.PresentAllowed || _surface.Handle == 0)
|
||
{
|
||
swapBuffersCallback?.Invoke();
|
||
return;
|
||
}
|
||
|
||
// If the size is not yet available, rebuild the swapchain later
|
||
if (_width <= 0 || _height <= 0)
|
||
{
|
||
RecreateSwapchain();
|
||
swapBuffersCallback?.Invoke();
|
||
return;
|
||
}
|
||
|
||
// Lazy Initialization/Recovery
|
||
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null || _renderFinishedSemaphores == null)
|
||
{
|
||
try { CreateSwapchain(); } catch { /* try again next frame */ }
|
||
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null || _renderFinishedSemaphores == null)
|
||
{
|
||
swapBuffersCallback?.Invoke();
|
||
return;
|
||
}
|
||
}
|
||
|
||
_gd.PipelineInternal.AutoFlush.Present();
|
||
|
||
uint nextImage = 0;
|
||
int semaphoreIndex = _frameIndex++ % _imageAvailableSemaphores.Length;
|
||
|
||
while (true)
|
||
{
|
||
Result acquireResult = _gd.SwapchainApi.AcquireNextImage(
|
||
_device,
|
||
_swapchain,
|
||
ulong.MaxValue,
|
||
_imageAvailableSemaphores[semaphoreIndex],
|
||
new Fence(),
|
||
ref nextImage);
|
||
|
||
if (acquireResult == Result.ErrorOutOfDateKhr ||
|
||
acquireResult == Result.SuboptimalKhr ||
|
||
_swapchainIsDirty)
|
||
{
|
||
RecreateSwapchain();
|
||
|
||
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null)
|
||
{
|
||
swapBuffersCallback?.Invoke();
|
||
return;
|
||
}
|
||
|
||
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
|
||
}
|
||
else if (acquireResult == Result.ErrorSurfaceLostKhr)
|
||
{
|
||
// Do not recreate in the background immediately – release and return
|
||
_gd.ReleaseSurface();
|
||
swapBuffersCallback?.Invoke();
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
acquireResult.ThrowOnError();
|
||
break;
|
||
}
|
||
}
|
||
|
||
Image swapchainImage = _swapchainImages[nextImage];
|
||
|
||
_gd.FlushAllCommands();
|
||
|
||
CommandBufferScoped cbs = _gd.CommandBufferPool.Rent();
|
||
|
||
// --- Set layout/stages correctly depending on the path ---
|
||
bool allowStorageDst = !PlatformInfo.IsBionic; // Android: kein Storage auf Swapchain
|
||
bool useComputeDst = allowStorageDst && _scalingFilter != null;
|
||
|
||
if (useComputeDst)
|
||
{
|
||
// Compute writes to the swapchain image → General + ShaderWrite
|
||
Transition(
|
||
cbs.CommandBuffer,
|
||
swapchainImage,
|
||
PipelineStageFlags.TopOfPipeBit,
|
||
PipelineStageFlags.ComputeShaderBit,
|
||
0,
|
||
AccessFlags.ShaderWriteBit,
|
||
ImageLayout.Undefined,
|
||
ImageLayout.General);
|
||
}
|
||
else
|
||
{
|
||
// Render pass writes to the swapchain image → ColorAttachmentOptimal
|
||
Transition(
|
||
cbs.CommandBuffer,
|
||
swapchainImage,
|
||
PipelineStageFlags.TopOfPipeBit,
|
||
PipelineStageFlags.ColorAttachmentOutputBit,
|
||
0,
|
||
AccessFlags.ColorAttachmentWriteBit,
|
||
ImageLayout.Undefined,
|
||
ImageLayout.ColorAttachmentOptimal);
|
||
}
|
||
|
||
TextureView view = (TextureView)texture;
|
||
|
||
UpdateEffect();
|
||
|
||
if (_effect != null)
|
||
{
|
||
view = _effect.Run(view, cbs, _width, _height);
|
||
}
|
||
|
||
int srcX0, srcX1, srcY0, srcY1;
|
||
|
||
if (crop.Left == 0 && crop.Right == 0)
|
||
{
|
||
srcX0 = 0;
|
||
srcX1 = view.Width;
|
||
}
|
||
else
|
||
{
|
||
srcX0 = crop.Left;
|
||
srcX1 = crop.Right;
|
||
}
|
||
|
||
if (crop.Top == 0 && crop.Bottom == 0)
|
||
{
|
||
srcY0 = 0;
|
||
srcY1 = view.Height;
|
||
}
|
||
else
|
||
{
|
||
srcY0 = crop.Top;
|
||
srcY1 = crop.Bottom;
|
||
}
|
||
|
||
if (ScreenCaptureRequested)
|
||
{
|
||
if (_effect != null)
|
||
{
|
||
_gd.FlushAllCommands();
|
||
Semaphore[] emptySems = Array.Empty<Semaphore>();
|
||
PipelineStageFlags[] waitStagesCO = new[] { PipelineStageFlags.ColorAttachmentOutputBit };
|
||
_gd.CommandBufferPool.Return(
|
||
cbs,
|
||
emptySems,
|
||
waitStagesCO,
|
||
emptySems);
|
||
cbs.GetFence().Wait();
|
||
cbs = _gd.CommandBufferPool.Rent();
|
||
}
|
||
|
||
CaptureFrame(view, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, view.Info.Format.IsBgr(), crop.FlipX, crop.FlipY);
|
||
ScreenCaptureRequested = false;
|
||
}
|
||
|
||
float ratioX = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _height * crop.AspectRatioX / (_width * crop.AspectRatioY));
|
||
float ratioY = crop.IsStretched ? 1.0f : MathF.Min(1.0f, _width * crop.AspectRatioY / (_height * crop.AspectRatioX));
|
||
|
||
int dstWidth = (int)(_width * ratioX);
|
||
int dstHeight = (int)(_height * ratioY);
|
||
|
||
int dstPaddingX = (_width - dstWidth) / 2;
|
||
int dstPaddingY = (_height - dstHeight) / 2;
|
||
|
||
int dstX0 = crop.FlipX ? _width - dstPaddingX : dstPaddingX;
|
||
int dstX1 = crop.FlipX ? dstPaddingX : _width - dstPaddingX;
|
||
|
||
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
||
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
||
|
||
if (_scalingFilter != null && useComputeDst)
|
||
{
|
||
_scalingFilter!.Run(
|
||
view,
|
||
cbs,
|
||
_swapchainImageViews[nextImage].GetImageViewForAttachment(),
|
||
_format,
|
||
_width,
|
||
_height,
|
||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||
new Extents2D(dstX0, dstY0, dstX1, dstY1)
|
||
);
|
||
}
|
||
else
|
||
{
|
||
_gd.HelperShader.BlitColor(
|
||
_gd,
|
||
cbs,
|
||
view,
|
||
_swapchainImageViews[nextImage],
|
||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
||
_isLinear,
|
||
true);
|
||
}
|
||
|
||
// Transition to Present – Stages/Access depending on the previous path
|
||
if (useComputeDst)
|
||
{
|
||
Transition(
|
||
cbs.CommandBuffer,
|
||
swapchainImage,
|
||
PipelineStageFlags.ComputeShaderBit,
|
||
PipelineStageFlags.BottomOfPipeBit,
|
||
AccessFlags.ShaderWriteBit,
|
||
0,
|
||
ImageLayout.General,
|
||
ImageLayout.PresentSrcKhr);
|
||
}
|
||
else
|
||
{
|
||
Transition(
|
||
cbs.CommandBuffer,
|
||
swapchainImage,
|
||
PipelineStageFlags.ColorAttachmentOutputBit,
|
||
PipelineStageFlags.BottomOfPipeBit,
|
||
AccessFlags.ColorAttachmentWriteBit,
|
||
0,
|
||
ImageLayout.ColorAttachmentOptimal,
|
||
ImageLayout.PresentSrcKhr);
|
||
}
|
||
|
||
Semaphore[] waitSems = new[] { _imageAvailableSemaphores[semaphoreIndex] };
|
||
PipelineStageFlags[] waitStages = new[] { PipelineStageFlags.ColorAttachmentOutputBit }; // Important on Android
|
||
Semaphore[] signalSems = new[] { _renderFinishedSemaphores[semaphoreIndex] };
|
||
_gd.CommandBufferPool.Return(cbs, waitSems, waitStages, signalSems);
|
||
|
||
_gd.FlushAllCommands();
|
||
|
||
PresentOne(_gd, _renderFinishedSemaphores[semaphoreIndex], _swapchain, nextImage);
|
||
|
||
swapBuffersCallback?.Invoke();
|
||
}
|
||
|
||
private static unsafe void PresentOne(
|
||
VulkanRenderer gd,
|
||
Semaphore signal,
|
||
SwapchainKHR swapchain,
|
||
uint imageIndex)
|
||
{
|
||
Semaphore* pWait = stackalloc Semaphore[1];
|
||
SwapchainKHR* pSwap = stackalloc SwapchainKHR[1];
|
||
uint* pImageIndex = stackalloc uint[1];
|
||
|
||
pWait[0] = signal;
|
||
pSwap[0] = swapchain;
|
||
pImageIndex[0] = imageIndex;
|
||
|
||
PresentInfoKHR presentInfo = new()
|
||
{
|
||
SType = StructureType.PresentInfoKhr,
|
||
WaitSemaphoreCount = 1,
|
||
PWaitSemaphores = pWait,
|
||
SwapchainCount = 1,
|
||
PSwapchains = pSwap,
|
||
PImageIndices = pImageIndex,
|
||
PResults = null
|
||
};
|
||
|
||
lock (gd.QueueLock)
|
||
{
|
||
gd.SwapchainApi.QueuePresent(gd.Queue, in presentInfo);
|
||
}
|
||
}
|
||
|
||
public override void SetAntiAliasing(AntiAliasing effect)
|
||
{
|
||
if (_currentAntiAliasing == effect && _effect != null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_currentAntiAliasing = effect;
|
||
|
||
_updateEffect = true;
|
||
}
|
||
|
||
public override void SetScalingFilter(ScalingFilter type)
|
||
{
|
||
if (_currentScalingFilter == type && _effect != null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_currentScalingFilter = type;
|
||
|
||
_updateScalingFilter = true;
|
||
}
|
||
|
||
public override void SetColorSpacePassthrough(bool colorSpacePassthroughEnabled)
|
||
{
|
||
_colorSpacePassthroughEnabled = colorSpacePassthroughEnabled;
|
||
_swapchainIsDirty = true;
|
||
}
|
||
|
||
private void UpdateEffect()
|
||
{
|
||
if (_updateEffect)
|
||
{
|
||
_updateEffect = false;
|
||
|
||
switch (_currentAntiAliasing)
|
||
{
|
||
case AntiAliasing.Fxaa:
|
||
_effect?.Dispose();
|
||
_effect = new FxaaPostProcessingEffect(_gd, _device);
|
||
break;
|
||
case AntiAliasing.None:
|
||
_effect?.Dispose();
|
||
_effect = null;
|
||
break;
|
||
case AntiAliasing.SmaaLow:
|
||
case AntiAliasing.SmaaMedium:
|
||
case AntiAliasing.SmaaHigh:
|
||
case AntiAliasing.SmaaUltra:
|
||
int quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
|
||
if (_effect is SmaaPostProcessingEffect smaa)
|
||
{
|
||
smaa.Quality = quality;
|
||
}
|
||
else
|
||
{
|
||
_effect?.Dispose();
|
||
_effect = new SmaaPostProcessingEffect(_gd, _device, quality);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (_updateScalingFilter)
|
||
{
|
||
_updateScalingFilter = false;
|
||
|
||
switch (_currentScalingFilter)
|
||
{
|
||
case ScalingFilter.Bilinear:
|
||
case ScalingFilter.Nearest:
|
||
_scalingFilter?.Dispose();
|
||
_scalingFilter = null;
|
||
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
|
||
break;
|
||
case ScalingFilter.Fsr:
|
||
if (_scalingFilter is not FsrScalingFilter)
|
||
{
|
||
_scalingFilter?.Dispose();
|
||
_scalingFilter = new FsrScalingFilter(_gd, _device);
|
||
}
|
||
|
||
_scalingFilter.Level = _scalingFilterLevel;
|
||
break;
|
||
case ScalingFilter.Area:
|
||
if (_scalingFilter is not AreaScalingFilter)
|
||
{
|
||
_scalingFilter?.Dispose();
|
||
_scalingFilter = new AreaScalingFilter(_gd, _device);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override void SetScalingFilterLevel(float level)
|
||
{
|
||
_scalingFilterLevel = level;
|
||
_updateScalingFilter = true;
|
||
}
|
||
|
||
private unsafe void Transition(
|
||
CommandBuffer commandBuffer,
|
||
Image image,
|
||
PipelineStageFlags srcStage,
|
||
PipelineStageFlags dstStage,
|
||
AccessFlags srcAccess,
|
||
AccessFlags dstAccess,
|
||
ImageLayout srcLayout,
|
||
ImageLayout dstLayout)
|
||
{
|
||
ImageSubresourceRange subresourceRange = new(ImageAspectFlags.ColorBit, 0, 1, 0, 1);
|
||
|
||
ImageMemoryBarrier barrier = new()
|
||
{
|
||
SType = StructureType.ImageMemoryBarrier,
|
||
SrcAccessMask = srcAccess,
|
||
DstAccessMask = dstAccess,
|
||
OldLayout = srcLayout,
|
||
NewLayout = dstLayout,
|
||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||
Image = image,
|
||
SubresourceRange = subresourceRange,
|
||
};
|
||
|
||
_gd.Api.CmdPipelineBarrier(
|
||
commandBuffer,
|
||
srcStage,
|
||
dstStage,
|
||
0,
|
||
0,
|
||
null,
|
||
0,
|
||
null,
|
||
1,
|
||
in barrier);
|
||
}
|
||
|
||
private void CaptureFrame(TextureView texture, int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
|
||
{
|
||
byte[] bitmap = texture.GetData(x, y, width, height);
|
||
|
||
_gd.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
|
||
}
|
||
|
||
public override void SetSize(int width, int height)
|
||
{
|
||
// We don't need to use width and height as we can get the size from the surface.
|
||
_swapchainIsDirty = true;
|
||
|
||
// After resuming, ensure that surface queries are allowed again,
|
||
// in case OnSurfaceLost() had previously closed the gate.
|
||
if (_surface.Handle != 0)
|
||
{
|
||
SetSurfaceQueryAllowed(true);
|
||
}
|
||
}
|
||
|
||
public override void ChangeVSyncMode(VSyncMode vSyncMode)
|
||
{
|
||
_vSyncMode = vSyncMode;
|
||
// Present mode may change, so mark the swapchain for recreation
|
||
_swapchainIsDirty = true;
|
||
}
|
||
|
||
protected virtual void Dispose(bool disposing)
|
||
{
|
||
if (disposing)
|
||
{
|
||
lock (_gd.SurfaceLock)
|
||
{
|
||
unsafe
|
||
{
|
||
if (_swapchainImageViews != null)
|
||
{
|
||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||
{
|
||
_swapchainImageViews[i]?.Dispose();
|
||
}
|
||
}
|
||
|
||
if (_imageAvailableSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||
{
|
||
if (_imageAvailableSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_renderFinishedSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||
{
|
||
if (_renderFinishedSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_swapchain.Handle != 0)
|
||
{
|
||
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
|
||
}
|
||
}
|
||
}
|
||
|
||
_effect?.Dispose();
|
||
_scalingFilter?.Dispose();
|
||
}
|
||
}
|
||
|
||
public void OnSurfaceLost()
|
||
{
|
||
lock (_gd.SurfaceLock)
|
||
{
|
||
// Thorough cleanup operation so that nothing "old" remains after the resume
|
||
_swapchainIsDirty = true;
|
||
SetSurfaceQueryAllowed(false);
|
||
|
||
_gd.Api.DeviceWaitIdle(_device);
|
||
|
||
unsafe
|
||
{
|
||
if (_imageAvailableSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||
{
|
||
if (_imageAvailableSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||
}
|
||
}
|
||
_imageAvailableSemaphores = null;
|
||
}
|
||
|
||
if (_renderFinishedSemaphores != null)
|
||
{
|
||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||
{
|
||
if (_renderFinishedSemaphores[i].Handle != 0)
|
||
{
|
||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
|
||
}
|
||
}
|
||
_renderFinishedSemaphores = null;
|
||
}
|
||
}
|
||
|
||
if (_swapchainImageViews != null)
|
||
{
|
||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||
{
|
||
_swapchainImageViews[i]?.Dispose();
|
||
}
|
||
_swapchainImageViews = null;
|
||
}
|
||
|
||
if (_swapchain.Handle != 0)
|
||
{
|
||
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, Span<AllocationCallbacks>.Empty);
|
||
_swapchain = default;
|
||
}
|
||
|
||
_surface = new SurfaceKHR(0);
|
||
_width = _height = 0; // Forces a later, clean recreate path
|
||
}
|
||
}
|
||
|
||
public override void Dispose()
|
||
{
|
||
Dispose(true);
|
||
}
|
||
}
|
||
}
|