Files
Kenji-NX/src/Ryujinx.Graphics.Vulkan/Window.cs
T

968 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}