aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Graphics.Vulkan/ImageWindow.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Graphics.Vulkan/ImageWindow.cs')
-rw-r--r--Ryujinx.Graphics.Vulkan/ImageWindow.cs361
1 files changed, 361 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Vulkan/ImageWindow.cs b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
new file mode 100644
index 00000000..5dd23155
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/ImageWindow.cs
@@ -0,0 +1,361 @@
+using Ryujinx.Graphics.GAL;
+using Silk.NET.Vulkan;
+using System;
+using VkFormat = Silk.NET.Vulkan.Format;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+ class ImageWindow : WindowBase, IWindow, IDisposable
+ {
+ private const int ImageCount = 5;
+ private const int SurfaceWidth = 1280;
+ private const int SurfaceHeight = 720;
+
+ private readonly VulkanRenderer _gd;
+ private readonly PhysicalDevice _physicalDevice;
+ private readonly Device _device;
+
+ private Auto<DisposableImage>[] _images;
+ private Auto<DisposableImageView>[] _imageViews;
+ private Auto<MemoryAllocation>[] _imageAllocationAuto;
+ private ulong[] _imageSizes;
+ private ulong[] _imageOffsets;
+
+ private Semaphore _imageAvailableSemaphore;
+ private Semaphore _renderFinishedSemaphore;
+
+ private int _width = SurfaceWidth;
+ private int _height = SurfaceHeight;
+ private VkFormat _format;
+ private bool _recreateImages;
+ private int _nextImage;
+
+ internal new bool ScreenCaptureRequested { get; set; }
+
+ public unsafe ImageWindow(VulkanRenderer gd, PhysicalDevice physicalDevice, Device device)
+ {
+ _gd = gd;
+ _physicalDevice = physicalDevice;
+ _device = device;
+
+ _format = VkFormat.R8G8B8A8Unorm;
+
+ _images = new Auto<DisposableImage>[ImageCount];
+ _imageAllocationAuto = new Auto<MemoryAllocation>[ImageCount];
+ _imageSizes = new ulong[ImageCount];
+ _imageOffsets = new ulong[ImageCount];
+
+ CreateImages();
+
+ var semaphoreCreateInfo = new SemaphoreCreateInfo()
+ {
+ SType = StructureType.SemaphoreCreateInfo
+ };
+
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _imageAvailableSemaphore).ThrowOnError();
+ gd.Api.CreateSemaphore(device, semaphoreCreateInfo, null, out _renderFinishedSemaphore).ThrowOnError();
+ }
+
+ private void RecreateImages()
+ {
+ for (int i = 0; i < ImageCount; i++)
+ {
+ _imageViews[i]?.Dispose();
+ _imageAllocationAuto[i]?.Dispose();
+ _images[i]?.Dispose();
+ }
+
+ CreateImages();
+ }
+
+ private unsafe void CreateImages()
+ {
+ _imageViews = new Auto<DisposableImageView>[ImageCount];
+
+ var cbs = _gd.CommandBufferPool.Rent();
+ for (int i = 0; i < _images.Length; i++)
+ {
+ var imageCreateInfo = new ImageCreateInfo
+ {
+ SType = StructureType.ImageCreateInfo,
+ ImageType = ImageType.ImageType2D,
+ Format = _format,
+ Extent =
+ new Extent3D((uint?)_width,
+ (uint?)_height, 1),
+ MipLevels = 1,
+ ArrayLayers = 1,
+ Samples = SampleCountFlags.SampleCount1Bit,
+ Tiling = ImageTiling.Optimal,
+ Usage = ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageTransferDstBit,
+ SharingMode = SharingMode.Exclusive,
+ InitialLayout = ImageLayout.Undefined,
+ Flags = ImageCreateFlags.ImageCreateMutableFormatBit
+ };
+
+ _gd.Api.CreateImage(_device, imageCreateInfo, null, out var image).ThrowOnError();
+ _images[i] = new Auto<DisposableImage>(new DisposableImage(_gd.Api, _device, image));
+
+ _gd.Api.GetImageMemoryRequirements(_device, image,
+ out var memoryRequirements);
+
+ var allocation = _gd.MemoryAllocator.AllocateDeviceMemory(_physicalDevice, memoryRequirements, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit);
+
+ _imageSizes[i] = allocation.Size;
+ _imageOffsets[i] = allocation.Offset;
+
+ _imageAllocationAuto[i] = new Auto<MemoryAllocation>(allocation);
+
+ _gd.Api.BindImageMemory(_device, image, allocation.Memory, allocation.Offset);
+
+ _imageViews[i] = CreateImageView(image, _format);
+
+ Transition(
+ cbs.CommandBuffer,
+ image,
+ 0,
+ 0,
+ ImageLayout.Undefined,
+ ImageLayout.ColorAttachmentOptimal);
+ }
+
+ _gd.CommandBufferPool.Return(cbs);
+ }
+
+ private unsafe Auto<DisposableImageView> CreateImageView(Image image, VkFormat format)
+ {
+ var componentMapping = new ComponentMapping(
+ ComponentSwizzle.R,
+ ComponentSwizzle.G,
+ ComponentSwizzle.B,
+ ComponentSwizzle.A);
+
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
+
+ var imageCreateInfo = new ImageViewCreateInfo()
+ {
+ SType = StructureType.ImageViewCreateInfo,
+ Image = image,
+ ViewType = ImageViewType.ImageViewType2D,
+ Format = format,
+ Components = componentMapping,
+ SubresourceRange = subresourceRange
+ };
+
+ _gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
+ return new Auto<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
+ }
+
+ public override unsafe void Present(ITexture texture, ImageCrop crop, Action<object> swapBuffersCallback)
+ {
+ if (_recreateImages)
+ {
+ RecreateImages();
+ _recreateImages = false;
+ }
+
+ var image = _images[_nextImage];
+
+ _gd.FlushAllCommands();
+
+ var cbs = _gd.CommandBufferPool.Rent();
+
+ Transition(
+ cbs.CommandBuffer,
+ image.GetUnsafe().Value,
+ 0,
+ AccessFlags.AccessTransferWriteBit,
+ ImageLayout.ColorAttachmentOptimal,
+ ImageLayout.General);
+
+ var view = (TextureView)texture;
+
+ int srcX0, srcX1, srcY0, srcY1;
+ float scale = view.ScaleFactor;
+
+ if (crop.Left == 0 && crop.Right == 0)
+ {
+ srcX0 = 0;
+ srcX1 = (int)(view.Width / scale);
+ }
+ else
+ {
+ srcX0 = crop.Left;
+ srcX1 = crop.Right;
+ }
+
+ if (crop.Top == 0 && crop.Bottom == 0)
+ {
+ srcY0 = 0;
+ srcY1 = (int)(view.Height / scale);
+ }
+ else
+ {
+ srcY0 = crop.Top;
+ srcY1 = crop.Bottom;
+ }
+
+ if (scale != 1f)
+ {
+ srcX0 = (int)(srcX0 * scale);
+ srcY0 = (int)(srcY0 * scale);
+ srcX1 = (int)Math.Ceiling(srcX1 * scale);
+ srcY1 = (int)Math.Ceiling(srcY1 * scale);
+ }
+
+ if (ScreenCaptureRequested)
+ {
+ 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;
+
+ _gd.HelperShader.Blit(
+ _gd,
+ cbs,
+ view,
+ _imageViews[_nextImage],
+ _width,
+ _height,
+ _format,
+ new Extents2D(srcX0, srcY0, srcX1, srcY1),
+ new Extents2D(dstX0, dstY1, dstX1, dstY0),
+ true,
+ true);
+
+ Transition(
+ cbs.CommandBuffer,
+ image.GetUnsafe().Value,
+ 0,
+ 0,
+ ImageLayout.General,
+ ImageLayout.ColorAttachmentOptimal);
+
+ _gd.CommandBufferPool.Return(
+ cbs,
+ null,
+ stackalloc[] { PipelineStageFlags.PipelineStageColorAttachmentOutputBit },
+ null);
+
+ var memory = _imageAllocationAuto[_nextImage].GetUnsafe().Memory;
+ var presentInfo = new PresentImageInfo(image.GetUnsafe().Value, memory, _imageSizes[_nextImage], _imageOffsets[_nextImage], _renderFinishedSemaphore, _imageAvailableSemaphore);
+
+ swapBuffersCallback(presentInfo);
+
+ _nextImage %= ImageCount;
+ }
+
+ private unsafe void Transition(
+ CommandBuffer commandBuffer,
+ Image image,
+ AccessFlags srcAccess,
+ AccessFlags dstAccess,
+ ImageLayout srcLayout,
+ ImageLayout dstLayout)
+ {
+ var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, 1, 0, 1);
+
+ var barrier = new ImageMemoryBarrier()
+ {
+ 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.PipelineStageTopOfPipeBit,
+ PipelineStageFlags.PipelineStageAllCommandsBit,
+ 0,
+ 0,
+ null,
+ 0,
+ null,
+ 1,
+ 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)
+ {
+ if (_width != width || _height != height)
+ {
+ _recreateImages = true;
+ }
+
+ _width = width;
+ _height = height;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ unsafe
+ {
+ _gd.Api.DestroySemaphore(_device, _renderFinishedSemaphore, null);
+ _gd.Api.DestroySemaphore(_device, _imageAvailableSemaphore, null);
+
+ for (int i = 0; i < ImageCount; i++)
+ {
+ _imageViews[i]?.Dispose();
+ _imageAllocationAuto[i]?.Dispose();
+ _images[i]?.Dispose();
+ }
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ }
+ }
+
+ public class PresentImageInfo
+ {
+ public Image Image { get; }
+ public DeviceMemory Memory { get; }
+ public ulong MemorySize { get; set; }
+ public ulong MemoryOffset { get; set; }
+ public Semaphore ReadySemaphore { get; }
+ public Semaphore AvailableSemaphore { get; }
+
+ public PresentImageInfo(Image image, DeviceMemory memory, ulong memorySize, ulong memoryOffset, Semaphore readySemaphore, Semaphore availableSemaphore)
+ {
+ this.Image = image;
+ this.Memory = memory;
+ this.MemorySize = memorySize;
+ this.MemoryOffset = memoryOffset;
+ this.ReadySemaphore = readySemaphore;
+ this.AvailableSemaphore = availableSemaphore;
+ }
+ }
+} \ No newline at end of file