mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
[AGC/Vulkan] Support multiple render targets (#149)
* [AGC] Support multiple typed pixel outputs Emit dense float, uint, and sint fragment outputs for sparse guest MRT slots. Preserve disabled components across partial exports, validate dense host locations, and retain the single-output compiler overload for compatibility. * [Vulkan] Execute translated draws with multiple color attachments Carry every active color target and its effective shader/register write mask through one Vulkan draw. Add per-attachment blending, independentBlend negotiation, device/format validation, multi-attachment synchronization, and safe image recreation after in-flight work completes. * [ShaderDump] Add MRT edge-case coverage Cover sparse mixed-type outputs, partial exports, merged partial exports, independent blend layouts, eight attachments, and invalid host locations. Run the synthetic shader suite in CI. --------- Co-authored-by: Dafenx <196083014+Dafenxz0@users.noreply.github.com>
This commit is contained in:
@@ -104,14 +104,17 @@ internal readonly record struct VulkanGuestBlendState(
|
||||
}
|
||||
|
||||
internal sealed record VulkanGuestRenderState(
|
||||
VulkanGuestBlendState Blend,
|
||||
IReadOnlyList<VulkanGuestBlendState> Blends,
|
||||
VulkanGuestRect? Scissor,
|
||||
VulkanGuestViewport? Viewport)
|
||||
{
|
||||
public static VulkanGuestRenderState Default { get; } = new(
|
||||
VulkanGuestBlendState.Default,
|
||||
[VulkanGuestBlendState.Default],
|
||||
Scissor: null,
|
||||
Viewport: null);
|
||||
|
||||
public VulkanGuestBlendState Blend =>
|
||||
Blends.Count == 0 ? VulkanGuestBlendState.Default : Blends[0];
|
||||
}
|
||||
|
||||
internal sealed record VulkanGuestRenderTarget(
|
||||
@@ -122,6 +125,13 @@ internal sealed record VulkanGuestRenderTarget(
|
||||
uint NumberType,
|
||||
uint MipLevels = 1);
|
||||
|
||||
internal readonly record struct VulkanRenderTargetFormat(
|
||||
Format Format,
|
||||
Gen5PixelOutputKind OutputKind)
|
||||
{
|
||||
public bool IsInteger => OutputKind is Gen5PixelOutputKind.Uint or Gen5PixelOutputKind.Sint;
|
||||
}
|
||||
|
||||
internal sealed record VulkanTranslatedGuestDraw(
|
||||
byte[] VertexSpirv,
|
||||
byte[] PixelSpirv,
|
||||
@@ -137,7 +147,7 @@ internal sealed record VulkanTranslatedGuestDraw(
|
||||
|
||||
internal sealed record VulkanOffscreenGuestDraw(
|
||||
VulkanTranslatedGuestDraw Draw,
|
||||
VulkanGuestRenderTarget Target,
|
||||
IReadOnlyList<VulkanGuestRenderTarget> Targets,
|
||||
bool PublishTarget);
|
||||
|
||||
internal sealed record VulkanComputeGuestDispatch(
|
||||
@@ -463,20 +473,70 @@ internal static unsafe class VulkanVideoPresenter
|
||||
VulkanGuestIndexBuffer? indexBuffer = null,
|
||||
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
||||
VulkanGuestRenderState? renderState = null)
|
||||
{
|
||||
SubmitOffscreenTranslatedDraw(
|
||||
pixelSpirv,
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
attributeCount,
|
||||
[target],
|
||||
vertexSpirv,
|
||||
vertexCount,
|
||||
instanceCount,
|
||||
primitiveType,
|
||||
indexBuffer,
|
||||
vertexBuffers,
|
||||
renderState);
|
||||
}
|
||||
|
||||
public static void SubmitOffscreenTranslatedDraw(
|
||||
byte[] pixelSpirv,
|
||||
IReadOnlyList<VulkanGuestDrawTexture> textures,
|
||||
IReadOnlyList<VulkanGuestMemoryBuffer> globalMemoryBuffers,
|
||||
uint attributeCount,
|
||||
IReadOnlyList<VulkanGuestRenderTarget> targets,
|
||||
byte[]? vertexSpirv = null,
|
||||
uint vertexCount = 3,
|
||||
uint instanceCount = 1,
|
||||
uint primitiveType = 4,
|
||||
VulkanGuestIndexBuffer? indexBuffer = null,
|
||||
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
||||
VulkanGuestRenderState? renderState = null)
|
||||
{
|
||||
if (pixelSpirv.Length == 0 ||
|
||||
target.Address == 0 ||
|
||||
target.Width == 0 ||
|
||||
target.Height == 0)
|
||||
targets.Count == 0 ||
|
||||
targets.Count > 8 ||
|
||||
targets.Any(target =>
|
||||
target.Address == 0 || target.Width == 0 || target.Height == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var firstTarget = targets[0];
|
||||
if (targets.Any(target =>
|
||||
target.Width != firstTarget.Width || target.Height != firstTarget.Height) ||
|
||||
targets.Select(target => target.Address).Distinct().Count() != targets.Count)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw with mismatched dimensions or aliased targets.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics())
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] vk.submit_call kind=SubmitOffscreenTranslatedDraw " +
|
||||
$"target=0x{target.Address:X16} {target.Width}x{target.Height} textures={textures.Count}");
|
||||
$"targets={targets.Count} first=0x{firstTarget.Address:X16} " +
|
||||
$"{firstTarget.Width}x{firstTarget.Height} textures={textures.Count}");
|
||||
}
|
||||
|
||||
var effectiveRenderState = renderState ?? VulkanGuestRenderState.Default;
|
||||
if (effectiveRenderState.Blends.Count == 1 && targets.Count > 1)
|
||||
{
|
||||
effectiveRenderState = effectiveRenderState with
|
||||
{
|
||||
Blends = Enumerable.Repeat(effectiveRenderState.Blends[0], targets.Count).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
@@ -486,12 +546,15 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
var guestTextureFormat = GetGuestTextureFormat(
|
||||
target.Format,
|
||||
target.NumberType);
|
||||
if (guestTextureFormat != 0)
|
||||
foreach (var target in targets)
|
||||
{
|
||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
||||
var guestTextureFormat = GetGuestTextureFormat(
|
||||
target.Format,
|
||||
target.NumberType);
|
||||
if (guestTextureFormat != 0)
|
||||
{
|
||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
||||
}
|
||||
}
|
||||
|
||||
EnqueueGuestWorkLocked(
|
||||
@@ -507,8 +570,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
instanceCount,
|
||||
primitiveType,
|
||||
indexBuffer,
|
||||
renderState ?? VulkanGuestRenderState.Default),
|
||||
target,
|
||||
effectiveRenderState),
|
||||
targets.ToArray(),
|
||||
PublishTarget: true));
|
||||
}
|
||||
}
|
||||
@@ -550,12 +613,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
4,
|
||||
null,
|
||||
VulkanGuestRenderState.Default),
|
||||
new VulkanGuestRenderTarget(
|
||||
[new VulkanGuestRenderTarget(
|
||||
Address: 0,
|
||||
width,
|
||||
height,
|
||||
Format: 12,
|
||||
NumberType: 7),
|
||||
NumberType: 7)],
|
||||
PublishTarget: false));
|
||||
}
|
||||
}
|
||||
@@ -785,25 +848,100 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return true;
|
||||
}
|
||||
|
||||
private static uint GetGuestTextureFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
private static uint GetGuestTextureFormat(uint format, uint numberType)
|
||||
{
|
||||
if (!TryDecodeRenderTargetFormat(format, numberType, out var decoded))
|
||||
{
|
||||
(9, _) => 9,
|
||||
(4, 4) => GuestFormatR32Uint,
|
||||
(4, 5) => GuestFormatR32Sint,
|
||||
(4, 7) => GuestFormatR32Sfloat,
|
||||
(5, 4) => GuestFormatR16G16Uint,
|
||||
(5, 5) => GuestFormatR16G16Sint,
|
||||
(5, 7) => GuestFormatR16G16Sfloat,
|
||||
(10, 4) => GuestFormatR8G8B8A8Uint,
|
||||
(10, 5) => GuestFormatR8G8B8A8Sint,
|
||||
(10, _) => 56,
|
||||
(12, 4) => GuestFormatR16G16B16A16Uint,
|
||||
(12, 5) => GuestFormatR16G16B16A16Sint,
|
||||
(12, 7) => 71,
|
||||
(_, 0) when IsKnownGuestTextureFormat(format) => format,
|
||||
return 0;
|
||||
}
|
||||
|
||||
return decoded.Format switch
|
||||
{
|
||||
Format.R8Unorm => 36,
|
||||
Format.R8Uint => 49,
|
||||
Format.R8G8Unorm => 3,
|
||||
Format.A2R10G10B10UnormPack32 => 9,
|
||||
Format.B10G11R11UfloatPack32 => 7,
|
||||
Format.R32Uint => GuestFormatR32Uint,
|
||||
Format.R32Sint => GuestFormatR32Sint,
|
||||
Format.R32Sfloat => GuestFormatR32Sfloat,
|
||||
Format.R16G16Unorm => 5,
|
||||
Format.R16G16Uint => GuestFormatR16G16Uint,
|
||||
Format.R16G16Sint => GuestFormatR16G16Sint,
|
||||
Format.R16G16Sfloat => GuestFormatR16G16Sfloat,
|
||||
Format.R32G32Sfloat => 75,
|
||||
Format.R8G8B8A8Unorm => 56,
|
||||
Format.R8G8B8A8Uint => GuestFormatR8G8B8A8Uint,
|
||||
Format.R8G8B8A8Sint => GuestFormatR8G8B8A8Sint,
|
||||
Format.R16G16B16A16Unorm => 12,
|
||||
Format.R16G16B16A16Uint => GuestFormatR16G16B16A16Uint,
|
||||
Format.R16G16B16A16Sint => GuestFormatR16G16B16A16Sint,
|
||||
Format.R16G16B16A16Sfloat => 71,
|
||||
Format.R32G32B32A32Sfloat => 14,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
internal static bool TryDecodeRenderTargetFormat(
|
||||
uint dataFormat,
|
||||
uint numberType,
|
||||
out VulkanRenderTargetFormat result)
|
||||
{
|
||||
var format = (dataFormat, numberType) switch
|
||||
{
|
||||
(4, 4) => Format.R32Uint,
|
||||
(4, 5) => Format.R32Sint,
|
||||
(4, 7) => Format.R32Sfloat,
|
||||
(5, 4) => Format.R16G16Uint,
|
||||
(5, 5) => Format.R16G16Sint,
|
||||
(5, 7) => Format.R16G16Sfloat,
|
||||
(9, _) => Format.A2R10G10B10UnormPack32,
|
||||
(10, 4) => Format.R8G8B8A8Uint,
|
||||
(10, 5) => Format.R8G8B8A8Sint,
|
||||
(10, _) => Format.R8G8B8A8Unorm,
|
||||
(12, 4) => Format.R16G16B16A16Uint,
|
||||
(12, 5) => Format.R16G16B16A16Sint,
|
||||
(12, 7) => Format.R16G16B16A16Sfloat,
|
||||
(GuestFormatR32Uint, _) or (20, 0) => Format.R32Uint,
|
||||
(GuestFormatR32Sint, _) => Format.R32Sint,
|
||||
(GuestFormatR32Sfloat, _) or (29, 0) or (4, 0) => Format.R32Sfloat,
|
||||
(GuestFormatR16G16Uint, _) => Format.R16G16Uint,
|
||||
(GuestFormatR16G16Sint, _) => Format.R16G16Sint,
|
||||
(GuestFormatR16G16Sfloat, _) => Format.R16G16Sfloat,
|
||||
(GuestFormatR8G8B8A8Uint, _) => Format.R8G8B8A8Uint,
|
||||
(GuestFormatR8G8B8A8Sint, _) => Format.R8G8B8A8Sint,
|
||||
(GuestFormatR16G16B16A16Uint, _) => Format.R16G16B16A16Uint,
|
||||
(GuestFormatR16G16B16A16Sint, _) => Format.R16G16B16A16Sint,
|
||||
(1, 0) or (36, 0) => Format.R8Unorm,
|
||||
(49, 0) => Format.R8Uint,
|
||||
(3, 0) => Format.R8G8Unorm,
|
||||
(5, 0) => Format.R16G16Unorm,
|
||||
(7, 0) => Format.B10G11R11UfloatPack32,
|
||||
(12, 0) => Format.R16G16B16A16Unorm,
|
||||
(13, 0) or (14, 0) => Format.R32G32B32A32Sfloat,
|
||||
(22, 0) or (71, 0) => Format.R16G16B16A16Sfloat,
|
||||
(56, 0) or (62, 0) or (64, 0) => Format.R8G8B8A8Unorm,
|
||||
(75, 0) => Format.R32G32Sfloat,
|
||||
_ => Format.Undefined,
|
||||
};
|
||||
|
||||
if (format == Format.Undefined)
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var outputKind = format switch
|
||||
{
|
||||
Format.R8Uint or Format.R32Uint or Format.R16G16Uint or
|
||||
Format.R8G8B8A8Uint or Format.R16G16B16A16Uint => Gen5PixelOutputKind.Uint,
|
||||
Format.R32Sint or Format.R16G16Sint or Format.R8G8B8A8Sint or
|
||||
Format.R16G16B16A16Sint => Gen5PixelOutputKind.Sint,
|
||||
_ => Gen5PixelOutputKind.Float,
|
||||
};
|
||||
result = new VulkanRenderTargetFormat(format, outputKind);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsKnownGuestTextureFormat(uint format) =>
|
||||
format is 4 or 5 or 7 or 9 or 13 or 14 or 22 or 29 or 36 or 56 or 62 or 64 or 71;
|
||||
@@ -948,6 +1086,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private DebugUtilsMessengerEXT _debugMessenger;
|
||||
private ExtDebugUtils? _debugUtils;
|
||||
private PhysicalDevice _physicalDevice;
|
||||
private bool _supportsIndependentBlend;
|
||||
private uint _maxColorAttachments;
|
||||
private Device _device;
|
||||
private PipelineCache _pipelineCache;
|
||||
private string? _pipelineCachePath;
|
||||
@@ -1024,9 +1164,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private readonly record struct GraphicsPipelineKey(
|
||||
string VertexShader,
|
||||
string FragmentShader,
|
||||
Format RenderTargetFormat,
|
||||
string RenderTargetLayout,
|
||||
PrimitiveTopology Topology,
|
||||
VulkanGuestBlendState Blend,
|
||||
string BlendLayout,
|
||||
string ResourceLayout,
|
||||
string VertexLayout);
|
||||
|
||||
@@ -1066,9 +1206,11 @@ internal static unsafe class VulkanVideoPresenter
|
||||
public uint VertexCount = 3;
|
||||
public uint InstanceCount = 1;
|
||||
public PrimitiveTopology Topology = PrimitiveTopology.TriangleList;
|
||||
public VulkanGuestBlendState Blend = VulkanGuestBlendState.Default;
|
||||
public VulkanGuestBlendState[] Blends = [VulkanGuestBlendState.Default];
|
||||
public VulkanGuestRect? Scissor;
|
||||
public VulkanGuestViewport? Viewport;
|
||||
public RenderPass TransientRenderPass;
|
||||
public Framebuffer TransientFramebuffer;
|
||||
}
|
||||
|
||||
private sealed class TextureResource
|
||||
@@ -1604,6 +1746,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
|
||||
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var selected);
|
||||
_maxColorAttachments = selected.Limits.MaxColorAttachments;
|
||||
var selectedName = SilkMarshal.PtrToString((nint)selected.DeviceName) ?? "unknown";
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][INFO] Vulkan device: {selectedName} ({selected.DeviceType})");
|
||||
@@ -1650,8 +1793,10 @@ internal static unsafe class VulkanVideoPresenter
|
||||
PQueuePriorities = &priority,
|
||||
};
|
||||
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
|
||||
_supportsIndependentBlend = supportedFeatures.IndependentBlend;
|
||||
var enabledFeatures = new PhysicalDeviceFeatures
|
||||
{
|
||||
IndependentBlend = supportedFeatures.IndependentBlend,
|
||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
||||
ShaderInt64 = supportedFeatures.ShaderInt64,
|
||||
@@ -2150,6 +2295,14 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
}
|
||||
|
||||
private void WaitForAllGuestSubmissions()
|
||||
{
|
||||
while (_pendingGuestSubmissions.Count != 0)
|
||||
{
|
||||
CollectCompletedGuestSubmissions(waitForOldest: true);
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectCompletedGuestSubmissions(bool waitForOldest)
|
||||
{
|
||||
if (waitForOldest && _pendingGuestSubmissions.TryPeek(out var oldest))
|
||||
@@ -2193,13 +2346,15 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private IReadOnlyList<GuestImageResource> GetTraceImages(
|
||||
TranslatedDrawResources resources,
|
||||
GuestImageResource? renderTarget = null)
|
||||
IReadOnlyList<GuestImageResource>? renderTargets = null)
|
||||
{
|
||||
var images = new HashSet<GuestImageResource>();
|
||||
if (renderTarget is not null &&
|
||||
ShouldTraceGuestImageContents(renderTarget))
|
||||
foreach (var renderTarget in renderTargets ?? [])
|
||||
{
|
||||
images.Add(renderTarget);
|
||||
if (ShouldTraceGuestImageContents(renderTarget))
|
||||
{
|
||||
images.Add(renderTarget);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var texture in resources.Textures)
|
||||
@@ -2430,10 +2585,16 @@ internal static unsafe class VulkanVideoPresenter
|
||||
private TranslatedDrawResources CreateTranslatedDrawResources(
|
||||
VulkanTranslatedGuestDraw draw,
|
||||
RenderPass renderPass,
|
||||
Format renderTargetFormat,
|
||||
IReadOnlyList<Format> renderTargetFormats,
|
||||
Extent2D extent)
|
||||
{
|
||||
var vertexSpirv = draw.VertexSpirv;
|
||||
if (draw.RenderState.Blends.Count != renderTargetFormats.Count)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"color attachment formats and blend states must have matching counts");
|
||||
}
|
||||
|
||||
if (vertexSpirv.Length == 0 &&
|
||||
!TryCompileFullscreenVertexShader(
|
||||
draw.AttributeCount,
|
||||
@@ -2453,7 +2614,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
VertexCount = GetDrawVertexCount(draw.PrimitiveType, draw.VertexCount, draw.IndexBuffer),
|
||||
InstanceCount = Math.Max(draw.InstanceCount, 1),
|
||||
Topology = GetPrimitiveTopology(draw.PrimitiveType),
|
||||
Blend = draw.RenderState.Blend,
|
||||
Blends = draw.RenderState.Blends.ToArray(),
|
||||
Scissor = draw.RenderState.Scissor,
|
||||
Viewport = draw.RenderState.Viewport,
|
||||
};
|
||||
@@ -2502,7 +2663,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
vertexSpirv,
|
||||
draw.PixelSpirv,
|
||||
renderPass,
|
||||
renderTargetFormat,
|
||||
renderTargetFormats,
|
||||
extent);
|
||||
return resources;
|
||||
}
|
||||
@@ -2799,15 +2960,18 @@ internal static unsafe class VulkanVideoPresenter
|
||||
byte[] vertexSpirv,
|
||||
byte[] fragmentSpirv,
|
||||
RenderPass renderPass,
|
||||
Format renderTargetFormat,
|
||||
IReadOnlyList<Format> renderTargetFormats,
|
||||
Extent2D extent)
|
||||
{
|
||||
var pipelineKey = new GraphicsPipelineKey(
|
||||
GetShaderDigest(vertexSpirv),
|
||||
GetShaderDigest(fragmentSpirv),
|
||||
renderTargetFormat,
|
||||
string.Join(',', renderTargetFormats.Select(format => (uint)format)),
|
||||
resources.Topology,
|
||||
resources.Blend,
|
||||
string.Join(';', resources.Blends.Select(blend =>
|
||||
$"{(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}:{blend.ColorDstFactor}:" +
|
||||
$"{blend.ColorFunc}:{blend.AlphaSrcFactor}:{blend.AlphaDstFactor}:" +
|
||||
$"{blend.AlphaFunc}:{(blend.SeparateAlphaBlend ? 1 : 0)}:{blend.WriteMask}")),
|
||||
GetResourceLayoutKey(resources),
|
||||
GetVertexLayoutKey(resources));
|
||||
if (_graphicsPipelines.TryGetValue(pipelineKey, out var cachedPipeline))
|
||||
@@ -2908,29 +3072,33 @@ internal static unsafe class VulkanVideoPresenter
|
||||
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||
RasterizationSamples = SampleCountFlags.Count1Bit,
|
||||
};
|
||||
var colorBlendAttachment = new PipelineColorBlendAttachmentState
|
||||
var colorBlendAttachments = stackalloc PipelineColorBlendAttachmentState[resources.Blends.Length];
|
||||
for (var index = 0; index < resources.Blends.Length; index++)
|
||||
{
|
||||
BlendEnable = resources.Blend.Enable,
|
||||
SrcColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorSrcFactor),
|
||||
DstColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorDstFactor),
|
||||
ColorBlendOp = ToVkBlendOp(resources.Blend.ColorFunc),
|
||||
SrcAlphaBlendFactor = resources.Blend.SeparateAlphaBlend
|
||||
? ToVkBlendFactor(resources.Blend.AlphaSrcFactor)
|
||||
: ToVkBlendFactor(resources.Blend.ColorSrcFactor),
|
||||
DstAlphaBlendFactor = resources.Blend.SeparateAlphaBlend
|
||||
? ToVkBlendFactor(resources.Blend.AlphaDstFactor)
|
||||
: ToVkBlendFactor(resources.Blend.ColorDstFactor),
|
||||
AlphaBlendOp = resources.Blend.SeparateAlphaBlend
|
||||
? ToVkBlendOp(resources.Blend.AlphaFunc)
|
||||
: ToVkBlendOp(resources.Blend.ColorFunc),
|
||||
ColorWriteMask =
|
||||
ToVkColorWriteMask(resources.Blend.WriteMask),
|
||||
};
|
||||
var blend = resources.Blends[index];
|
||||
colorBlendAttachments[index] = new PipelineColorBlendAttachmentState
|
||||
{
|
||||
BlendEnable = blend.Enable,
|
||||
SrcColorBlendFactor = ToVkBlendFactor(blend.ColorSrcFactor),
|
||||
DstColorBlendFactor = ToVkBlendFactor(blend.ColorDstFactor),
|
||||
ColorBlendOp = ToVkBlendOp(blend.ColorFunc),
|
||||
SrcAlphaBlendFactor = blend.SeparateAlphaBlend
|
||||
? ToVkBlendFactor(blend.AlphaSrcFactor)
|
||||
: ToVkBlendFactor(blend.ColorSrcFactor),
|
||||
DstAlphaBlendFactor = blend.SeparateAlphaBlend
|
||||
? ToVkBlendFactor(blend.AlphaDstFactor)
|
||||
: ToVkBlendFactor(blend.ColorDstFactor),
|
||||
AlphaBlendOp = blend.SeparateAlphaBlend
|
||||
? ToVkBlendOp(blend.AlphaFunc)
|
||||
: ToVkBlendOp(blend.ColorFunc),
|
||||
ColorWriteMask = ToVkColorWriteMask(blend.WriteMask),
|
||||
};
|
||||
}
|
||||
var colorBlend = new PipelineColorBlendStateCreateInfo
|
||||
{
|
||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &colorBlendAttachment,
|
||||
AttachmentCount = (uint)resources.Blends.Length,
|
||||
PAttachments = colorBlendAttachments,
|
||||
};
|
||||
var dynamicStateValues = stackalloc DynamicState[2];
|
||||
dynamicStateValues[0] = DynamicState.Viewport;
|
||||
@@ -4372,6 +4540,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
|
||||
}
|
||||
|
||||
private bool SupportsColorAttachment(Format format)
|
||||
{
|
||||
_vk.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var properties);
|
||||
return (properties.OptimalTilingFeatures & FormatFeatureFlags.ColorAttachmentBit) != 0;
|
||||
}
|
||||
|
||||
private static Format GetTextureFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
{
|
||||
@@ -4434,26 +4608,6 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_ => Format.R8G8B8A8Unorm,
|
||||
};
|
||||
|
||||
private static Format GetRenderTargetFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
{
|
||||
(4, 4) => Format.R32Uint,
|
||||
(4, 5) => Format.R32Sint,
|
||||
(4, 7) => Format.R32Sfloat,
|
||||
(5, 4) => Format.R16G16Uint,
|
||||
(5, 5) => Format.R16G16Sint,
|
||||
(5, 7) => Format.R16G16Sfloat,
|
||||
(9, _) => Format.A2R10G10B10UnormPack32,
|
||||
(10, 4) => Format.R8G8B8A8Uint,
|
||||
(10, 5) => Format.R8G8B8A8Sint,
|
||||
(10, _) => Format.R8G8B8A8Unorm,
|
||||
(12, 4) => Format.R16G16B16A16Uint,
|
||||
(12, 5) => Format.R16G16B16A16Sint,
|
||||
(12, 7) => Format.R16G16B16A16Sfloat,
|
||||
(_, 0) => GetTextureFormat(format, numberType),
|
||||
_ => Format.Undefined,
|
||||
};
|
||||
|
||||
private static bool IsBlockCompressedFormat(Format format) =>
|
||||
format is Format.BC1RgbaUnormBlock or
|
||||
Format.BC1RgbaSrgbBlock or
|
||||
@@ -4694,47 +4848,114 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private void ExecuteOffscreenDraw(VulkanOffscreenGuestDraw work)
|
||||
{
|
||||
if (_deviceLost)
|
||||
if (_deviceLost || work.Targets.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var format = GetRenderTargetFormat(work.Target.Format, work.Target.NumberType);
|
||||
if (format == Format.Undefined)
|
||||
if (work.Targets.Count > _maxColorAttachments)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan skipped unsupported render target " +
|
||||
$"addr=0x{work.Target.Address:X16} format={work.Target.Format} " +
|
||||
$"number={work.Target.NumberType}");
|
||||
$"[LOADER][WARN] Vulkan skipped MRT draw requesting {work.Targets.Count} color attachments; " +
|
||||
$"the selected device supports {_maxColorAttachments}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetFormats = new VulkanRenderTargetFormat[work.Targets.Count];
|
||||
for (var index = 0; index < targetFormats.Length; index++)
|
||||
{
|
||||
var target = work.Targets[index];
|
||||
if (!TryDecodeRenderTargetFormat(target.Format, target.NumberType, out targetFormats[index]) ||
|
||||
!SupportsColorAttachment(targetFormats[index].Format))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan skipped MRT draw with unsupported color target " +
|
||||
$"format={target.Format} number_type={target.NumberType}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (work.Draw.RenderState.Blends.Count != targetFormats.Length)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw with mismatched attachment/blend counts.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetFormats
|
||||
.Select((format, index) => format.IsInteger && work.Draw.RenderState.Blends[index].Enable)
|
||||
.Any(invalid => invalid))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw with blending enabled for an integer attachment.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_supportsIndependentBlend &&
|
||||
work.Draw.RenderState.Blends.Skip(1).Any(blend => blend != work.Draw.RenderState.Blends[0]))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
"[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend.");
|
||||
return;
|
||||
}
|
||||
|
||||
var formats = targetFormats.Select(target => target.Format).ToArray();
|
||||
|
||||
var targetAddresses = work.Targets
|
||||
.Where(target => target.Address != 0)
|
||||
.Select(target => target.Address)
|
||||
.ToHashSet();
|
||||
if (work.Draw.Textures.Any(texture =>
|
||||
texture.Address == work.Target.Address &&
|
||||
texture.Address != 0))
|
||||
targetAddresses.Contains(texture.Address)))
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] Vulkan skipped render-target feedback loop " +
|
||||
$"addr=0x{work.Target.Address:X16}");
|
||||
$"targets={string.Join(',', targetAddresses.Select(address => $"0x{address:X16}"))}");
|
||||
return;
|
||||
}
|
||||
|
||||
var target = GetOrCreateGuestImage(work.Target, format);
|
||||
var targets = new GuestImageResource[work.Targets.Count];
|
||||
EnsureGuestSubmissionCapacity();
|
||||
for (var index = 0; index < targets.Length; index++)
|
||||
{
|
||||
targets[index] = GetOrCreateGuestImage(work.Targets[index], formats[index]);
|
||||
}
|
||||
|
||||
var firstTarget = targets[0];
|
||||
TranslatedDrawResources? resources = null;
|
||||
CommandBuffer commandBuffer = default;
|
||||
var submitted = false;
|
||||
RenderPass transientRenderPass = default;
|
||||
Framebuffer transientFramebuffer = default;
|
||||
try
|
||||
{
|
||||
EnsureGuestSubmissionCapacity();
|
||||
var extent = new Extent2D(target.Width, target.Height);
|
||||
var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
|
||||
var renderPass = firstTarget.RenderPass;
|
||||
var framebuffer = firstTarget.Framebuffer;
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
(renderPass, framebuffer) = CreateRenderPassAndFramebuffer(
|
||||
formats,
|
||||
targets.Select(target => target.MipViews[0]).ToArray(),
|
||||
firstTarget.Width,
|
||||
firstTarget.Height);
|
||||
transientRenderPass = renderPass;
|
||||
transientFramebuffer = framebuffer;
|
||||
}
|
||||
|
||||
resources = CreateTranslatedDrawResources(
|
||||
work.Draw,
|
||||
target.RenderPass,
|
||||
target.Format,
|
||||
renderPass,
|
||||
formats,
|
||||
extent);
|
||||
resources.TransientRenderPass = transientRenderPass;
|
||||
resources.TransientFramebuffer = transientFramebuffer;
|
||||
transientRenderPass = default;
|
||||
transientFramebuffer = default;
|
||||
resources.DebugName =
|
||||
$"SharpEmu offscreen rt=0x{work.Target.Address:X16} " +
|
||||
$"{work.Target.Width}x{work.Target.Height} fmt{work.Target.Format}";
|
||||
$"SharpEmu offscreen mrt={targets.Length} " +
|
||||
$"first=0x{work.Targets[0].Address:X16} " +
|
||||
$"{firstTarget.Width}x{firstTarget.Height}";
|
||||
|
||||
commandBuffer = AllocateGuestCommandBuffer();
|
||||
_commandBuffer = commandBuffer;
|
||||
@@ -4751,24 +4972,30 @@ internal static unsafe class VulkanVideoPresenter
|
||||
RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit);
|
||||
RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit);
|
||||
|
||||
var targetHasPriorContents = target.Initialized || target.InitialUploadPending;
|
||||
var toColorAttachment = new ImageMemoryBarrier
|
||||
var toColorAttachments = stackalloc ImageMemoryBarrier[targets.Length];
|
||||
var anyPriorContents = false;
|
||||
for (var index = 0; index < targets.Length; index++)
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = targetHasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||
OldLayout = targetHasPriorContents
|
||||
? ImageLayout.ShaderReadOnlyOptimal
|
||||
: ImageLayout.Undefined,
|
||||
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = target.Image,
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
var hasPriorContents = targets[index].Initialized || targets[index].InitialUploadPending;
|
||||
anyPriorContents |= hasPriorContents;
|
||||
toColorAttachments[index] = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = hasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||
OldLayout = hasPriorContents
|
||||
? ImageLayout.ShaderReadOnlyOptimal
|
||||
: ImageLayout.Undefined,
|
||||
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = targets[index].Image,
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
}
|
||||
_vk.CmdPipelineBarrier(
|
||||
_commandBuffer,
|
||||
targetHasPriorContents
|
||||
anyPriorContents
|
||||
? PipelineStageFlags.AllCommandsBit
|
||||
: PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
@@ -4777,28 +5004,32 @@ internal static unsafe class VulkanVideoPresenter
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&toColorAttachment);
|
||||
(uint)targets.Length,
|
||||
toColorAttachments);
|
||||
|
||||
RecordTranslatedGraphicsPass(
|
||||
resources,
|
||||
target.RenderPass,
|
||||
target.Framebuffer,
|
||||
renderPass,
|
||||
framebuffer,
|
||||
extent);
|
||||
RecordStorageImagesForRead(resources, PipelineStageFlags.FragmentShaderBit);
|
||||
|
||||
var toShaderRead = new ImageMemoryBarrier
|
||||
var toShaderRead = stackalloc ImageMemoryBarrier[targets.Length];
|
||||
for (var index = 0; index < targets.Length; index++)
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||
DstAccessMask = AccessFlags.ShaderReadBit,
|
||||
OldLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = target.Image,
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
toShaderRead[index] = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||
DstAccessMask = AccessFlags.ShaderReadBit,
|
||||
OldLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = targets[index].Image,
|
||||
SubresourceRange = ColorSubresourceRange(),
|
||||
};
|
||||
}
|
||||
_vk.CmdPipelineBarrier(
|
||||
_commandBuffer,
|
||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||
@@ -4808,55 +5039,70 @@ internal static unsafe class VulkanVideoPresenter
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&toShaderRead);
|
||||
(uint)targets.Length,
|
||||
toShaderRead);
|
||||
EndDebugLabel(_commandBuffer);
|
||||
|
||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(offscreen)");
|
||||
SubmitGuestCommandBuffer(
|
||||
commandBuffer,
|
||||
resources,
|
||||
GetTraceImages(resources, target));
|
||||
GetTraceImages(resources, targets));
|
||||
submitted = true;
|
||||
target.Initialized = true;
|
||||
foreach (var target in targets)
|
||||
{
|
||||
target.Initialized = true;
|
||||
}
|
||||
MarkSampledImagesInitialized(resources);
|
||||
MarkStorageImagesInitialized(resources, traceContents: false);
|
||||
|
||||
var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
|
||||
work.Target.Format,
|
||||
work.Target.NumberType);
|
||||
if (work.PublishTarget && guestTextureFormat != 0)
|
||||
if (work.PublishTarget)
|
||||
{
|
||||
lock (_gate)
|
||||
for (var index = 0; index < targets.Length; index++)
|
||||
{
|
||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
||||
_gpuGuestImages[target.Address] = guestTextureFormat;
|
||||
var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
|
||||
work.Targets[index].Format,
|
||||
work.Targets[index].NumberType);
|
||||
if (guestTextureFormat == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
_availableGuestImages[targets[index].Address] = guestTextureFormat;
|
||||
_gpuGuestImages[targets[index].Address] = guestTextureFormat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var writeCount = _tracedGuestWriteCounts.TryGetValue(
|
||||
target.Address,
|
||||
out var previousCount)
|
||||
? previousCount + 1
|
||||
: 1;
|
||||
_tracedGuestWriteCounts[target.Address] = writeCount;
|
||||
if (writeCount <= 3)
|
||||
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
|
||||
{
|
||||
_commandBuffer = _presentationCommandBuffer;
|
||||
Check(
|
||||
_vk.QueueWaitIdle(_queue),
|
||||
"vkQueueWaitIdle(guest write trace)");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] vk.guest_write_sample " +
|
||||
$"addr=0x{target.Address:X16} write={writeCount} " +
|
||||
$"ps_bytes={work.Draw.PixelSpirv.Length}");
|
||||
TraceGuestImageContents(target);
|
||||
var writeCount = _tracedGuestWriteCounts.TryGetValue(
|
||||
target.Address,
|
||||
out var previousCount)
|
||||
? previousCount + 1
|
||||
: 1;
|
||||
_tracedGuestWriteCounts[target.Address] = writeCount;
|
||||
if (writeCount <= 3)
|
||||
{
|
||||
_commandBuffer = _presentationCommandBuffer;
|
||||
Check(
|
||||
_vk.QueueWaitIdle(_queue),
|
||||
"vkQueueWaitIdle(guest write trace)");
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][TRACE] vk.guest_write_sample " +
|
||||
$"addr=0x{target.Address:X16} write={writeCount} " +
|
||||
$"ps_bytes={work.Draw.PixelSpirv.Length}");
|
||||
TraceGuestImageContents(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
TraceVulkanShader(
|
||||
$"vk.offscreen_draw addr=0x{target.Address:X16} " +
|
||||
$"size={target.Width}x{target.Height} format={target.Format} " +
|
||||
$"vk.offscreen_draw mrt={targets.Length} " +
|
||||
$"size={firstTarget.Width}x{firstTarget.Height} " +
|
||||
$"textures={work.Draw.Textures.Count}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
@@ -4866,19 +5112,22 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_guestImages.TryGetValue(work.Target.Address, out var failedTarget) ||
|
||||
!failedTarget.Initialized)
|
||||
lock (_gate)
|
||||
{
|
||||
lock (_gate)
|
||||
foreach (var target in work.Targets)
|
||||
{
|
||||
_availableGuestImages.Remove(work.Target.Address);
|
||||
_gpuGuestImages.Remove(work.Target.Address);
|
||||
if (!_guestImages.TryGetValue(target.Address, out var failedTarget) ||
|
||||
!failedTarget.Initialized)
|
||||
{
|
||||
_availableGuestImages.Remove(target.Address);
|
||||
_gpuGuestImages.Remove(target.Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][ERROR] Vulkan offscreen draw failed " +
|
||||
$"addr=0x{work.Target.Address:X16}: {exception.Message}");
|
||||
$"mrt={work.Targets.Count}: {exception.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -4896,6 +5145,16 @@ internal static unsafe class VulkanVideoPresenter
|
||||
{
|
||||
DestroyTranslatedDrawResources(resources);
|
||||
}
|
||||
|
||||
if (transientFramebuffer.Handle != 0)
|
||||
{
|
||||
_vk.DestroyFramebuffer(_device, transientFramebuffer, null);
|
||||
}
|
||||
|
||||
if (transientRenderPass.Handle != 0)
|
||||
{
|
||||
_vk.DestroyRenderPass(_device, transientRenderPass, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4934,6 +5193,8 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return existing;
|
||||
}
|
||||
|
||||
CompletePendingPresentation(wait: true);
|
||||
WaitForAllGuestSubmissions();
|
||||
DestroyGuestImage(existing);
|
||||
_guestImages.Remove(target.Address);
|
||||
lock (_gate)
|
||||
@@ -5052,35 +5313,55 @@ internal static unsafe class VulkanVideoPresenter
|
||||
Format format,
|
||||
ImageView attachmentView,
|
||||
uint width,
|
||||
uint height) =>
|
||||
CreateRenderPassAndFramebuffer([format], [attachmentView], width, height);
|
||||
|
||||
private (RenderPass RenderPass, Framebuffer Framebuffer) CreateRenderPassAndFramebuffer(
|
||||
IReadOnlyList<Format> formats,
|
||||
IReadOnlyList<ImageView> attachmentViews,
|
||||
uint width,
|
||||
uint height)
|
||||
{
|
||||
var colorAttachment = new AttachmentDescription
|
||||
if (formats.Count == 0 || formats.Count != attachmentViews.Count)
|
||||
{
|
||||
Format = format,
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
LoadOp = AttachmentLoadOp.Load,
|
||||
StoreOp = AttachmentStoreOp.Store,
|
||||
StencilLoadOp = AttachmentLoadOp.DontCare,
|
||||
StencilStoreOp = AttachmentStoreOp.DontCare,
|
||||
InitialLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
FinalLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
};
|
||||
var colorReference = new AttachmentReference
|
||||
throw new InvalidOperationException("render target formats and views must have matching counts");
|
||||
}
|
||||
|
||||
var colorAttachments = stackalloc AttachmentDescription[formats.Count];
|
||||
var colorReferences = stackalloc AttachmentReference[formats.Count];
|
||||
var views = stackalloc ImageView[formats.Count];
|
||||
for (var index = 0; index < formats.Count; index++)
|
||||
{
|
||||
Attachment = 0,
|
||||
Layout = ImageLayout.ColorAttachmentOptimal,
|
||||
};
|
||||
colorAttachments[index] = new AttachmentDescription
|
||||
{
|
||||
Format = formats[index],
|
||||
Samples = SampleCountFlags.Count1Bit,
|
||||
LoadOp = AttachmentLoadOp.Load,
|
||||
StoreOp = AttachmentStoreOp.Store,
|
||||
StencilLoadOp = AttachmentLoadOp.DontCare,
|
||||
StencilStoreOp = AttachmentStoreOp.DontCare,
|
||||
InitialLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
FinalLayout = ImageLayout.ColorAttachmentOptimal,
|
||||
};
|
||||
colorReferences[index] = new AttachmentReference
|
||||
{
|
||||
Attachment = (uint)index,
|
||||
Layout = ImageLayout.ColorAttachmentOptimal,
|
||||
};
|
||||
views[index] = attachmentViews[index];
|
||||
}
|
||||
|
||||
var subpass = new SubpassDescription
|
||||
{
|
||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||
ColorAttachmentCount = 1,
|
||||
PColorAttachments = &colorReference,
|
||||
ColorAttachmentCount = (uint)formats.Count,
|
||||
PColorAttachments = colorReferences,
|
||||
};
|
||||
var renderPassInfo = new RenderPassCreateInfo
|
||||
{
|
||||
SType = StructureType.RenderPassCreateInfo,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &colorAttachment,
|
||||
AttachmentCount = (uint)formats.Count,
|
||||
PAttachments = colorAttachments,
|
||||
SubpassCount = 1,
|
||||
PSubpasses = &subpass,
|
||||
};
|
||||
@@ -5088,13 +5369,12 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
||||
"vkCreateRenderPass(offscreen)");
|
||||
|
||||
var attachment = attachmentView;
|
||||
var framebufferInfo = new FramebufferCreateInfo
|
||||
{
|
||||
SType = StructureType.FramebufferCreateInfo,
|
||||
RenderPass = renderPass,
|
||||
AttachmentCount = 1,
|
||||
PAttachments = &attachment,
|
||||
AttachmentCount = (uint)formats.Count,
|
||||
PAttachments = views,
|
||||
Width = width,
|
||||
Height = height,
|
||||
Layers = 1,
|
||||
@@ -5632,7 +5912,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
translatedResources = CreateTranslatedDrawResources(
|
||||
translatedDraw,
|
||||
_renderPass,
|
||||
_swapchainFormat,
|
||||
[_swapchainFormat],
|
||||
_extent);
|
||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||
!_firstGuestDrawPresented)
|
||||
@@ -6527,6 +6807,16 @@ internal static unsafe class VulkanVideoPresenter
|
||||
|
||||
private void DestroyTranslatedDrawResources(TranslatedDrawResources resources)
|
||||
{
|
||||
if (resources.TransientFramebuffer.Handle != 0)
|
||||
{
|
||||
_vk.DestroyFramebuffer(_device, resources.TransientFramebuffer, null);
|
||||
}
|
||||
|
||||
if (resources.TransientRenderPass.Handle != 0)
|
||||
{
|
||||
_vk.DestroyRenderPass(_device, resources.TransientRenderPass, null);
|
||||
}
|
||||
|
||||
foreach (var texture in resources.Textures)
|
||||
{
|
||||
if (texture is null)
|
||||
|
||||
Reference in New Issue
Block a user