sharpemu/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Presen...

378 lines
16 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Libs.VideoOut;
using SharpEmu.ShaderCompiler.Vulkan;
using Silk.NET.Core.Native;
using SharpEmu.Libs.Gpu.Vulkan;
using Silk.NET.Vulkan;
internal static unsafe partial class VulkanVideoPresenter
{
private sealed partial class Presenter
{
// This partial owns HDR presentation resources.
private void CreateHdrPresentationResources()
{
var colorAttachment = new AttachmentDescription
{
Format = _swapchainFormat,
Samples = SampleCountFlags.Count1Bit,
LoadOp = AttachmentLoadOp.Clear,
StoreOp = AttachmentStoreOp.Store,
StencilLoadOp = AttachmentLoadOp.DontCare,
StencilStoreOp = AttachmentStoreOp.DontCare,
InitialLayout = ImageLayout.Undefined,
FinalLayout = ImageLayout.PresentSrcKhr,
};
var colorReference = new AttachmentReference(0, ImageLayout.ColorAttachmentOptimal);
var subpass = new SubpassDescription
{
PipelineBindPoint = PipelineBindPoint.Graphics,
ColorAttachmentCount = 1,
PColorAttachments = &colorReference,
};
var dependency = new SubpassDependency
{
SrcSubpass = Vk.SubpassExternal,
DstSubpass = 0,
SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit,
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
};
var renderPassInfo = new RenderPassCreateInfo
{
SType = StructureType.RenderPassCreateInfo,
AttachmentCount = 1,
PAttachments = &colorAttachment,
SubpassCount = 1,
PSubpasses = &subpass,
DependencyCount = 1,
PDependencies = &dependency,
};
Check(
_vk.CreateRenderPass(_device, &renderPassInfo, null, out _hdrRenderPass),
"vkCreateRenderPass(HDR presentation)");
_hdrFramebuffers = new Framebuffer[_swapchainImageViews.Length];
for (var index = 0; index < _swapchainImageViews.Length; index++)
{
var view = _swapchainImageViews[index];
var framebufferInfo = new FramebufferCreateInfo
{
SType = StructureType.FramebufferCreateInfo,
RenderPass = _hdrRenderPass,
AttachmentCount = 1,
PAttachments = &view,
Width = _extent.Width,
Height = _extent.Height,
Layers = 1,
};
Check(
_vk.CreateFramebuffer(
_device,
&framebufferInfo,
null,
out _hdrFramebuffers[index]),
"vkCreateFramebuffer(HDR presentation)");
}
var binding = new DescriptorSetLayoutBinding
{
Binding = 1,
DescriptorType = DescriptorType.CombinedImageSampler,
DescriptorCount = 1,
StageFlags = ShaderStageFlags.FragmentBit,
};
var descriptorLayoutInfo = new DescriptorSetLayoutCreateInfo
{
SType = StructureType.DescriptorSetLayoutCreateInfo,
BindingCount = 1,
PBindings = &binding,
};
Check(
_vk.CreateDescriptorSetLayout(
_device,
&descriptorLayoutInfo,
null,
out _hdrDescriptorSetLayout),
"vkCreateDescriptorSetLayout(HDR presentation)");
var descriptorPoolSize = new DescriptorPoolSize
{
Type = DescriptorType.CombinedImageSampler,
DescriptorCount = checked((uint)_presentationSampleViews.Length * 2),
};
var descriptorPoolInfo = new DescriptorPoolCreateInfo
{
SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = checked((uint)_presentationSampleViews.Length * 2),
PoolSizeCount = 1,
PPoolSizes = &descriptorPoolSize,
};
Check(
_vk.CreateDescriptorPool(
_device,
&descriptorPoolInfo,
null,
out _hdrDescriptorPool),
"vkCreateDescriptorPool(HDR presentation)");
var samplerInfo = new SamplerCreateInfo
{
SType = StructureType.SamplerCreateInfo,
MagFilter = Filter.Linear,
MinFilter = Filter.Linear,
MipmapMode = SamplerMipmapMode.Linear,
AddressModeU = SamplerAddressMode.ClampToEdge,
AddressModeV = SamplerAddressMode.ClampToEdge,
AddressModeW = SamplerAddressMode.ClampToEdge,
MaxLod = 1f,
};
Check(
_vk.CreateSampler(_device, &samplerInfo, null, out _hdrSampler),
"vkCreateSampler(HDR presentation)");
_hdrDescriptorSets = new DescriptorSet[_presentationSampleViews.Length];
_hdrPqDescriptorSets = new DescriptorSet[_presentationSampleViews.Length];
for (var pq = 0; pq < 2; pq++)
{
var descriptorSets = pq == 0 ? _hdrDescriptorSets : _hdrPqDescriptorSets;
var imageViews = pq == 0 ? _presentationSampleViews : _presentationImageViews;
for (var index = 0; index < descriptorSets.Length; index++)
{
var layout = _hdrDescriptorSetLayout;
var allocateInfo = new DescriptorSetAllocateInfo
{
SType = StructureType.DescriptorSetAllocateInfo,
DescriptorPool = _hdrDescriptorPool,
DescriptorSetCount = 1,
PSetLayouts = &layout,
};
Check(
_vk.AllocateDescriptorSets(
_device,
&allocateInfo,
out descriptorSets[index]),
"vkAllocateDescriptorSets(HDR presentation)");
var imageInfo = new DescriptorImageInfo
{
Sampler = _hdrSampler,
ImageView = imageViews[index],
ImageLayout = ImageLayout.ShaderReadOnlyOptimal,
};
var write = new WriteDescriptorSet
{
SType = StructureType.WriteDescriptorSet,
DstSet = descriptorSets[index],
DstBinding = 1,
DescriptorCount = 1,
DescriptorType = DescriptorType.CombinedImageSampler,
PImageInfo = &imageInfo,
};
_vk.UpdateDescriptorSets(_device, 1, &write, 0, null);
}
}
var descriptorSetLayout = _hdrDescriptorSetLayout;
var pipelineLayoutInfo = new PipelineLayoutCreateInfo
{
SType = StructureType.PipelineLayoutCreateInfo,
SetLayoutCount = 1,
PSetLayouts = &descriptorSetLayout,
};
Check(
_vk.CreatePipelineLayout(
_device,
&pipelineLayoutInfo,
null,
out _hdrPipelineLayout),
"vkCreatePipelineLayout(HDR presentation)");
CreateHdrPresentationPipelines();
}
private void CreateHdrPresentationPipelines()
{
CreateHdrPresentationPipeline(
SpirvFixedShaders.CreateCopyFragment(_hdrSdrWhiteLevel),
"SDR",
out _hdrPipeline);
CreateHdrPresentationPipeline(
SpirvFixedShaders.CreatePqToScRgbFragment(),
"PQ",
out _hdrPqPipeline);
}
private void CreateHdrPresentationPipeline(
byte[] fragmentBytes,
string label,
out Pipeline pipeline)
{
var vertexModule = CreateShaderModule(SpirvFixedShaders.CreateFullscreenVertex(1));
var fragmentModule = CreateShaderModule(fragmentBytes);
var entryPoint = (byte*)SilkMarshal.StringToPtr("main");
try
{
var stages = stackalloc PipelineShaderStageCreateInfo[2];
stages[0] = new PipelineShaderStageCreateInfo
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.VertexBit,
Module = vertexModule,
PName = entryPoint,
};
stages[1] = new PipelineShaderStageCreateInfo
{
SType = StructureType.PipelineShaderStageCreateInfo,
Stage = ShaderStageFlags.FragmentBit,
Module = fragmentModule,
PName = entryPoint,
};
var vertexInput = new PipelineVertexInputStateCreateInfo
{
SType = StructureType.PipelineVertexInputStateCreateInfo,
};
var inputAssembly = new PipelineInputAssemblyStateCreateInfo
{
SType = StructureType.PipelineInputAssemblyStateCreateInfo,
Topology = PrimitiveTopology.TriangleList,
};
var viewport = new Viewport(0, 0, _extent.Width, _extent.Height, 0, 1);
var scissor = new Rect2D(new Offset2D(0, 0), _extent);
var viewportState = new PipelineViewportStateCreateInfo
{
SType = StructureType.PipelineViewportStateCreateInfo,
ViewportCount = 1,
PViewports = &viewport,
ScissorCount = 1,
PScissors = &scissor,
};
var rasterization = new PipelineRasterizationStateCreateInfo
{
SType = StructureType.PipelineRasterizationStateCreateInfo,
PolygonMode = PolygonMode.Fill,
CullMode = CullModeFlags.None,
FrontFace = FrontFace.CounterClockwise,
LineWidth = 1,
};
var multisample = new PipelineMultisampleStateCreateInfo
{
SType = StructureType.PipelineMultisampleStateCreateInfo,
RasterizationSamples = SampleCountFlags.Count1Bit,
};
var blendAttachment = new PipelineColorBlendAttachmentState
{
ColorWriteMask = ColorComponentFlags.RBit |
ColorComponentFlags.GBit |
ColorComponentFlags.BBit |
ColorComponentFlags.ABit,
};
var blend = new PipelineColorBlendStateCreateInfo
{
SType = StructureType.PipelineColorBlendStateCreateInfo,
AttachmentCount = 1,
PAttachments = &blendAttachment,
};
var pipelineInfo = new GraphicsPipelineCreateInfo
{
SType = StructureType.GraphicsPipelineCreateInfo,
StageCount = 2,
PStages = stages,
PVertexInputState = &vertexInput,
PInputAssemblyState = &inputAssembly,
PViewportState = &viewportState,
PRasterizationState = &rasterization,
PMultisampleState = &multisample,
PColorBlendState = &blend,
Layout = _hdrPipelineLayout,
RenderPass = _hdrRenderPass,
};
Check(
_vk.CreateGraphicsPipelines(
_device,
_pipelineCache,
1,
&pipelineInfo,
null,
out pipeline),
$"vkCreateGraphicsPipelines(HDR {label} presentation)");
MarkPipelineCacheDirty();
}
finally
{
SilkMarshal.Free((nint)entryPoint);
_vk.DestroyShaderModule(_device, fragmentModule, null);
_vk.DestroyShaderModule(_device, vertexModule, null);
}
}
private void RecordHdrPresentation(uint imageIndex, bool isHdr)
{
var sourceBarrier = new ImageMemoryBarrier2
{
SType = StructureType.ImageMemoryBarrier2,
SrcAccessMask = AccessFlags2.MemoryWriteBit |
AccessFlags2.TransferWriteBit |
AccessFlags2.ColorAttachmentWriteBit,
DstAccessMask = AccessFlags2.ShaderReadBit,
OldLayout = ImageLayout.ShaderReadOnlyOptimal,
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = _presentationImages[imageIndex],
SubresourceRange = ColorSubresourceRange(),
};
VulkanSynchronization.PipelineBarrier(_vk,
_commandBuffer,
PipelineStageFlags.AllCommandsBit,
PipelineStageFlags.FragmentShaderBit,
0,
0,
null,
0,
null,
1,
&sourceBarrier);
var clearValue = new ClearValue
{
Color = new ClearColorValue(0f, 0f, 0f, 1f),
};
var renderPassInfo = new RenderPassBeginInfo
{
SType = StructureType.RenderPassBeginInfo,
RenderPass = _hdrRenderPass,
Framebuffer = _hdrFramebuffers[imageIndex],
RenderArea = new Rect2D(new Offset2D(0, 0), _extent),
ClearValueCount = 1,
PClearValues = &clearValue,
};
_vk.CmdBeginRenderPass(
_commandBuffer,
&renderPassInfo,
SubpassContents.Inline);
_vk.CmdBindPipeline(
_commandBuffer,
PipelineBindPoint.Graphics,
isHdr ? _hdrPqPipeline : _hdrPipeline);
SharpEmu.Libs.Gpu.Vulkan.GraphicsDynamicStateEpoch.Advance();
var descriptorSet = isHdr
? _hdrPqDescriptorSets[imageIndex]
: _hdrDescriptorSets[imageIndex];
_vk.CmdBindDescriptorSets(
_commandBuffer,
PipelineBindPoint.Graphics,
_hdrPipelineLayout,
0,
1,
&descriptorSet,
0,
null);
_vk.CmdDraw(_commandBuffer, 3, 1, 0, 0);
_vk.CmdEndRenderPass(_commandBuffer);
}
}
}