Files
the-beast-roofer-edition/src/Ryujinx.Graphics.Vulkan/Window.cs
T
rooferdev caea82cbb4 TAA Phase 5: expose native TAA in the UI (Anti-Aliasing dropdown)
Adds AntiAliasing.Taa and a 'TAA' entry to Settings > Graphics > Anti-Aliasing,
so the native temporal AA is a normal user-selectable option alongside FXAA/SMAA
(it is a post-process effect, not a scaling filter). Selecting it instantiates
TemporalFilter as the active IPostProcessingEffect; applies live, no restart.
RYUJINX_TAA=1 still force-enables it as a dev override. On the OpenGL backend TAA
falls back to no AA (Vulkan-only feature). Validated in game via the UI.

Bakes the in-game-validated defaults so UI users get the tuned look with no env
vars: history blend 0.80, variance clamp 1.25 (RYUJINX_TAA_* still override).
2026-06-28 21:41:25 -04:00

805 lines
29 KiB
C#

using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Vulkan.Effects;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using System;
using System.Linq;
using VkFormat = Silk.NET.Vulkan.Format;
namespace Ryujinx.Graphics.Vulkan
{
class Window : WindowBase, IDisposable
{
private const int SurfaceWidth = 1280;
private const int SurfaceHeight = 720;
private readonly VulkanRenderer _gd;
private readonly SurfaceKHR _surface;
private readonly PhysicalDevice _physicalDevice;
private readonly Device _device;
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 Dlss.DlssUpscaler _dlss;
private bool _isLinear;
private float _scalingFilterLevel;
private bool _updateScalingFilter;
private ScalingFilter _currentScalingFilter;
private bool _colorSpacePassthroughEnabled;
private bool _hdrEnabled;
private float _hdrPaperWhite = 200f;
private float _hdrPeak = 1100f;
private float _hdrCurve = 2.8f;
private float _hdrGamma = 2.2f;
private float _hdrBlend = 0.5f;
private float _hdrWhiten = 0.4f;
public unsafe Window(VulkanRenderer gd, SurfaceKHR surface, PhysicalDevice physicalDevice, Device device)
{
_gd = gd;
_physicalDevice = physicalDevice;
_device = device;
_surface = surface;
CreateSwapchain();
}
private void RecreateSwapchain()
{
SwapchainKHR oldSwapchain = _swapchain;
_swapchainIsDirty = false;
for (int i = 0; i < _swapchainImageViews.Length; i++)
{
_swapchainImageViews[i].Dispose();
}
// Destroy old Swapchain.
_gd.Api.DeviceWaitIdle(_device);
unsafe
{
for (int i = 0; i < _imageAvailableSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _imageAvailableSemaphores[i], null);
}
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
}
}
_gd.SwapchainApi.DestroySwapchain(_device, oldSwapchain, Span<AllocationCallbacks>.Empty);
CreateSwapchain();
}
private unsafe void CreateSwapchain()
{
_gd.SurfaceApi.GetPhysicalDeviceSurfaceCapabilities(_physicalDevice, _surface, out SurfaceCapabilitiesKHR capabilities);
uint surfaceFormatsCount;
_gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, null);
SurfaceFormatKHR[] surfaceFormats = new SurfaceFormatKHR[surfaceFormatsCount];
fixed (SurfaceFormatKHR* pSurfaceFormats = surfaceFormats)
{
_gd.SurfaceApi.GetPhysicalDeviceSurfaceFormats(_physicalDevice, _surface, &surfaceFormatsCount, pSurfaceFormats);
}
uint presentModesCount;
_gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, null);
PresentModeKHR[] presentModes = new PresentModeKHR[presentModesCount];
fixed (PresentModeKHR* pPresentModes = presentModes)
{
_gd.SurfaceApi.GetPhysicalDeviceSurfacePresentModes(_physicalDevice, _surface, &presentModesCount, pPresentModes);
}
uint imageCount = capabilities.MinImageCount + 1;
if (capabilities.MaxImageCount > 0 && imageCount > capabilities.MaxImageCount)
{
imageCount = capabilities.MaxImageCount;
}
SurfaceFormatKHR surfaceFormat = ChooseSwapSurfaceFormat(surfaceFormats, _colorSpacePassthroughEnabled, _hdrEnabled);
Extent2D extent = ChooseSwapExtent(capabilities);
_width = (int)extent.Width;
_height = (int)extent.Height;
_format = surfaceFormat.Format;
SwapchainKHR oldSwapchain = _swapchain;
SwapchainCreateInfoKHR swapchainCreateInfo = new()
{
SType = StructureType.SwapchainCreateInfoKhr,
Surface = _surface,
MinImageCount = imageCount,
ImageFormat = surfaceFormat.Format,
ImageColorSpace = surfaceFormat.ColorSpace,
ImageExtent = extent,
ImageUsage = ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | ImageUsageFlags.StorageBit,
ImageSharingMode = SharingMode.Exclusive,
ImageArrayLayers = 1,
PreTransform = capabilities.CurrentTransform,
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, bool hdrEnabled)
{
// HDR output (scRGB inverse tone mapping): when HDR is enabled and the surface advertises the scRGB
// (extended sRGB linear) HDR colorspace, select the 16-bit float swapchain. That
// colorspace is only enumerated on a genuinely HDR-capable display/OS, so on an SDR
// display this branch never triggers and the normal SDR format is used unchanged.
if (hdrEnabled)
{
foreach (SurfaceFormatKHR hdrFormat in availableFormats)
{
if (hdrFormat.Format == VkFormat.R16G16B16A16Sfloat &&
hdrFormat.ColorSpace == ColorSpaceKHR.ColorSpaceExtendedSrgbLinearExt)
{
return hdrFormat;
}
}
}
if (availableFormats.Length == 1 && availableFormats[0].Format == VkFormat.Undefined)
{
return new SurfaceFormatKHR(VkFormat.B8G8R8A8Unorm, ColorSpaceKHR.PaceSrgbNonlinearKhr);
}
SurfaceFormatKHR formatToReturn = availableFormats[0];
if (colorSpacePassthroughEnabled)
{
foreach (SurfaceFormatKHR format in availableFormats)
{
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 (SurfaceFormatKHR format in availableFormats)
{
if (format.Format == VkFormat.B8G8R8A8Unorm && format.ColorSpace == ColorSpaceKHR.PaceSrgbNonlinearKhr)
{
formatToReturn = format;
break;
}
}
}
return formatToReturn;
}
private static CompositeAlphaFlagsKHR ChooseCompositeAlpha(CompositeAlphaFlagsKHR supportedFlags)
{
if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.OpaqueBitKhr))
{
return CompositeAlphaFlagsKHR.OpaqueBitKhr;
}
else if (supportedFlags.HasFlag(CompositeAlphaFlagsKHR.PreMultipliedBitKhr))
{
return CompositeAlphaFlagsKHR.PreMultipliedBitKhr;
}
else
{
return CompositeAlphaFlagsKHR.InheritBitKhr;
}
}
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
{
// VRR: FifoRelaxed lets the display's variable refresh rate (G-Sync/FreeSync) drive the refresh
// in fullscreen, smoothing a fluctuating frame rate without tearing. Immediate (full VSync off)
// was tried first but could hang the GPU under load, so FifoRelaxed is used instead.
if (vSyncMode == VSyncMode.Vrr && availablePresentModes.Contains(PresentModeKHR.FifoRelaxedKhr))
{
return PresentModeKHR.FifoRelaxedKhr;
}
else if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
{
return PresentModeKHR.ImmediateKhr;
}
else if (availablePresentModes.Contains(PresentModeKHR.MailboxKhr))
{
return PresentModeKHR.MailboxKhr;
}
else
{
return 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, ITexture depthTexture, ImageCrop crop, Action swapBuffersCallback)
{
_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();
semaphoreIndex = (_frameIndex - 1) % _imageAvailableSemaphores.Length;
}
else
{
acquireResult.ThrowOnError();
break;
}
}
Image swapchainImage = _swapchainImages[nextImage];
_gd.FlushAllCommands();
CommandBufferScoped cbs = _gd.CommandBufferPool.Rent();
Transition(
cbs.CommandBuffer,
swapchainImage,
0,
AccessFlags.TransferWriteBit,
ImageLayout.Undefined,
ImageLayout.General);
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();
_gd.CommandBufferPool.Return(
cbs,
null,
[PipelineStageFlags.ColorAttachmentOutputBit],
null);
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;
bool dlssHandled = false;
if (Dlss.DlssIntegration.IsEnabled)
{
_dlss ??= new Dlss.DlssUpscaler(_gd, _device);
// DLSS upscales the frame and blits straight to the swapchain; on any failure it
// returns false and we fall through to the normal scaling/blit path below.
dlssHandled = _dlss.TryRun(
view,
depthTexture as TextureView,
cbs,
_swapchainImageViews[nextImage],
_width,
_height,
new Extents2D(dstX0, dstY1, dstX1, dstY0),
_format == VkFormat.R16G16B16A16Sfloat,
_hdrPaperWhite / 80f,
_hdrPeak / 80f,
_hdrCurve,
_hdrGamma,
_hdrBlend,
_hdrWhiten);
}
// DLSS (when it ran) wrote the swapchain directly -- pure DLSS, no hybrid hand-off, nothing more
// to do. Otherwise (DLSS disabled, or it could not run this frame) render the configured spatial
// path: the NIS scaling filter, or a plain HDR-aware blit.
if (!dlssHandled)
{
RenderSpatial(_swapchainImageViews[nextImage]);
}
// Renders the configured spatial path (NIS scaling filter, or a plain HDR-aware blit) into the
// given destination -- the swapchain in steady state, or the cross-fade scratch target during a
// DLSS<->NIS transition. Both inputs to the final blend are therefore in the same display space.
void RenderSpatial(TextureView spatialDst)
{
if (_scalingFilter != null && _scalingFilter.IsResolutionSupported(view.Width, view.Height, _width, _height))
{
_scalingFilter.Run(
view,
cbs,
spatialDst.GetImageViewForAttachment(),
_format,
_width,
_height,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY0, dstX1, dstY1),
_format == VkFormat.R16G16B16A16Sfloat,
_hdrPaperWhite / 80f,
_hdrPeak / 80f,
_hdrCurve,
_hdrGamma,
_hdrBlend,
_hdrWhiten
);
}
else
{
_gd.HelperShader.BlitColor(
_gd,
cbs,
view,
spatialDst,
new Extents2D(srcX0, srcY0, srcX1, srcY1),
new Extents2D(dstX0, dstY1, dstX1, dstY0),
_isLinear,
true,
_format == VkFormat.R16G16B16A16Sfloat,
_hdrPaperWhite / 80f,
_hdrPeak / 80f,
_hdrCurve,
_hdrGamma,
_hdrBlend,
_hdrWhiten);
}
}
Transition(
cbs.CommandBuffer,
swapchainImage,
0,
0,
ImageLayout.General,
ImageLayout.PresentSrcKhr);
_gd.FlushAllCommands();
_gd.CommandBufferPool.Return(
cbs,
[_imageAvailableSemaphores[semaphoreIndex]],
[PipelineStageFlags.ColorAttachmentOutputBit],
[_renderFinishedSemaphores[semaphoreIndex]]);
// TODO: Present queue.
Semaphore semaphore = _renderFinishedSemaphores[semaphoreIndex];
SwapchainKHR swapchain = _swapchain;
Result result;
PresentInfoKHR presentInfo = new()
{
SType = StructureType.PresentInfoKhr,
WaitSemaphoreCount = 1,
PWaitSemaphores = &semaphore,
SwapchainCount = 1,
PSwapchains = &swapchain,
PImageIndices = &nextImage,
PResults = &result,
};
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;
// RYUJINX_TAA=1 forces the native temporal filter regardless of the UI selection (dev override).
AntiAliasing antiAliasing = Effects.TemporalFilter.IsEnabled ? AntiAliasing.Taa : _currentAntiAliasing;
switch (antiAliasing)
{
case AntiAliasing.Fxaa:
_effect?.Dispose();
_effect = new FxaaPostProcessingEffect(_gd, _device);
break;
case AntiAliasing.Taa:
if (_effect is not Effects.TemporalFilter)
{
_effect?.Dispose();
_effect = new Effects.TemporalFilter(_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;
case ScalingFilter.Nis:
if (_scalingFilter is not NisScalingFilter)
{
_scalingFilter?.Dispose();
_scalingFilter = new NisScalingFilter(_gd, _device);
}
_scalingFilter.Level = _scalingFilterLevel;
break;
}
}
}
public override void SetScalingFilterLevel(float level)
{
_scalingFilterLevel = level;
_updateScalingFilter = true;
}
private unsafe void Transition(
CommandBuffer commandBuffer,
Image image,
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,
PipelineStageFlags.TopOfPipeBit,
PipelineStageFlags.AllCommandsBit,
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;
}
public override void ChangeVSyncMode(VSyncMode vSyncMode)
{
_vSyncMode = vSyncMode;
//present mode may change, so mark the swapchain for recreation
_swapchainIsDirty = true;
}
public override void SetHdrMode(bool enabled, float paperWhite, float peak, float curve, float gamma, float blend, float whiten)
{
_hdrPaperWhite = paperWhite;
_hdrPeak = peak;
_hdrCurve = curve;
_hdrGamma = gamma;
_hdrBlend = blend;
_hdrWhiten = whiten;
if (_hdrEnabled != enabled)
{
_hdrEnabled = enabled;
// The swapchain surface format (SDR vs scRGB float) changes, so recreate it.
_swapchainIsDirty = true;
}
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
unsafe
{
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);
}
for (int i = 0; i < _renderFinishedSemaphores.Length; i++)
{
_gd.Api.DestroySemaphore(_device, _renderFinishedSemaphores[i], null);
}
_gd.SwapchainApi.DestroySwapchain(_device, _swapchain, null);
}
_effect?.Dispose();
_scalingFilter?.Dispose();
_dlss?.Dispose();
}
}
public override void Dispose()
{
Dispose(true);
}
}
}