mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-09-28 23:28:00 +02:00
single MR with all patches i did, compared to the 2.0.5 release:
See merge request kenji-nx/ryujinx!18
This commit is contained in:
@@ -28,6 +28,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
// JNI/Lifecycle Flag
|
||||
internal volatile bool PresentAllowed = true;
|
||||
|
||||
public uint ProgramCount { get; set; } = 0;
|
||||
|
||||
internal FormatCapabilities FormatCapabilities { get; private set; }
|
||||
@@ -49,6 +52,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
internal Lock BackgroundQueueLock { get; private set; }
|
||||
internal Lock QueueLock { get; private set; }
|
||||
|
||||
// SurfaceLock to serialize Create/Destroy/Queries
|
||||
internal Lock SurfaceLock { get; private set; }
|
||||
|
||||
internal MemoryAllocator MemoryAllocator { get; private set; }
|
||||
internal HostMemoryAllocator HostMemoryAllocator { get; private set; }
|
||||
internal CommandBufferPool CommandBufferPool { get; private set; }
|
||||
@@ -500,6 +506,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Queue = queue;
|
||||
QueueLock = new();
|
||||
|
||||
// Init Locks
|
||||
SurfaceLock = new();
|
||||
if (maxQueueCount >= 2)
|
||||
{
|
||||
Api.GetDeviceQueue(_device, queueFamilyIndex, 1, out var backgroundQueue);
|
||||
BackgroundQueue = backgroundQueue;
|
||||
BackgroundQueueLock = new();
|
||||
}
|
||||
|
||||
LoadFeatures(maxQueueCount, queueFamilyIndex);
|
||||
|
||||
QueueFamilyIndex = queueFamilyIndex;
|
||||
@@ -551,7 +566,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
||||
{
|
||||
ProgramCount++;
|
||||
|
||||
|
||||
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
||||
|
||||
if (info.State.HasValue || isCompute)
|
||||
@@ -1006,13 +1021,100 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return !(IsMoltenVk || IsQualcommProprietary);
|
||||
}
|
||||
|
||||
internal unsafe void RecreateSurface()
|
||||
// ===== Surface/Present Lifecycle helpers =====
|
||||
|
||||
public unsafe bool RecreateSurface()
|
||||
{
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
if (!PresentAllowed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_surface = _getSurface(_instance.Instance, Api);
|
||||
lock (SurfaceLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_surface.Handle != 0)
|
||||
{
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
_surface = new SurfaceKHR(0);
|
||||
}
|
||||
|
||||
(_window as Window)?.SetSurface(_surface);
|
||||
_surface = _getSurface(_instance.Instance, Api);
|
||||
if (_surface.Handle == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
( _window as Window )?.SetSurface(_surface);
|
||||
( _window as Window )?.SetSurfaceQueryAllowed(true);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// retry später
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void ReleaseSurface()
|
||||
{
|
||||
lock (SurfaceLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
( _window as Window )?.SetSurfaceQueryAllowed(false);
|
||||
|
||||
if (_surface.Handle != 0)
|
||||
{
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
_surface = new SurfaceKHR(0);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// still
|
||||
}
|
||||
|
||||
( _window as Window )?.OnSurfaceLost();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPresentEnabled(bool enabled)
|
||||
{
|
||||
PresentAllowed = enabled;
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
( _window as Window )?.SetSurfaceQueryAllowed(false);
|
||||
ReleaseSurface();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = RecreateSurface();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPresentAllowed(bool allowed)
|
||||
{
|
||||
PresentAllowed = allowed;
|
||||
Logger.Trace?.Print(LogClass.Gpu, $"PresentAllowed={allowed}");
|
||||
|
||||
if (allowed)
|
||||
{
|
||||
try
|
||||
{
|
||||
( _window as Window )?.SetSurfaceQueryAllowed(true);
|
||||
_window?.SetSize(0, 0);
|
||||
_ = RecreateSurface();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
( _window as Window )?.SetSurfaceQueryAllowed(false);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
@@ -1034,23 +1136,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
MemoryAllocator.Dispose();
|
||||
|
||||
foreach (var shader in Shaders)
|
||||
{
|
||||
shader.Dispose();
|
||||
}
|
||||
foreach (var shader in Shaders) shader.Dispose();
|
||||
foreach (var texture in Textures) texture.Release();
|
||||
foreach (var sampler in Samplers) sampler.Dispose();
|
||||
|
||||
foreach (var texture in Textures)
|
||||
if (_surface.Handle != 0)
|
||||
{
|
||||
texture.Release();
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
}
|
||||
|
||||
foreach (var sampler in Samplers)
|
||||
{
|
||||
sampler.Dispose();
|
||||
}
|
||||
|
||||
SurfaceApi.DestroySurface(_instance.Instance, _surface, null);
|
||||
|
||||
Api.DestroyDevice(_device, null);
|
||||
|
||||
_debugMessenger.Dispose();
|
||||
|
||||
@@ -42,6 +42,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
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;
|
||||
@@ -49,160 +52,270 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_device = device;
|
||||
_surface = surface;
|
||||
|
||||
CreateSwapchain();
|
||||
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;
|
||||
var 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;
|
||||
var 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;
|
||||
var 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()
|
||||
{
|
||||
var oldSwapchain = _swapchain;
|
||||
_swapchainIsDirty = false;
|
||||
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
if (!_gd.PresentAllowed || _surface.Handle == 0 || !CanQuerySurface())
|
||||
{
|
||||
_swapchainImageViews[i].Dispose();
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy old Swapchain.
|
||||
|
||||
_gd.Api.DeviceWaitIdle(_device);
|
||||
|
||||
unsafe
|
||||
lock (_gd.SurfaceLock)
|
||||
{
|
||||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||||
var oldSwapchain = _swapchain;
|
||||
_swapchainIsDirty = false;
|
||||
|
||||
if (_swapchainImageViews != null)
|
||||
{
|
||||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
{
|
||||
_swapchainImageViews[i]?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||||
_gd.Api.DeviceWaitIdle(_device);
|
||||
|
||||
unsafe
|
||||
{
|
||||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
|
||||
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();
|
||||
}
|
||||
|
||||
_gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span<AllocationCallbacks>.Empty);
|
||||
|
||||
CreateSwapchain();
|
||||
}
|
||||
|
||||
internal void SetSurface(SurfaceKHR surface)
|
||||
{
|
||||
_surface = surface;
|
||||
RecreateSwapchain();
|
||||
lock (_gd.SurfaceLock)
|
||||
{
|
||||
_surface = surface;
|
||||
|
||||
if (!_gd.PresentAllowed || _surface.Handle == 0)
|
||||
{
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SetSurfaceQueryAllowed(true);
|
||||
RecreateSwapchain();
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void CreateSwapchain()
|
||||
{
|
||||
_gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out var capabilities);
|
||||
|
||||
uint surfaceFormatsCount;
|
||||
|
||||
_gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null);
|
||||
|
||||
var surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
|
||||
|
||||
fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
|
||||
if (!_gd.PresentAllowed || _surface.Handle == 0 || !CanQuerySurface())
|
||||
{
|
||||
_gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats);
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint presentModesCount;
|
||||
|
||||
_gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null);
|
||||
|
||||
var presentModes = new PresentModeKHR[presentModesCount];
|
||||
|
||||
fixed (PresentModeKHR* pPresentModes = presentModes)
|
||||
lock (_gd.SurfaceLock)
|
||||
{
|
||||
_gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes);
|
||||
}
|
||||
if (!TryGetSurfaceCapabilities(out var capabilities))
|
||||
{
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint imageCount = capabilities.MinImageCount + 1;
|
||||
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
|
||||
{
|
||||
imageCount = capabilities.MaxImageCount;
|
||||
}
|
||||
if (!TryGetSurfaceFormats(out var surfaceFormats))
|
||||
{
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
||||
if (!TryGetPresentModes(out var presentModes))
|
||||
{
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var extent = ChooseSwapExtent(capabilities);
|
||||
uint imageCount = capabilities.MinImageCount + 1;
|
||||
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
|
||||
{
|
||||
imageCount = capabilities.MaxImageCount;
|
||||
}
|
||||
|
||||
_width = (int)extent.Width;
|
||||
_height = (int)extent.Height;
|
||||
_format = surfaceFormat.Format;
|
||||
var surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled);
|
||||
var extent = ChooseSwapExtent(capabilities);
|
||||
|
||||
var oldSwapchain = _swapchain;
|
||||
// Guard against 0x0 extent immediately after resume
|
||||
if (extent.Width == 0 || extent.Height == 0)
|
||||
{
|
||||
_swapchainIsDirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentTransform = capabilities.CurrentTransform;
|
||||
_width = (int)extent.Width;
|
||||
_height = (int)extent.Height;
|
||||
_format = surfaceFormat.Format;
|
||||
|
||||
var swapchainCreateInfo = new SwapchainCreateInfoKHR
|
||||
{
|
||||
SType = StructureType.SwapchainCreateInfoKhr,
|
||||
Surface = _surface,
|
||||
MinImageCount = imageCount,
|
||||
ImageFormat = surfaceFormat.Format,
|
||||
ImageColorSpace = surfaceFormat.ColorSpace,
|
||||
ImageExtent = extent,
|
||||
ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | (PlatformInfo.IsBionic ? 0 : ImageUsageFlags.StorageBit),
|
||||
ImageSharingMode = SharingMode.Exclusive,
|
||||
ImageArrayLayers = 1,
|
||||
PreTransform = PlatformInfo.IsBionic ? SurfaceTransformFlagsKHR.IdentityBitKhr : capabilities.CurrentTransform,
|
||||
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
||||
PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
|
||||
Clipped = true,
|
||||
};
|
||||
var oldSwapchain = _swapchain;
|
||||
|
||||
var textureCreateInfo = new TextureCreateInfo(
|
||||
_width,
|
||||
_height,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
FormatTable.GetFormat(surfaceFormat.Format),
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
CurrentTransform = capabilities.CurrentTransform;
|
||||
|
||||
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||||
var usage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit;
|
||||
if (!PlatformInfo.IsBionic)
|
||||
{
|
||||
usage |= ImageUsageFlags.StorageBit; // Only desktop allows storage for swapchain
|
||||
}
|
||||
|
||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
||||
// On Android: Identity; otherwise, the CurrentTransform recommended by the driver
|
||||
var preTransform = PlatformInfo.IsBionic
|
||||
? SurfaceTransformFlagsKHR.IdentityBitKhr
|
||||
: capabilities.CurrentTransform;
|
||||
|
||||
_swapchainImages = new Image[imageCount];
|
||||
var swapchainCreateInfo = new SwapchainCreateInfoKHR
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
fixed (Image* pSwapchainImages = _swapchainImages)
|
||||
{
|
||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
|
||||
}
|
||||
var textureCreateInfo = new TextureCreateInfo(
|
||||
_width,
|
||||
_height,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
FormatTable.GetFormat(surfaceFormat.Format),
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
_swapchainImageViews = new TextureView[imageCount];
|
||||
_gd.SwapchainApi.CreateSwapchain(_device, in swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||||
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
{
|
||||
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
|
||||
}
|
||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
||||
|
||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
||||
{
|
||||
SType = StructureType.SemaphoreCreateInfo,
|
||||
};
|
||||
_swapchainImages = new Image[imageCount];
|
||||
|
||||
_imageAvailableSemaphores = new Semaphore[imageCount];
|
||||
fixed (Image* pSwapchainImages = _swapchainImages)
|
||||
{
|
||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _imageAvailableSemaphores[i]).ThrowOnError();
|
||||
}
|
||||
_swapchainImageViews = new TextureView[imageCount];
|
||||
|
||||
_renderFinishedSemaphores = new Semaphore[imageCount];
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
{
|
||||
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.CreateSemaphore(_device, in semaphoreCreateInfo, null, out _renderFinishedSemaphores[i]).ThrowOnError();
|
||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,40 +348,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
|
||||
{
|
||||
if (availableFormats == null || availableFormats.Length == 0)
|
||||
{
|
||||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, (ColorSpaceKHR)0);
|
||||
}
|
||||
|
||||
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
|
||||
{
|
||||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
|
||||
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, availableFormats[0].ColorSpace);
|
||||
}
|
||||
|
||||
var formatToReturn = availableFormats[0];
|
||||
if (colorSpacePassthroughEnabled)
|
||||
foreach (var f in availableFormats)
|
||||
{
|
||||
foreach (var format in availableFormats)
|
||||
if (f.Format == VkFormat.B8G8R8A8Unorm)
|
||||
{
|
||||
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.SpacePassThroughExt)
|
||||
{
|
||||
formatToReturn = format;
|
||||
break;
|
||||
}
|
||||
else if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
|
||||
{
|
||||
formatToReturn = format;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var format in availableFormats)
|
||||
{
|
||||
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
|
||||
{
|
||||
formatToReturn = format;
|
||||
break;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
||||
return formatToReturn;
|
||||
return availableFormats[0];
|
||||
}
|
||||
|
||||
private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags)
|
||||
@@ -277,13 +375,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return CompositeAlphaFlagsKHR.OpaqueBitKhr;
|
||||
}
|
||||
else if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr))
|
||||
{
|
||||
return CompositeAlphaFlagsKHR.PreMultipliedBitKhr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CompositeAlphaFlagsKHR.InheritBitKhr;
|
||||
return supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr)
|
||||
? CompositeAlphaFlagsKHR.PreMultipliedBitKhr
|
||||
: CompositeAlphaFlagsKHR.InheritBitKhr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,13 +389,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return PresentModeKHR.ImmediateKhr;
|
||||
}
|
||||
else if (availablePresentModes.Contains(PresentModeKHR.MailboxKhr))
|
||||
{
|
||||
return PresentModeKHR.MailboxKhr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PresentModeKHR.FifoKhr;
|
||||
return availablePresentModes.Contains(PresentModeKHR.MailboxKhr) ? PresentModeKHR.MailboxKhr : PresentModeKHR.FifoKhr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +410,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
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;
|
||||
@@ -338,11 +461,21 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_swapchainIsDirty)
|
||||
{
|
||||
RecreateSwapchain();
|
||||
|
||||
if (_swapchain.Handle == 0 || _imageAvailableSemaphores == null)
|
||||
{
|
||||
swapBuffersCallback?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
|
||||
}
|
||||
else if(acquireResult == Result.ErrorSurfaceLostKhr)
|
||||
else if (acquireResult == Result.ErrorSurfaceLostKhr)
|
||||
{
|
||||
_gd.RecreateSurface();
|
||||
// Do not recreate in the background immediately – release and return
|
||||
_gd.ReleaseSurface();
|
||||
swapBuffersCallback?.Invoke();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -357,13 +490,36 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var cbs = _gd.CommandBufferPool.Rent();
|
||||
|
||||
Transition(
|
||||
cbs.CommandBuffer,
|
||||
swapchainImage,
|
||||
0,
|
||||
AccessFlags.TransferWriteBit,
|
||||
ImageLayout.Undefined,
|
||||
ImageLayout.General);
|
||||
// --- 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);
|
||||
}
|
||||
|
||||
var view = (TextureView)texture;
|
||||
|
||||
@@ -402,18 +558,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (_effect != null)
|
||||
{
|
||||
var emptySems = Array.Empty<Silk.NET.Vulkan.Semaphore>();
|
||||
var waitStagesCO = new PipelineStageFlags[] { PipelineStageFlags.ColorAttachmentOutputBit };
|
||||
_gd.CommandBufferPool.Return(
|
||||
cbs,
|
||||
null,
|
||||
stackalloc[] { PipelineStageFlags.ColorAttachmentOutputBit },
|
||||
null);
|
||||
emptySems,
|
||||
waitStagesCO,
|
||||
emptySems);
|
||||
_gd.FlushAllCommands();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -432,9 +589,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
||||
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
||||
|
||||
if (_scalingFilter != null)
|
||||
if (_scalingFilter != null && useComputeDst)
|
||||
{
|
||||
_scalingFilter.Run(
|
||||
_scalingFilter!.Run(
|
||||
view,
|
||||
cbs,
|
||||
_swapchainImageViews[nextImage].GetImageViewForAttachment(),
|
||||
@@ -443,7 +600,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_height,
|
||||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||
new Extents2D(dstX0, dstY0, dstX1, dstY1)
|
||||
);
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -458,44 +615,71 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
true);
|
||||
}
|
||||
|
||||
Transition(
|
||||
cbs.CommandBuffer,
|
||||
swapchainImage,
|
||||
0,
|
||||
0,
|
||||
ImageLayout.General,
|
||||
ImageLayout.PresentSrcKhr);
|
||||
// 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);
|
||||
}
|
||||
|
||||
_gd.CommandBufferPool.Return(
|
||||
cbs,
|
||||
[_imageAvailableSemaphores[semaphoreIndex]],
|
||||
[PipelineStageFlags.ColorAttachmentOutputBit],
|
||||
[_renderFinishedSemaphores[semaphoreIndex]]);
|
||||
var waitSems = new Silk.NET.Vulkan.Semaphore[] { _imageAvailableSemaphores[semaphoreIndex] };
|
||||
var waitStages = new PipelineStageFlags[] { PipelineStageFlags.ColorAttachmentOutputBit }; // Important on Android
|
||||
var signalSems = new Silk.NET.Vulkan.Semaphore[] { _renderFinishedSemaphores[semaphoreIndex] };
|
||||
_gd.CommandBufferPool.Return(cbs, waitSems, waitStages, signalSems);
|
||||
|
||||
// TODO: Present queue.
|
||||
var semaphore = _renderFinishedSemaphores[semaphoreIndex];
|
||||
var swapchain = _swapchain;
|
||||
PresentOne(_gd, _renderFinishedSemaphores[semaphoreIndex], _swapchain, nextImage);
|
||||
|
||||
Result result;
|
||||
swapBuffersCallback?.Invoke();
|
||||
}
|
||||
|
||||
private static unsafe void PresentOne(
|
||||
VulkanRenderer gd,
|
||||
Silk.NET.Vulkan.Semaphore signal,
|
||||
SwapchainKHR swapchain,
|
||||
uint imageIndex)
|
||||
{
|
||||
Silk.NET.Vulkan.Semaphore* pWait = stackalloc Silk.NET.Vulkan.Semaphore[1];
|
||||
SwapchainKHR* pSwap = stackalloc SwapchainKHR[1];
|
||||
uint* pImageIndex = stackalloc uint[1];
|
||||
|
||||
pWait[0] = signal;
|
||||
pSwap[0] = swapchain;
|
||||
pImageIndex[0] = imageIndex;
|
||||
|
||||
var presentInfo = new PresentInfoKHR
|
||||
{
|
||||
SType = StructureType.PresentInfoKhr,
|
||||
WaitSemaphoreCount = 1,
|
||||
PWaitSemaphores = &semaphore,
|
||||
PWaitSemaphores = pWait,
|
||||
SwapchainCount = 1,
|
||||
PSwapchains = &swapchain,
|
||||
PImageIndices = &nextImage,
|
||||
PResults = &result,
|
||||
PSwapchains = pSwap,
|
||||
PImageIndices = pImageIndex,
|
||||
PResults = null
|
||||
};
|
||||
|
||||
lock (_gd.QueueLock)
|
||||
lock (gd.QueueLock)
|
||||
{
|
||||
_gd.SwapchainApi.QueuePresent(_gd.Queue, in presentInfo);
|
||||
gd.SwapchainApi.QueuePresent(gd.Queue, in presentInfo);
|
||||
}
|
||||
|
||||
//While this does nothing in most cases, it's useful to notify the end of the frame, and is used to handle native window in Android.
|
||||
swapBuffersCallback?.Invoke();
|
||||
}
|
||||
|
||||
public override void SetAntiAliasing(AntiAliasing effect)
|
||||
@@ -603,6 +787,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private unsafe void Transition(
|
||||
CommandBuffer commandBuffer,
|
||||
Image image,
|
||||
PipelineStageFlags srcStage,
|
||||
PipelineStageFlags dstStage,
|
||||
AccessFlags srcAccess,
|
||||
AccessFlags dstAccess,
|
||||
ImageLayout srcLayout,
|
||||
@@ -625,8 +811,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
_gd.Api.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
srcStage,
|
||||
dstStage,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
@@ -647,12 +833,19 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
// 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
|
||||
// Present mode may change, so mark the swapchain for recreation
|
||||
_swapchainIsDirty = true;
|
||||
}
|
||||
|
||||
@@ -660,24 +853,45 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
unsafe
|
||||
lock (_gd.SurfaceLock)
|
||||
{
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
unsafe
|
||||
{
|
||||
_swapchainImageViews[i].Dispose();
|
||||
}
|
||||
if (_swapchainImageViews != null)
|
||||
{
|
||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||
{
|
||||
_swapchainImageViews[i]?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||||
}
|
||||
if (_imageAvailableSemaphores != null)
|
||||
{
|
||||
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
|
||||
{
|
||||
if (_imageAvailableSemaphores[i].Handle != 0)
|
||||
{
|
||||
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
|
||||
{
|
||||
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
|
||||
if (_swapchain.Handle != 0)
|
||||
{
|
||||
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_effect?.Dispose();
|
||||
@@ -685,6 +899,63 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user