mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-24 03:38:52 +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:
@@ -100,6 +100,9 @@ jobs:
|
|||||||
- name: Build solution
|
- name: Build solution
|
||||||
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
run: dotnet build SharpEmu.slnx -c Release --no-restore
|
||||||
|
|
||||||
|
- name: Validate synthetic shaders
|
||||||
|
run: dotnet run --project tools/SharpEmu.Tools.ShaderDump/SharpEmu.Tools.ShaderDump.csproj -c Release -- artifacts/shader-dump
|
||||||
|
|
||||||
- name: Publish win-x64 CLI
|
- name: Publish win-x64 CLI
|
||||||
run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r win-x64 --self-contained true --no-restore -p:PublishDir="${env:PUBLISH_DIR}"
|
run: dotnet publish src/SharpEmu.CLI/SharpEmu.CLI.csproj -c Release -r win-x64 --self-contained true --no-restore -p:PublishDir="${env:PUBLISH_DIR}"
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public static class AgcExports
|
|||||||
private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new();
|
private static readonly HashSet<uint> _tracedSubmittedDrawOpcodes = new();
|
||||||
private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
|
private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
|
||||||
private static readonly Dictionary<
|
private static readonly Dictionary<
|
||||||
(ulong Es, ulong EsState, ulong Ps, ulong PsState, Gen5PixelOutputKind Output, uint Attributes),
|
(ulong Es, ulong EsState, ulong Ps, ulong PsState, string OutputLayout, uint Attributes),
|
||||||
(byte[] Vertex, byte[] Pixel)> _graphicsSpirvCache = new();
|
(byte[] Vertex, byte[] Pixel)> _graphicsSpirvCache = new();
|
||||||
private static readonly Dictionary<
|
private static readonly Dictionary<
|
||||||
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
|
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
|
||||||
@@ -3329,12 +3329,13 @@ public static class AgcExports
|
|||||||
textures,
|
textures,
|
||||||
globalMemoryBuffers,
|
globalMemoryBuffers,
|
||||||
translatedDraw.AttributeCount,
|
translatedDraw.AttributeCount,
|
||||||
new VulkanGuestRenderTarget(
|
translatedDraw.RenderTargets.Select(target =>
|
||||||
firstTarget.Address,
|
new VulkanGuestRenderTarget(
|
||||||
firstTarget.Width,
|
target.Address,
|
||||||
firstTarget.Height,
|
target.Width,
|
||||||
firstTarget.Format,
|
target.Height,
|
||||||
firstTarget.NumberType),
|
target.Format,
|
||||||
|
target.NumberType)).ToArray(),
|
||||||
translatedDraw.VertexSpirv,
|
translatedDraw.VertexSpirv,
|
||||||
translatedDraw.VertexCount,
|
translatedDraw.VertexCount,
|
||||||
translatedDraw.InstanceCount,
|
translatedDraw.InstanceCount,
|
||||||
@@ -3462,11 +3463,34 @@ public static class AgcExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var renderTargets = GetRenderTargets(state.CxRegisters)
|
var renderTargets = GetRenderTargets(state.CxRegisters)
|
||||||
.Where(target =>
|
.Where(target => HasPixelColorExport(pixelState, target.Slot))
|
||||||
target.Slot == 0 &&
|
.OrderBy(target => target.Slot)
|
||||||
HasPixelColorExport(pixelState, target.Slot))
|
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var outputKind = GetPixelOutputKind(renderTargets.FirstOrDefault().NumberType);
|
var renderTargetFormats = new VulkanRenderTargetFormat[renderTargets.Length];
|
||||||
|
for (var index = 0; index < renderTargets.Length; index++)
|
||||||
|
{
|
||||||
|
var target = renderTargets[index];
|
||||||
|
if (!VulkanVideoPresenter.TryDecodeRenderTargetFormat(
|
||||||
|
target.Format,
|
||||||
|
target.NumberType,
|
||||||
|
out renderTargetFormats[index]))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"unsupported color target format={target.Format} number_type={target.NumberType}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixelOutputs = renderTargets
|
||||||
|
.Select((target, location) => new Gen5PixelOutputBinding(
|
||||||
|
target.Slot,
|
||||||
|
(uint)location,
|
||||||
|
renderTargetFormats[location].OutputKind))
|
||||||
|
.ToArray();
|
||||||
|
var outputLayout = string.Join(
|
||||||
|
';',
|
||||||
|
pixelOutputs.Select(output =>
|
||||||
|
$"{output.GuestSlot}:{output.HostLocation}:{(int)output.Kind}"));
|
||||||
var attributeCount = GetInterpolatedAttributeCount(pixelState);
|
var attributeCount = GetInterpolatedAttributeCount(pixelState);
|
||||||
var exportStateFingerprint = ComputeShaderStructureFingerprint(exportEvaluation);
|
var exportStateFingerprint = ComputeShaderStructureFingerprint(exportEvaluation);
|
||||||
var pixelStateFingerprint = ComputeShaderStructureFingerprint(pixelEvaluation);
|
var pixelStateFingerprint = ComputeShaderStructureFingerprint(pixelEvaluation);
|
||||||
@@ -3475,7 +3499,7 @@ public static class AgcExports
|
|||||||
exportStateFingerprint,
|
exportStateFingerprint,
|
||||||
pixelShaderAddress,
|
pixelShaderAddress,
|
||||||
pixelStateFingerprint,
|
pixelStateFingerprint,
|
||||||
outputKind,
|
outputLayout,
|
||||||
attributeCount);
|
attributeCount);
|
||||||
var totalGlobalBuffers =
|
var totalGlobalBuffers =
|
||||||
pixelEvaluation.GlobalMemoryBindings.Count +
|
pixelEvaluation.GlobalMemoryBindings.Count +
|
||||||
@@ -3491,7 +3515,7 @@ public static class AgcExports
|
|||||||
if (!Gen5SpirvTranslator.TryCompilePixelShader(
|
if (!Gen5SpirvTranslator.TryCompilePixelShader(
|
||||||
pixelState,
|
pixelState,
|
||||||
pixelEvaluation,
|
pixelEvaluation,
|
||||||
outputKind,
|
pixelOutputs,
|
||||||
out var pixelShader,
|
out var pixelShader,
|
||||||
out error,
|
out error,
|
||||||
globalBufferBase: 0,
|
globalBufferBase: 0,
|
||||||
@@ -3579,7 +3603,7 @@ public static class AgcExports
|
|||||||
vertexInputs,
|
vertexInputs,
|
||||||
renderTargets,
|
renderTargets,
|
||||||
ApplyTransparentPremultipliedFillClear(
|
ApplyTransparentPremultipliedFillClear(
|
||||||
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()),
|
CreateRenderState(state.CxRegisters, renderTargets, pixelState),
|
||||||
textures,
|
textures,
|
||||||
vertexInputs,
|
vertexInputs,
|
||||||
pixelEvaluation.InitialScalarRegisters));
|
pixelEvaluation.InitialScalarRegisters));
|
||||||
@@ -3595,7 +3619,8 @@ public static class AgcExports
|
|||||||
/// Chowdren resets its effect layers with an untextured transparent-black
|
/// Chowdren resets its effect layers with an untextured transparent-black
|
||||||
/// fill using premultiplied blending. With One/OneMinusSrcAlpha that draw
|
/// fill using premultiplied blending. With One/OneMinusSrcAlpha that draw
|
||||||
/// is otherwise a no-op, causing fog and vignette layers to accumulate.
|
/// is otherwise a no-op, causing fog and vignette layers to accumulate.
|
||||||
/// Treat precisely that draw shape as an overwrite.
|
/// Treat precisely that draw shape as an overwrite only when every MRT
|
||||||
|
/// attachment uses the same premultiplied blend pattern.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static VulkanGuestRenderState ApplyTransparentPremultipliedFillClear(
|
private static VulkanGuestRenderState ApplyTransparentPremultipliedFillClear(
|
||||||
VulkanGuestRenderState renderState,
|
VulkanGuestRenderState renderState,
|
||||||
@@ -3607,13 +3632,7 @@ public static class AgcExports
|
|||||||
textures.Count != 0 ||
|
textures.Count != 0 ||
|
||||||
vertexInputs.Count != 0 ||
|
vertexInputs.Count != 0 ||
|
||||||
pixelUserData.Count < 4 ||
|
pixelUserData.Count < 4 ||
|
||||||
renderState.Blend is not
|
!renderState.Blends.All(IsTransparentPremultipliedFillBlend))
|
||||||
{
|
|
||||||
Enable: true,
|
|
||||||
ColorSrcFactor: 1,
|
|
||||||
ColorDstFactor: 5,
|
|
||||||
ColorFunc: 0,
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
return renderState;
|
return renderState;
|
||||||
}
|
}
|
||||||
@@ -3628,10 +3647,21 @@ public static class AgcExports
|
|||||||
|
|
||||||
return renderState with
|
return renderState with
|
||||||
{
|
{
|
||||||
Blend = renderState.Blend with { Enable = false },
|
Blends = renderState.Blends
|
||||||
|
.Select(blend => blend with { Enable = false })
|
||||||
|
.ToArray(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsTransparentPremultipliedFillBlend(VulkanGuestBlendState blend) =>
|
||||||
|
blend is
|
||||||
|
{
|
||||||
|
Enable: true,
|
||||||
|
ColorSrcFactor: 1,
|
||||||
|
ColorDstFactor: 5,
|
||||||
|
ColorFunc: 0,
|
||||||
|
};
|
||||||
|
|
||||||
private static VulkanGuestIndexBuffer? CreateVulkanIndexBuffer(
|
private static VulkanGuestIndexBuffer? CreateVulkanIndexBuffer(
|
||||||
CpuContext ctx,
|
CpuContext ctx,
|
||||||
SubmittedDcbState state,
|
SubmittedDcbState state,
|
||||||
@@ -3654,19 +3684,15 @@ public static class AgcExports
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Gen5PixelOutputKind GetPixelOutputKind(uint numberType) =>
|
|
||||||
numberType switch
|
|
||||||
{
|
|
||||||
4 => Gen5PixelOutputKind.Uint,
|
|
||||||
5 => Gen5PixelOutputKind.Sint,
|
|
||||||
_ => Gen5PixelOutputKind.Float,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static bool HasPixelColorExport(Gen5ShaderState state, uint target) =>
|
private static bool HasPixelColorExport(Gen5ShaderState state, uint target) =>
|
||||||
state.Program.Instructions.Any(instruction =>
|
GetPixelColorExportMask(state, target) != 0;
|
||||||
instruction.Control is Gen5ExportControl export &&
|
|
||||||
export.Target == target &&
|
private static uint GetPixelColorExportMask(Gen5ShaderState state, uint target) =>
|
||||||
export.EnableMask != 0);
|
state.Program.Instructions
|
||||||
|
.Select(instruction => instruction.Control)
|
||||||
|
.OfType<Gen5ExportControl>()
|
||||||
|
.Where(export => export.Target == target)
|
||||||
|
.Aggregate(0u, (mask, export) => mask | (export.EnableMask & 0xFu));
|
||||||
|
|
||||||
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
|
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
|
||||||
{
|
{
|
||||||
@@ -3824,11 +3850,25 @@ public static class AgcExports
|
|||||||
|
|
||||||
private static VulkanGuestRenderState CreateRenderState(
|
private static VulkanGuestRenderState CreateRenderState(
|
||||||
IReadOnlyDictionary<uint, uint> registers,
|
IReadOnlyDictionary<uint, uint> registers,
|
||||||
RenderTargetDescriptor target)
|
IReadOnlyList<RenderTargetDescriptor> targets,
|
||||||
|
Gen5ShaderState pixelState)
|
||||||
{
|
{
|
||||||
|
if (targets.Count == 0)
|
||||||
|
{
|
||||||
|
return VulkanGuestRenderState.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var target = targets[0];
|
||||||
var scissor = DecodeScissor(registers, target.Width, target.Height);
|
var scissor = DecodeScissor(registers, target.Width, target.Height);
|
||||||
return new VulkanGuestRenderState(
|
return new VulkanGuestRenderState(
|
||||||
DecodeBlendState(registers, target.Slot),
|
targets.Select(target =>
|
||||||
|
{
|
||||||
|
var blend = DecodeBlendState(registers, target.Slot);
|
||||||
|
return blend with
|
||||||
|
{
|
||||||
|
WriteMask = blend.WriteMask & GetPixelColorExportMask(pixelState, target.Slot),
|
||||||
|
};
|
||||||
|
}).ToArray(),
|
||||||
scissor,
|
scissor,
|
||||||
DecodeViewport(registers, target.Width, target.Height, scissor));
|
DecodeViewport(registers, target.Width, target.Height, scissor));
|
||||||
}
|
}
|
||||||
@@ -3853,7 +3893,7 @@ public static class AgcExports
|
|||||||
(control >> 24) & 0x1Fu,
|
(control >> 24) & 0x1Fu,
|
||||||
(control >> 21) & 0x7u,
|
(control >> 21) & 0x7u,
|
||||||
((control >> 29) & 1u) != 0,
|
((control >> 29) & 1u) != 0,
|
||||||
writeMask == 0 ? 0xFu : writeMask);
|
writeMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VulkanGuestRect? DecodeScissor(
|
private static VulkanGuestRect? DecodeScissor(
|
||||||
@@ -5191,7 +5231,7 @@ public static class AgcExports
|
|||||||
if (Gen5SpirvTranslator.TryCompilePixelShader(
|
if (Gen5SpirvTranslator.TryCompilePixelShader(
|
||||||
pixelState,
|
pixelState,
|
||||||
evaluation,
|
evaluation,
|
||||||
Gen5PixelOutputKind.Float,
|
[new(0, 0, Gen5PixelOutputKind.Float)],
|
||||||
out var compiledPixel,
|
out var compiledPixel,
|
||||||
out var compileError))
|
out var compileError))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,6 +49,11 @@ internal enum Gen5PixelOutputKind
|
|||||||
Sint,
|
Sint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal readonly record struct Gen5PixelOutputBinding(
|
||||||
|
uint GuestSlot,
|
||||||
|
uint HostLocation,
|
||||||
|
Gen5PixelOutputKind Kind);
|
||||||
|
|
||||||
internal enum Gen5SpirvStage
|
internal enum Gen5SpirvStage
|
||||||
{
|
{
|
||||||
Vertex,
|
Vertex,
|
||||||
|
|||||||
@@ -19,13 +19,59 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
int globalBufferBase = 0,
|
int globalBufferBase = 0,
|
||||||
int totalGlobalBufferCount = -1,
|
int totalGlobalBufferCount = -1,
|
||||||
int imageBindingBase = 0,
|
int imageBindingBase = 0,
|
||||||
|
int scalarRegisterBufferIndex = -1) =>
|
||||||
|
TryCompilePixelShader(
|
||||||
|
state,
|
||||||
|
evaluation,
|
||||||
|
[new Gen5PixelOutputBinding(0, 0, outputKind)],
|
||||||
|
out shader,
|
||||||
|
out error,
|
||||||
|
globalBufferBase,
|
||||||
|
totalGlobalBufferCount,
|
||||||
|
imageBindingBase,
|
||||||
|
scalarRegisterBufferIndex);
|
||||||
|
|
||||||
|
public static bool TryCompilePixelShader(
|
||||||
|
Gen5ShaderState state,
|
||||||
|
Gen5ShaderEvaluation evaluation,
|
||||||
|
IReadOnlyList<Gen5PixelOutputBinding> outputs,
|
||||||
|
out Gen5SpirvShader shader,
|
||||||
|
out string error,
|
||||||
|
int globalBufferBase = 0,
|
||||||
|
int totalGlobalBufferCount = -1,
|
||||||
|
int imageBindingBase = 0,
|
||||||
int scalarRegisterBufferIndex = -1)
|
int scalarRegisterBufferIndex = -1)
|
||||||
{
|
{
|
||||||
|
if (outputs.Count > 8 || outputs.Any(output => output.GuestSlot > 7))
|
||||||
|
{
|
||||||
|
shader = default!;
|
||||||
|
error = "pixel outputs must contain at most eight guest slots in the 0..7 range";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputs.Select(output => output.GuestSlot).Distinct().Count() != outputs.Count ||
|
||||||
|
outputs.Select(output => output.HostLocation).Distinct().Count() != outputs.Count)
|
||||||
|
{
|
||||||
|
shader = default!;
|
||||||
|
error = "pixel output guest slots and host locations must be unique";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!outputs
|
||||||
|
.OrderBy(output => output.HostLocation)
|
||||||
|
.Select((output, index) => output.HostLocation == (uint)index)
|
||||||
|
.All(isDense => isDense))
|
||||||
|
{
|
||||||
|
shader = default!;
|
||||||
|
error = "pixel output host locations must be dense in the 0..N-1 range";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var context = new CompilationContext(
|
var context = new CompilationContext(
|
||||||
Gen5SpirvStage.Pixel,
|
Gen5SpirvStage.Pixel,
|
||||||
state,
|
state,
|
||||||
evaluation,
|
evaluation,
|
||||||
outputKind,
|
outputs,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@@ -50,7 +96,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
Gen5SpirvStage.Vertex,
|
Gen5SpirvStage.Vertex,
|
||||||
state,
|
state,
|
||||||
evaluation,
|
evaluation,
|
||||||
Gen5PixelOutputKind.Float,
|
[],
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@@ -74,7 +120,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
Gen5SpirvStage.Compute,
|
Gen5SpirvStage.Compute,
|
||||||
state,
|
state,
|
||||||
evaluation,
|
evaluation,
|
||||||
Gen5PixelOutputKind.Float,
|
[],
|
||||||
Math.Max(localSizeX, 1),
|
Math.Max(localSizeX, 1),
|
||||||
Math.Max(localSizeY, 1),
|
Math.Max(localSizeY, 1),
|
||||||
Math.Max(localSizeZ, 1),
|
Math.Max(localSizeZ, 1),
|
||||||
@@ -91,7 +137,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private readonly Gen5SpirvStage _stage;
|
private readonly Gen5SpirvStage _stage;
|
||||||
private readonly Gen5ShaderState _state;
|
private readonly Gen5ShaderState _state;
|
||||||
private readonly Gen5ShaderEvaluation _evaluation;
|
private readonly Gen5ShaderEvaluation _evaluation;
|
||||||
private readonly Gen5PixelOutputKind _outputKind;
|
private readonly IReadOnlyList<Gen5PixelOutputBinding> _pixelOutputBindings;
|
||||||
private readonly uint _localSizeX;
|
private readonly uint _localSizeX;
|
||||||
private readonly uint _localSizeY;
|
private readonly uint _localSizeY;
|
||||||
private readonly uint _localSizeZ;
|
private readonly uint _localSizeZ;
|
||||||
@@ -101,6 +147,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private readonly int _scalarRegisterBufferIndex;
|
private readonly int _scalarRegisterBufferIndex;
|
||||||
private readonly List<uint> _interfaces = [];
|
private readonly List<uint> _interfaces = [];
|
||||||
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
||||||
|
private readonly Dictionary<uint, SpirvPixelOutput> _pixelOutputs = [];
|
||||||
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
||||||
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
|
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
|
||||||
private readonly List<SpirvImageResource> _imageResources = [];
|
private readonly List<SpirvImageResource> _imageResources = [];
|
||||||
@@ -133,7 +180,6 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
private uint _lds;
|
private uint _lds;
|
||||||
private uint _workgroupUintPointer;
|
private uint _workgroupUintPointer;
|
||||||
private uint _positionOutput;
|
private uint _positionOutput;
|
||||||
private uint _pixelOutput;
|
|
||||||
private uint _vertexIndexInput;
|
private uint _vertexIndexInput;
|
||||||
private uint _instanceIndexInput;
|
private uint _instanceIndexInput;
|
||||||
private uint _fragCoordInput;
|
private uint _fragCoordInput;
|
||||||
@@ -163,11 +209,16 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
uint Type,
|
uint Type,
|
||||||
uint ComponentCount);
|
uint ComponentCount);
|
||||||
|
|
||||||
|
private readonly record struct SpirvPixelOutput(
|
||||||
|
uint Variable,
|
||||||
|
uint Type,
|
||||||
|
Gen5PixelOutputKind Kind);
|
||||||
|
|
||||||
public CompilationContext(
|
public CompilationContext(
|
||||||
Gen5SpirvStage stage,
|
Gen5SpirvStage stage,
|
||||||
Gen5ShaderState state,
|
Gen5ShaderState state,
|
||||||
Gen5ShaderEvaluation evaluation,
|
Gen5ShaderEvaluation evaluation,
|
||||||
Gen5PixelOutputKind outputKind,
|
IReadOnlyList<Gen5PixelOutputBinding> pixelOutputBindings,
|
||||||
uint localSizeX,
|
uint localSizeX,
|
||||||
uint localSizeY,
|
uint localSizeY,
|
||||||
uint localSizeZ,
|
uint localSizeZ,
|
||||||
@@ -179,7 +230,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
_stage = stage;
|
_stage = stage;
|
||||||
_state = state;
|
_state = state;
|
||||||
_evaluation = evaluation;
|
_evaluation = evaluation;
|
||||||
_outputKind = outputKind;
|
_pixelOutputBindings = pixelOutputBindings;
|
||||||
_localSizeX = localSizeX;
|
_localSizeX = localSizeX;
|
||||||
_localSizeY = localSizeY;
|
_localSizeY = localSizeY;
|
||||||
_localSizeZ = localSizeZ;
|
_localSizeZ = localSizeZ;
|
||||||
@@ -700,19 +751,24 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
(uint)SpirvBuiltIn.FragCoord);
|
(uint)SpirvBuiltIn.FragCoord);
|
||||||
_interfaces.Add(_fragCoordInput);
|
_interfaces.Add(_fragCoordInput);
|
||||||
|
|
||||||
var outputType = _outputKind switch
|
foreach (var binding in _pixelOutputBindings)
|
||||||
{
|
{
|
||||||
Gen5PixelOutputKind.Uint => _uvec4Type,
|
var outputType = GetPixelOutputType(binding.Kind);
|
||||||
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
|
var outputPointer =
|
||||||
_ => _vec4Type,
|
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
||||||
};
|
var variable = _module.AddGlobalVariable(
|
||||||
var outputPointer =
|
outputPointer,
|
||||||
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
SpirvStorageClass.Output);
|
||||||
_pixelOutput = _module.AddGlobalVariable(
|
_module.AddName(variable, $"mrt{binding.GuestSlot}");
|
||||||
outputPointer,
|
_module.AddDecoration(
|
||||||
SpirvStorageClass.Output);
|
variable,
|
||||||
_module.AddDecoration(_pixelOutput, SpirvDecoration.Location, 0);
|
SpirvDecoration.Location,
|
||||||
_interfaces.Add(_pixelOutput);
|
binding.HostLocation);
|
||||||
|
_pixelOutputs.Add(
|
||||||
|
binding.GuestSlot,
|
||||||
|
new SpirvPixelOutput(variable, outputType, binding.Kind));
|
||||||
|
_interfaces.Add(variable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -831,7 +887,10 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
1);
|
1);
|
||||||
StoreV(2, Bitcast(_uintType, x), guardWithExec: false);
|
StoreV(2, Bitcast(_uintType, x), guardWithExec: false);
|
||||||
StoreV(3, Bitcast(_uintType, y), guardWithExec: false);
|
StoreV(3, Bitcast(_uintType, y), guardWithExec: false);
|
||||||
Store(_pixelOutput, _module.ConstantNull(GetPixelOutputType()));
|
foreach (var output in _pixelOutputs.Values)
|
||||||
|
{
|
||||||
|
Store(output.Variable, _module.ConstantNull(output.Type));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2084,21 +2143,27 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
if (_stage == Gen5SpirvStage.Pixel)
|
if (_stage == Gen5SpirvStage.Pixel)
|
||||||
{
|
{
|
||||||
if (export.Target != 0)
|
if (!_pixelOutputs.TryGetValue(export.Target, out var output))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputType = GetPixelOutputType();
|
|
||||||
var values = new uint[4];
|
var values = new uint[4];
|
||||||
for (var component = 0; component < 4; component++)
|
for (var component = 0; component < 4; component++)
|
||||||
{
|
{
|
||||||
var enabled = (export.EnableMask & (1u << component)) != 0;
|
var enabled = (export.EnableMask & (1u << component)) != 0;
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
{
|
{
|
||||||
values[component] = _outputKind == Gen5PixelOutputKind.Sint
|
values[component] = _module.AddInstruction(
|
||||||
? Bitcast(_intType, UInt(0))
|
SpirvOp.CompositeExtract,
|
||||||
: UInt(0);
|
output.Kind switch
|
||||||
|
{
|
||||||
|
Gen5PixelOutputKind.Uint => _uintType,
|
||||||
|
Gen5PixelOutputKind.Sint => _intType,
|
||||||
|
_ => _floatType,
|
||||||
|
},
|
||||||
|
Load(output.Type, output.Variable),
|
||||||
|
(uint)component);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2107,7 +2172,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
var value = LoadCompressedExportComponent(
|
var value = LoadCompressedExportComponent(
|
||||||
instruction,
|
instruction,
|
||||||
component);
|
component);
|
||||||
values[component] = _outputKind switch
|
values[component] = output.Kind switch
|
||||||
{
|
{
|
||||||
Gen5PixelOutputKind.Uint => _module.AddInstruction(
|
Gen5PixelOutputKind.Uint => _module.AddInstruction(
|
||||||
SpirvOp.ConvertFToU,
|
SpirvOp.ConvertFToU,
|
||||||
@@ -2123,7 +2188,7 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var raw = LoadV(instruction.Sources[component].Value);
|
var raw = LoadV(instruction.Sources[component].Value);
|
||||||
values[component] = _outputKind switch
|
values[component] = output.Kind switch
|
||||||
{
|
{
|
||||||
Gen5PixelOutputKind.Uint => raw,
|
Gen5PixelOutputKind.Uint => raw,
|
||||||
Gen5PixelOutputKind.Sint => Bitcast(_intType, raw),
|
Gen5PixelOutputKind.Sint => Bitcast(_intType, raw),
|
||||||
@@ -2133,15 +2198,15 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
|
|
||||||
var vector = _module.AddInstruction(
|
var vector = _module.AddInstruction(
|
||||||
SpirvOp.CompositeConstruct,
|
SpirvOp.CompositeConstruct,
|
||||||
outputType,
|
output.Type,
|
||||||
values);
|
values);
|
||||||
vector = _module.AddInstruction(
|
vector = _module.AddInstruction(
|
||||||
SpirvOp.Select,
|
SpirvOp.Select,
|
||||||
outputType,
|
output.Type,
|
||||||
Load(_boolType, _exec),
|
Load(_boolType, _exec),
|
||||||
vector,
|
vector,
|
||||||
Load(outputType, _pixelOutput));
|
Load(output.Type, output.Variable));
|
||||||
Store(_pixelOutput, vector);
|
Store(output.Variable, vector);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2209,8 +2274,8 @@ internal static partial class Gen5SpirvTranslator
|
|||||||
(uint)(component & 1));
|
(uint)(component & 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint GetPixelOutputType() =>
|
private uint GetPixelOutputType(Gen5PixelOutputKind kind) =>
|
||||||
_outputKind switch
|
kind switch
|
||||||
{
|
{
|
||||||
Gen5PixelOutputKind.Uint => _uvec4Type,
|
Gen5PixelOutputKind.Uint => _uvec4Type,
|
||||||
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
|
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
|
||||||
|
|||||||
@@ -104,14 +104,17 @@ internal readonly record struct VulkanGuestBlendState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record VulkanGuestRenderState(
|
internal sealed record VulkanGuestRenderState(
|
||||||
VulkanGuestBlendState Blend,
|
IReadOnlyList<VulkanGuestBlendState> Blends,
|
||||||
VulkanGuestRect? Scissor,
|
VulkanGuestRect? Scissor,
|
||||||
VulkanGuestViewport? Viewport)
|
VulkanGuestViewport? Viewport)
|
||||||
{
|
{
|
||||||
public static VulkanGuestRenderState Default { get; } = new(
|
public static VulkanGuestRenderState Default { get; } = new(
|
||||||
VulkanGuestBlendState.Default,
|
[VulkanGuestBlendState.Default],
|
||||||
Scissor: null,
|
Scissor: null,
|
||||||
Viewport: null);
|
Viewport: null);
|
||||||
|
|
||||||
|
public VulkanGuestBlendState Blend =>
|
||||||
|
Blends.Count == 0 ? VulkanGuestBlendState.Default : Blends[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record VulkanGuestRenderTarget(
|
internal sealed record VulkanGuestRenderTarget(
|
||||||
@@ -122,6 +125,13 @@ internal sealed record VulkanGuestRenderTarget(
|
|||||||
uint NumberType,
|
uint NumberType,
|
||||||
uint MipLevels = 1);
|
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(
|
internal sealed record VulkanTranslatedGuestDraw(
|
||||||
byte[] VertexSpirv,
|
byte[] VertexSpirv,
|
||||||
byte[] PixelSpirv,
|
byte[] PixelSpirv,
|
||||||
@@ -137,7 +147,7 @@ internal sealed record VulkanTranslatedGuestDraw(
|
|||||||
|
|
||||||
internal sealed record VulkanOffscreenGuestDraw(
|
internal sealed record VulkanOffscreenGuestDraw(
|
||||||
VulkanTranslatedGuestDraw Draw,
|
VulkanTranslatedGuestDraw Draw,
|
||||||
VulkanGuestRenderTarget Target,
|
IReadOnlyList<VulkanGuestRenderTarget> Targets,
|
||||||
bool PublishTarget);
|
bool PublishTarget);
|
||||||
|
|
||||||
internal sealed record VulkanComputeGuestDispatch(
|
internal sealed record VulkanComputeGuestDispatch(
|
||||||
@@ -463,20 +473,70 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
VulkanGuestIndexBuffer? indexBuffer = null,
|
VulkanGuestIndexBuffer? indexBuffer = null,
|
||||||
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
||||||
VulkanGuestRenderState? renderState = 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 ||
|
if (pixelSpirv.Length == 0 ||
|
||||||
target.Address == 0 ||
|
targets.Count == 0 ||
|
||||||
target.Width == 0 ||
|
targets.Count > 8 ||
|
||||||
target.Height == 0)
|
targets.Any(target =>
|
||||||
|
target.Address == 0 || target.Width == 0 || target.Height == 0))
|
||||||
{
|
{
|
||||||
return;
|
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())
|
if (ShouldTracePresentedGuestImageContentsForDiagnostics())
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] vk.submit_call kind=SubmitOffscreenTranslatedDraw " +
|
$"[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)
|
lock (_gate)
|
||||||
@@ -486,12 +546,15 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var guestTextureFormat = GetGuestTextureFormat(
|
foreach (var target in targets)
|
||||||
target.Format,
|
|
||||||
target.NumberType);
|
|
||||||
if (guestTextureFormat != 0)
|
|
||||||
{
|
{
|
||||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
var guestTextureFormat = GetGuestTextureFormat(
|
||||||
|
target.Format,
|
||||||
|
target.NumberType);
|
||||||
|
if (guestTextureFormat != 0)
|
||||||
|
{
|
||||||
|
_availableGuestImages[target.Address] = guestTextureFormat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EnqueueGuestWorkLocked(
|
EnqueueGuestWorkLocked(
|
||||||
@@ -507,8 +570,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
instanceCount,
|
instanceCount,
|
||||||
primitiveType,
|
primitiveType,
|
||||||
indexBuffer,
|
indexBuffer,
|
||||||
renderState ?? VulkanGuestRenderState.Default),
|
effectiveRenderState),
|
||||||
target,
|
targets.ToArray(),
|
||||||
PublishTarget: true));
|
PublishTarget: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,12 +613,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
4,
|
4,
|
||||||
null,
|
null,
|
||||||
VulkanGuestRenderState.Default),
|
VulkanGuestRenderState.Default),
|
||||||
new VulkanGuestRenderTarget(
|
[new VulkanGuestRenderTarget(
|
||||||
Address: 0,
|
Address: 0,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
Format: 12,
|
Format: 12,
|
||||||
NumberType: 7),
|
NumberType: 7)],
|
||||||
PublishTarget: false));
|
PublishTarget: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -785,25 +848,100 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint GetGuestTextureFormat(uint format, uint numberType) =>
|
private static uint GetGuestTextureFormat(uint format, uint numberType)
|
||||||
(format, numberType) switch
|
{
|
||||||
|
if (!TryDecodeRenderTargetFormat(format, numberType, out var decoded))
|
||||||
{
|
{
|
||||||
(9, _) => 9,
|
return 0;
|
||||||
(4, 4) => GuestFormatR32Uint,
|
}
|
||||||
(4, 5) => GuestFormatR32Sint,
|
|
||||||
(4, 7) => GuestFormatR32Sfloat,
|
return decoded.Format switch
|
||||||
(5, 4) => GuestFormatR16G16Uint,
|
{
|
||||||
(5, 5) => GuestFormatR16G16Sint,
|
Format.R8Unorm => 36,
|
||||||
(5, 7) => GuestFormatR16G16Sfloat,
|
Format.R8Uint => 49,
|
||||||
(10, 4) => GuestFormatR8G8B8A8Uint,
|
Format.R8G8Unorm => 3,
|
||||||
(10, 5) => GuestFormatR8G8B8A8Sint,
|
Format.A2R10G10B10UnormPack32 => 9,
|
||||||
(10, _) => 56,
|
Format.B10G11R11UfloatPack32 => 7,
|
||||||
(12, 4) => GuestFormatR16G16B16A16Uint,
|
Format.R32Uint => GuestFormatR32Uint,
|
||||||
(12, 5) => GuestFormatR16G16B16A16Sint,
|
Format.R32Sint => GuestFormatR32Sint,
|
||||||
(12, 7) => 71,
|
Format.R32Sfloat => GuestFormatR32Sfloat,
|
||||||
(_, 0) when IsKnownGuestTextureFormat(format) => format,
|
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,
|
_ => 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) =>
|
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;
|
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 DebugUtilsMessengerEXT _debugMessenger;
|
||||||
private ExtDebugUtils? _debugUtils;
|
private ExtDebugUtils? _debugUtils;
|
||||||
private PhysicalDevice _physicalDevice;
|
private PhysicalDevice _physicalDevice;
|
||||||
|
private bool _supportsIndependentBlend;
|
||||||
|
private uint _maxColorAttachments;
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private PipelineCache _pipelineCache;
|
private PipelineCache _pipelineCache;
|
||||||
private string? _pipelineCachePath;
|
private string? _pipelineCachePath;
|
||||||
@@ -1024,9 +1164,9 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private readonly record struct GraphicsPipelineKey(
|
private readonly record struct GraphicsPipelineKey(
|
||||||
string VertexShader,
|
string VertexShader,
|
||||||
string FragmentShader,
|
string FragmentShader,
|
||||||
Format RenderTargetFormat,
|
string RenderTargetLayout,
|
||||||
PrimitiveTopology Topology,
|
PrimitiveTopology Topology,
|
||||||
VulkanGuestBlendState Blend,
|
string BlendLayout,
|
||||||
string ResourceLayout,
|
string ResourceLayout,
|
||||||
string VertexLayout);
|
string VertexLayout);
|
||||||
|
|
||||||
@@ -1066,9 +1206,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
public uint VertexCount = 3;
|
public uint VertexCount = 3;
|
||||||
public uint InstanceCount = 1;
|
public uint InstanceCount = 1;
|
||||||
public PrimitiveTopology Topology = PrimitiveTopology.TriangleList;
|
public PrimitiveTopology Topology = PrimitiveTopology.TriangleList;
|
||||||
public VulkanGuestBlendState Blend = VulkanGuestBlendState.Default;
|
public VulkanGuestBlendState[] Blends = [VulkanGuestBlendState.Default];
|
||||||
public VulkanGuestRect? Scissor;
|
public VulkanGuestRect? Scissor;
|
||||||
public VulkanGuestViewport? Viewport;
|
public VulkanGuestViewport? Viewport;
|
||||||
|
public RenderPass TransientRenderPass;
|
||||||
|
public Framebuffer TransientFramebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class TextureResource
|
private sealed class TextureResource
|
||||||
@@ -1604,6 +1746,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var selected);
|
_vk.GetPhysicalDeviceProperties(_physicalDevice, out var selected);
|
||||||
|
_maxColorAttachments = selected.Limits.MaxColorAttachments;
|
||||||
var selectedName = SilkMarshal.PtrToString((nint)selected.DeviceName) ?? "unknown";
|
var selectedName = SilkMarshal.PtrToString((nint)selected.DeviceName) ?? "unknown";
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][INFO] Vulkan device: {selectedName} ({selected.DeviceType})");
|
$"[LOADER][INFO] Vulkan device: {selectedName} ({selected.DeviceType})");
|
||||||
@@ -1650,8 +1793,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
PQueuePriorities = &priority,
|
PQueuePriorities = &priority,
|
||||||
};
|
};
|
||||||
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
|
_vk.GetPhysicalDeviceFeatures(_physicalDevice, out var supportedFeatures);
|
||||||
|
_supportsIndependentBlend = supportedFeatures.IndependentBlend;
|
||||||
var enabledFeatures = new PhysicalDeviceFeatures
|
var enabledFeatures = new PhysicalDeviceFeatures
|
||||||
{
|
{
|
||||||
|
IndependentBlend = supportedFeatures.IndependentBlend,
|
||||||
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
VertexPipelineStoresAndAtomics = supportedFeatures.VertexPipelineStoresAndAtomics,
|
||||||
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
FragmentStoresAndAtomics = supportedFeatures.FragmentStoresAndAtomics,
|
||||||
ShaderInt64 = supportedFeatures.ShaderInt64,
|
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)
|
private void CollectCompletedGuestSubmissions(bool waitForOldest)
|
||||||
{
|
{
|
||||||
if (waitForOldest && _pendingGuestSubmissions.TryPeek(out var oldest))
|
if (waitForOldest && _pendingGuestSubmissions.TryPeek(out var oldest))
|
||||||
@@ -2193,13 +2346,15 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private IReadOnlyList<GuestImageResource> GetTraceImages(
|
private IReadOnlyList<GuestImageResource> GetTraceImages(
|
||||||
TranslatedDrawResources resources,
|
TranslatedDrawResources resources,
|
||||||
GuestImageResource? renderTarget = null)
|
IReadOnlyList<GuestImageResource>? renderTargets = null)
|
||||||
{
|
{
|
||||||
var images = new HashSet<GuestImageResource>();
|
var images = new HashSet<GuestImageResource>();
|
||||||
if (renderTarget is not null &&
|
foreach (var renderTarget in renderTargets ?? [])
|
||||||
ShouldTraceGuestImageContents(renderTarget))
|
|
||||||
{
|
{
|
||||||
images.Add(renderTarget);
|
if (ShouldTraceGuestImageContents(renderTarget))
|
||||||
|
{
|
||||||
|
images.Add(renderTarget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var texture in resources.Textures)
|
foreach (var texture in resources.Textures)
|
||||||
@@ -2430,10 +2585,16 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
private TranslatedDrawResources CreateTranslatedDrawResources(
|
private TranslatedDrawResources CreateTranslatedDrawResources(
|
||||||
VulkanTranslatedGuestDraw draw,
|
VulkanTranslatedGuestDraw draw,
|
||||||
RenderPass renderPass,
|
RenderPass renderPass,
|
||||||
Format renderTargetFormat,
|
IReadOnlyList<Format> renderTargetFormats,
|
||||||
Extent2D extent)
|
Extent2D extent)
|
||||||
{
|
{
|
||||||
var vertexSpirv = draw.VertexSpirv;
|
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 &&
|
if (vertexSpirv.Length == 0 &&
|
||||||
!TryCompileFullscreenVertexShader(
|
!TryCompileFullscreenVertexShader(
|
||||||
draw.AttributeCount,
|
draw.AttributeCount,
|
||||||
@@ -2453,7 +2614,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
VertexCount = GetDrawVertexCount(draw.PrimitiveType, draw.VertexCount, draw.IndexBuffer),
|
VertexCount = GetDrawVertexCount(draw.PrimitiveType, draw.VertexCount, draw.IndexBuffer),
|
||||||
InstanceCount = Math.Max(draw.InstanceCount, 1),
|
InstanceCount = Math.Max(draw.InstanceCount, 1),
|
||||||
Topology = GetPrimitiveTopology(draw.PrimitiveType),
|
Topology = GetPrimitiveTopology(draw.PrimitiveType),
|
||||||
Blend = draw.RenderState.Blend,
|
Blends = draw.RenderState.Blends.ToArray(),
|
||||||
Scissor = draw.RenderState.Scissor,
|
Scissor = draw.RenderState.Scissor,
|
||||||
Viewport = draw.RenderState.Viewport,
|
Viewport = draw.RenderState.Viewport,
|
||||||
};
|
};
|
||||||
@@ -2502,7 +2663,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
vertexSpirv,
|
vertexSpirv,
|
||||||
draw.PixelSpirv,
|
draw.PixelSpirv,
|
||||||
renderPass,
|
renderPass,
|
||||||
renderTargetFormat,
|
renderTargetFormats,
|
||||||
extent);
|
extent);
|
||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
@@ -2799,15 +2960,18 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
byte[] vertexSpirv,
|
byte[] vertexSpirv,
|
||||||
byte[] fragmentSpirv,
|
byte[] fragmentSpirv,
|
||||||
RenderPass renderPass,
|
RenderPass renderPass,
|
||||||
Format renderTargetFormat,
|
IReadOnlyList<Format> renderTargetFormats,
|
||||||
Extent2D extent)
|
Extent2D extent)
|
||||||
{
|
{
|
||||||
var pipelineKey = new GraphicsPipelineKey(
|
var pipelineKey = new GraphicsPipelineKey(
|
||||||
GetShaderDigest(vertexSpirv),
|
GetShaderDigest(vertexSpirv),
|
||||||
GetShaderDigest(fragmentSpirv),
|
GetShaderDigest(fragmentSpirv),
|
||||||
renderTargetFormat,
|
string.Join(',', renderTargetFormats.Select(format => (uint)format)),
|
||||||
resources.Topology,
|
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),
|
GetResourceLayoutKey(resources),
|
||||||
GetVertexLayoutKey(resources));
|
GetVertexLayoutKey(resources));
|
||||||
if (_graphicsPipelines.TryGetValue(pipelineKey, out var cachedPipeline))
|
if (_graphicsPipelines.TryGetValue(pipelineKey, out var cachedPipeline))
|
||||||
@@ -2908,29 +3072,33 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
SType = StructureType.PipelineMultisampleStateCreateInfo,
|
||||||
RasterizationSamples = SampleCountFlags.Count1Bit,
|
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,
|
var blend = resources.Blends[index];
|
||||||
SrcColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorSrcFactor),
|
colorBlendAttachments[index] = new PipelineColorBlendAttachmentState
|
||||||
DstColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorDstFactor),
|
{
|
||||||
ColorBlendOp = ToVkBlendOp(resources.Blend.ColorFunc),
|
BlendEnable = blend.Enable,
|
||||||
SrcAlphaBlendFactor = resources.Blend.SeparateAlphaBlend
|
SrcColorBlendFactor = ToVkBlendFactor(blend.ColorSrcFactor),
|
||||||
? ToVkBlendFactor(resources.Blend.AlphaSrcFactor)
|
DstColorBlendFactor = ToVkBlendFactor(blend.ColorDstFactor),
|
||||||
: ToVkBlendFactor(resources.Blend.ColorSrcFactor),
|
ColorBlendOp = ToVkBlendOp(blend.ColorFunc),
|
||||||
DstAlphaBlendFactor = resources.Blend.SeparateAlphaBlend
|
SrcAlphaBlendFactor = blend.SeparateAlphaBlend
|
||||||
? ToVkBlendFactor(resources.Blend.AlphaDstFactor)
|
? ToVkBlendFactor(blend.AlphaSrcFactor)
|
||||||
: ToVkBlendFactor(resources.Blend.ColorDstFactor),
|
: ToVkBlendFactor(blend.ColorSrcFactor),
|
||||||
AlphaBlendOp = resources.Blend.SeparateAlphaBlend
|
DstAlphaBlendFactor = blend.SeparateAlphaBlend
|
||||||
? ToVkBlendOp(resources.Blend.AlphaFunc)
|
? ToVkBlendFactor(blend.AlphaDstFactor)
|
||||||
: ToVkBlendOp(resources.Blend.ColorFunc),
|
: ToVkBlendFactor(blend.ColorDstFactor),
|
||||||
ColorWriteMask =
|
AlphaBlendOp = blend.SeparateAlphaBlend
|
||||||
ToVkColorWriteMask(resources.Blend.WriteMask),
|
? ToVkBlendOp(blend.AlphaFunc)
|
||||||
};
|
: ToVkBlendOp(blend.ColorFunc),
|
||||||
|
ColorWriteMask = ToVkColorWriteMask(blend.WriteMask),
|
||||||
|
};
|
||||||
|
}
|
||||||
var colorBlend = new PipelineColorBlendStateCreateInfo
|
var colorBlend = new PipelineColorBlendStateCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
SType = StructureType.PipelineColorBlendStateCreateInfo,
|
||||||
AttachmentCount = 1,
|
AttachmentCount = (uint)resources.Blends.Length,
|
||||||
PAttachments = &colorBlendAttachment,
|
PAttachments = colorBlendAttachments,
|
||||||
};
|
};
|
||||||
var dynamicStateValues = stackalloc DynamicState[2];
|
var dynamicStateValues = stackalloc DynamicState[2];
|
||||||
dynamicStateValues[0] = DynamicState.Viewport;
|
dynamicStateValues[0] = DynamicState.Viewport;
|
||||||
@@ -4372,6 +4540,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
: checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes);
|
: 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) =>
|
private static Format GetTextureFormat(uint format, uint numberType) =>
|
||||||
(format, numberType) switch
|
(format, numberType) switch
|
||||||
{
|
{
|
||||||
@@ -4434,26 +4608,6 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_ => Format.R8G8B8A8Unorm,
|
_ => 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) =>
|
private static bool IsBlockCompressedFormat(Format format) =>
|
||||||
format is Format.BC1RgbaUnormBlock or
|
format is Format.BC1RgbaUnormBlock or
|
||||||
Format.BC1RgbaSrgbBlock or
|
Format.BC1RgbaSrgbBlock or
|
||||||
@@ -4694,47 +4848,114 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void ExecuteOffscreenDraw(VulkanOffscreenGuestDraw work)
|
private void ExecuteOffscreenDraw(VulkanOffscreenGuestDraw work)
|
||||||
{
|
{
|
||||||
if (_deviceLost)
|
if (_deviceLost || work.Targets.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var format = GetRenderTargetFormat(work.Target.Format, work.Target.NumberType);
|
if (work.Targets.Count > _maxColorAttachments)
|
||||||
if (format == Format.Undefined)
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][WARN] Vulkan skipped unsupported render target " +
|
$"[LOADER][WARN] Vulkan skipped MRT draw requesting {work.Targets.Count} color attachments; " +
|
||||||
$"addr=0x{work.Target.Address:X16} format={work.Target.Format} " +
|
$"the selected device supports {_maxColorAttachments}.");
|
||||||
$"number={work.Target.NumberType}");
|
|
||||||
return;
|
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 =>
|
if (work.Draw.Textures.Any(texture =>
|
||||||
texture.Address == work.Target.Address &&
|
targetAddresses.Contains(texture.Address)))
|
||||||
texture.Address != 0))
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][WARN] Vulkan skipped render-target feedback loop " +
|
$"[LOADER][WARN] Vulkan skipped render-target feedback loop " +
|
||||||
$"addr=0x{work.Target.Address:X16}");
|
$"targets={string.Join(',', targetAddresses.Select(address => $"0x{address:X16}"))}");
|
||||||
return;
|
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;
|
TranslatedDrawResources? resources = null;
|
||||||
CommandBuffer commandBuffer = default;
|
CommandBuffer commandBuffer = default;
|
||||||
var submitted = false;
|
var submitted = false;
|
||||||
|
RenderPass transientRenderPass = default;
|
||||||
|
Framebuffer transientFramebuffer = default;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
EnsureGuestSubmissionCapacity();
|
var extent = new Extent2D(firstTarget.Width, firstTarget.Height);
|
||||||
var extent = new Extent2D(target.Width, target.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(
|
resources = CreateTranslatedDrawResources(
|
||||||
work.Draw,
|
work.Draw,
|
||||||
target.RenderPass,
|
renderPass,
|
||||||
target.Format,
|
formats,
|
||||||
extent);
|
extent);
|
||||||
|
resources.TransientRenderPass = transientRenderPass;
|
||||||
|
resources.TransientFramebuffer = transientFramebuffer;
|
||||||
|
transientRenderPass = default;
|
||||||
|
transientFramebuffer = default;
|
||||||
resources.DebugName =
|
resources.DebugName =
|
||||||
$"SharpEmu offscreen rt=0x{work.Target.Address:X16} " +
|
$"SharpEmu offscreen mrt={targets.Length} " +
|
||||||
$"{work.Target.Width}x{work.Target.Height} fmt{work.Target.Format}";
|
$"first=0x{work.Targets[0].Address:X16} " +
|
||||||
|
$"{firstTarget.Width}x{firstTarget.Height}";
|
||||||
|
|
||||||
commandBuffer = AllocateGuestCommandBuffer();
|
commandBuffer = AllocateGuestCommandBuffer();
|
||||||
_commandBuffer = commandBuffer;
|
_commandBuffer = commandBuffer;
|
||||||
@@ -4751,24 +4972,30 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit);
|
RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit);
|
||||||
RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit);
|
RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit);
|
||||||
|
|
||||||
var targetHasPriorContents = target.Initialized || target.InitialUploadPending;
|
var toColorAttachments = stackalloc ImageMemoryBarrier[targets.Length];
|
||||||
var toColorAttachment = new ImageMemoryBarrier
|
var anyPriorContents = false;
|
||||||
|
for (var index = 0; index < targets.Length; index++)
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageMemoryBarrier,
|
var hasPriorContents = targets[index].Initialized || targets[index].InitialUploadPending;
|
||||||
SrcAccessMask = targetHasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
anyPriorContents |= hasPriorContents;
|
||||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
toColorAttachments[index] = new ImageMemoryBarrier
|
||||||
OldLayout = targetHasPriorContents
|
{
|
||||||
? ImageLayout.ShaderReadOnlyOptimal
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
: ImageLayout.Undefined,
|
SrcAccessMask = hasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
||||||
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
OldLayout = hasPriorContents
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
? ImageLayout.ShaderReadOnlyOptimal
|
||||||
Image = target.Image,
|
: ImageLayout.Undefined,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
||||||
};
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
|
Image = targets[index].Image,
|
||||||
|
SubresourceRange = ColorSubresourceRange(),
|
||||||
|
};
|
||||||
|
}
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
targetHasPriorContents
|
anyPriorContents
|
||||||
? PipelineStageFlags.AllCommandsBit
|
? PipelineStageFlags.AllCommandsBit
|
||||||
: PipelineStageFlags.TopOfPipeBit,
|
: PipelineStageFlags.TopOfPipeBit,
|
||||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
@@ -4777,28 +5004,32 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
null,
|
null,
|
||||||
1,
|
(uint)targets.Length,
|
||||||
&toColorAttachment);
|
toColorAttachments);
|
||||||
|
|
||||||
RecordTranslatedGraphicsPass(
|
RecordTranslatedGraphicsPass(
|
||||||
resources,
|
resources,
|
||||||
target.RenderPass,
|
renderPass,
|
||||||
target.Framebuffer,
|
framebuffer,
|
||||||
extent);
|
extent);
|
||||||
RecordStorageImagesForRead(resources, PipelineStageFlags.FragmentShaderBit);
|
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,
|
toShaderRead[index] = new ImageMemoryBarrier
|
||||||
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
{
|
||||||
DstAccessMask = AccessFlags.ShaderReadBit,
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
OldLayout = ImageLayout.ColorAttachmentOptimal,
|
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||||
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
DstAccessMask = AccessFlags.ShaderReadBit,
|
||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
OldLayout = ImageLayout.ColorAttachmentOptimal,
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||||
Image = target.Image,
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
};
|
Image = targets[index].Image,
|
||||||
|
SubresourceRange = ColorSubresourceRange(),
|
||||||
|
};
|
||||||
|
}
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
@@ -4808,55 +5039,70 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
null,
|
null,
|
||||||
1,
|
(uint)targets.Length,
|
||||||
&toShaderRead);
|
toShaderRead);
|
||||||
EndDebugLabel(_commandBuffer);
|
EndDebugLabel(_commandBuffer);
|
||||||
|
|
||||||
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(offscreen)");
|
Check(_vk.EndCommandBuffer(_commandBuffer), "vkEndCommandBuffer(offscreen)");
|
||||||
SubmitGuestCommandBuffer(
|
SubmitGuestCommandBuffer(
|
||||||
commandBuffer,
|
commandBuffer,
|
||||||
resources,
|
resources,
|
||||||
GetTraceImages(resources, target));
|
GetTraceImages(resources, targets));
|
||||||
submitted = true;
|
submitted = true;
|
||||||
target.Initialized = true;
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
target.Initialized = true;
|
||||||
|
}
|
||||||
MarkSampledImagesInitialized(resources);
|
MarkSampledImagesInitialized(resources);
|
||||||
MarkStorageImagesInitialized(resources, traceContents: false);
|
MarkStorageImagesInitialized(resources, traceContents: false);
|
||||||
|
|
||||||
var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
|
if (work.PublishTarget)
|
||||||
work.Target.Format,
|
|
||||||
work.Target.NumberType);
|
|
||||||
if (work.PublishTarget && guestTextureFormat != 0)
|
|
||||||
{
|
{
|
||||||
lock (_gate)
|
for (var index = 0; index < targets.Length; index++)
|
||||||
{
|
{
|
||||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
|
||||||
_gpuGuestImages[target.Address] = guestTextureFormat;
|
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(
|
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
|
||||||
target.Address,
|
|
||||||
out var previousCount)
|
|
||||||
? previousCount + 1
|
|
||||||
: 1;
|
|
||||||
_tracedGuestWriteCounts[target.Address] = writeCount;
|
|
||||||
if (writeCount <= 3)
|
|
||||||
{
|
{
|
||||||
_commandBuffer = _presentationCommandBuffer;
|
var writeCount = _tracedGuestWriteCounts.TryGetValue(
|
||||||
Check(
|
target.Address,
|
||||||
_vk.QueueWaitIdle(_queue),
|
out var previousCount)
|
||||||
"vkQueueWaitIdle(guest write trace)");
|
? previousCount + 1
|
||||||
Console.Error.WriteLine(
|
: 1;
|
||||||
$"[LOADER][TRACE] vk.guest_write_sample " +
|
_tracedGuestWriteCounts[target.Address] = writeCount;
|
||||||
$"addr=0x{target.Address:X16} write={writeCount} " +
|
if (writeCount <= 3)
|
||||||
$"ps_bytes={work.Draw.PixelSpirv.Length}");
|
{
|
||||||
TraceGuestImageContents(target);
|
_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(
|
TraceVulkanShader(
|
||||||
$"vk.offscreen_draw addr=0x{target.Address:X16} " +
|
$"vk.offscreen_draw mrt={targets.Length} " +
|
||||||
$"size={target.Width}x{target.Height} format={target.Format} " +
|
$"size={firstTarget.Width}x{firstTarget.Height} " +
|
||||||
$"textures={work.Draw.Textures.Count}");
|
$"textures={work.Draw.Textures.Count}");
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
@@ -4866,19 +5112,22 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_guestImages.TryGetValue(work.Target.Address, out var failedTarget) ||
|
lock (_gate)
|
||||||
!failedTarget.Initialized)
|
|
||||||
{
|
{
|
||||||
lock (_gate)
|
foreach (var target in work.Targets)
|
||||||
{
|
{
|
||||||
_availableGuestImages.Remove(work.Target.Address);
|
if (!_guestImages.TryGetValue(target.Address, out var failedTarget) ||
|
||||||
_gpuGuestImages.Remove(work.Target.Address);
|
!failedTarget.Initialized)
|
||||||
|
{
|
||||||
|
_availableGuestImages.Remove(target.Address);
|
||||||
|
_gpuGuestImages.Remove(target.Address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][ERROR] Vulkan offscreen draw failed " +
|
$"[LOADER][ERROR] Vulkan offscreen draw failed " +
|
||||||
$"addr=0x{work.Target.Address:X16}: {exception.Message}");
|
$"mrt={work.Targets.Count}: {exception.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -4896,6 +5145,16 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
DestroyTranslatedDrawResources(resources);
|
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;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompletePendingPresentation(wait: true);
|
||||||
|
WaitForAllGuestSubmissions();
|
||||||
DestroyGuestImage(existing);
|
DestroyGuestImage(existing);
|
||||||
_guestImages.Remove(target.Address);
|
_guestImages.Remove(target.Address);
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
@@ -5052,35 +5313,55 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
Format format,
|
Format format,
|
||||||
ImageView attachmentView,
|
ImageView attachmentView,
|
||||||
uint width,
|
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)
|
uint height)
|
||||||
{
|
{
|
||||||
var colorAttachment = new AttachmentDescription
|
if (formats.Count == 0 || formats.Count != attachmentViews.Count)
|
||||||
{
|
{
|
||||||
Format = format,
|
throw new InvalidOperationException("render target formats and views must have matching counts");
|
||||||
Samples = SampleCountFlags.Count1Bit,
|
}
|
||||||
LoadOp = AttachmentLoadOp.Load,
|
|
||||||
StoreOp = AttachmentStoreOp.Store,
|
var colorAttachments = stackalloc AttachmentDescription[formats.Count];
|
||||||
StencilLoadOp = AttachmentLoadOp.DontCare,
|
var colorReferences = stackalloc AttachmentReference[formats.Count];
|
||||||
StencilStoreOp = AttachmentStoreOp.DontCare,
|
var views = stackalloc ImageView[formats.Count];
|
||||||
InitialLayout = ImageLayout.ColorAttachmentOptimal,
|
for (var index = 0; index < formats.Count; index++)
|
||||||
FinalLayout = ImageLayout.ColorAttachmentOptimal,
|
|
||||||
};
|
|
||||||
var colorReference = new AttachmentReference
|
|
||||||
{
|
{
|
||||||
Attachment = 0,
|
colorAttachments[index] = new AttachmentDescription
|
||||||
Layout = ImageLayout.ColorAttachmentOptimal,
|
{
|
||||||
};
|
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
|
var subpass = new SubpassDescription
|
||||||
{
|
{
|
||||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||||
ColorAttachmentCount = 1,
|
ColorAttachmentCount = (uint)formats.Count,
|
||||||
PColorAttachments = &colorReference,
|
PColorAttachments = colorReferences,
|
||||||
};
|
};
|
||||||
var renderPassInfo = new RenderPassCreateInfo
|
var renderPassInfo = new RenderPassCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.RenderPassCreateInfo,
|
SType = StructureType.RenderPassCreateInfo,
|
||||||
AttachmentCount = 1,
|
AttachmentCount = (uint)formats.Count,
|
||||||
PAttachments = &colorAttachment,
|
PAttachments = colorAttachments,
|
||||||
SubpassCount = 1,
|
SubpassCount = 1,
|
||||||
PSubpasses = &subpass,
|
PSubpasses = &subpass,
|
||||||
};
|
};
|
||||||
@@ -5088,13 +5369,12 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
_vk.CreateRenderPass(_device, &renderPassInfo, null, out var renderPass),
|
||||||
"vkCreateRenderPass(offscreen)");
|
"vkCreateRenderPass(offscreen)");
|
||||||
|
|
||||||
var attachment = attachmentView;
|
|
||||||
var framebufferInfo = new FramebufferCreateInfo
|
var framebufferInfo = new FramebufferCreateInfo
|
||||||
{
|
{
|
||||||
SType = StructureType.FramebufferCreateInfo,
|
SType = StructureType.FramebufferCreateInfo,
|
||||||
RenderPass = renderPass,
|
RenderPass = renderPass,
|
||||||
AttachmentCount = 1,
|
AttachmentCount = (uint)formats.Count,
|
||||||
PAttachments = &attachment,
|
PAttachments = views,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
Layers = 1,
|
Layers = 1,
|
||||||
@@ -5632,7 +5912,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
translatedResources = CreateTranslatedDrawResources(
|
translatedResources = CreateTranslatedDrawResources(
|
||||||
translatedDraw,
|
translatedDraw,
|
||||||
_renderPass,
|
_renderPass,
|
||||||
_swapchainFormat,
|
[_swapchainFormat],
|
||||||
_extent);
|
_extent);
|
||||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||||
!_firstGuestDrawPresented)
|
!_firstGuestDrawPresented)
|
||||||
@@ -6527,6 +6807,16 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
private void DestroyTranslatedDrawResources(TranslatedDrawResources resources)
|
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)
|
foreach (var texture in resources.Textures)
|
||||||
{
|
{
|
||||||
if (texture is null)
|
if (texture is null)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Feeds hand-assembled Gen5 (gfx10) instruction words through the real
|
// Feeds hand-assembled Gen5 (gfx10) instruction words through the real
|
||||||
// decode -> SPIR-V pipeline (Gen5ShaderTranslator / Gen5SpirvTranslator, via
|
// decode -> SPIR-V pipeline (Gen5ShaderTranslator / Gen5SpirvTranslator, via
|
||||||
// reflection so no emulator source changes are required) and writes the
|
// reflection so no emulator source changes are required) and writes the
|
||||||
// resulting vertex and compute SPIR-V blobs to disk. The blobs can then be
|
// resulting vertex, pixel, and compute SPIR-V blobs to disk. The blobs can then be
|
||||||
// checked with spirv-val / spirv-dis.
|
// checked with spirv-val / spirv-dis.
|
||||||
//
|
//
|
||||||
// Programs that contain buffer_store_dword automatically get a single
|
// Programs that contain buffer_store_dword automatically get a single
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
// guestBuffers[0] (descriptor set 0, binding 0).
|
// guestBuffers[0] (descriptor set 0, binding 0).
|
||||||
//
|
//
|
||||||
// Each program carries an expectation: ExpectTranslate=true programs must
|
// Each program carries an expectation: ExpectTranslate=true programs must
|
||||||
// decode and emit both stages; ExpectTranslate=false programs pin a decode
|
// decode and emit the requested stages; ExpectTranslate=false programs pin a decode
|
||||||
// failure that must stay loud. Any unexpected outcome makes the tool exit
|
// failure that must stay loud. Any unexpected outcome makes the tool exit
|
||||||
// non-zero, so it can gate scripts/CI.
|
// non-zero, so it can gate scripts/CI.
|
||||||
//
|
//
|
||||||
@@ -43,6 +43,63 @@ const ulong ProgramAddress = 0x100000;
|
|||||||
0xD56C0005, 0x00020501, // v_mul_hi_i32 v5, v1, v2
|
0xD56C0005, 0x00020501, // v_mul_hi_i32 v5, v1, v2
|
||||||
0xBF810000, // s_endpgm
|
0xBF810000, // s_endpgm
|
||||||
]),
|
]),
|
||||||
|
("mrt", true, [
|
||||||
|
0x7E0002FF, 0x3F800000, // v_mov_b32 v0, 1.0f
|
||||||
|
0x7E0202FF, 0x00000000, // v_mov_b32 v1, 0.0f
|
||||||
|
0x7E0402FF, 0x00000000, // v_mov_b32 v2, 0.0f
|
||||||
|
0x7E0602FF, 0x3F800000, // v_mov_b32 v3, 1.0f
|
||||||
|
0x7E0802FF, 0x00000001, // v_mov_b32 v4, 1u
|
||||||
|
0x7E0A02FF, 0x00000002, // v_mov_b32 v5, 2u
|
||||||
|
0x7E0C02FF, 0x00000003, // v_mov_b32 v6, 3u
|
||||||
|
0x7E0E02FF, 0x00000004, // v_mov_b32 v7, 4u
|
||||||
|
0x7E1002FF, 0xFFFFFFFF, // v_mov_b32 v8, -1
|
||||||
|
0x7E1202FF, 0x00000002, // v_mov_b32 v9, 2
|
||||||
|
0x7E1402FF, 0xFFFFFFFD, // v_mov_b32 v10, -3
|
||||||
|
0x7E1602FF, 0x00000004, // v_mov_b32 v11, 4
|
||||||
|
0xF800000F, 0x03020100, // exp mrt0 v0, v1, v2, v3
|
||||||
|
0xF800003F, 0x07060504, // exp mrt3 v4, v5, v6, v7
|
||||||
|
0xF800086F, 0x0B0A0908, // exp mrt6 v8, v9, v10, v11 done
|
||||||
|
0xBF810000, // s_endpgm
|
||||||
|
]),
|
||||||
|
("mrt-float2", true, [
|
||||||
|
0x7E0002FF, 0x3F800000, // v_mov_b32 v0, 1.0f
|
||||||
|
0x7E0202FF, 0x3E800000, // v_mov_b32 v1, 0.25f
|
||||||
|
0x7E0402FF, 0x3E800000, // v_mov_b32 v2, 0.25f
|
||||||
|
0x7E0602FF, 0x3F000000, // v_mov_b32 v3, 0.5f
|
||||||
|
0xF800000F, 0x03020100, // exp mrt0 v0, v1, v2, v3
|
||||||
|
0xF800081F, 0x03020100, // exp mrt1 v0, v1, v2, v3 done
|
||||||
|
0xBF810000, // s_endpgm
|
||||||
|
]),
|
||||||
|
("mrt8", true, [
|
||||||
|
0x7E0002FF, 0x3F800000, // v_mov_b32 v0, 1.0f
|
||||||
|
0x7E0202FF, 0x00000000, // v_mov_b32 v1, 0.0f
|
||||||
|
0x7E0402FF, 0x00000000, // v_mov_b32 v2, 0.0f
|
||||||
|
0x7E0602FF, 0x3F800000, // v_mov_b32 v3, 1.0f
|
||||||
|
0xF800000F, 0x03020100, // exp mrt0 v0, v1, v2, v3
|
||||||
|
0xF800001F, 0x03020100, // exp mrt1 v0, v1, v2, v3
|
||||||
|
0xF800002F, 0x03020100, // exp mrt2 v0, v1, v2, v3
|
||||||
|
0xF800003F, 0x03020100, // exp mrt3 v0, v1, v2, v3
|
||||||
|
0xF800004F, 0x03020100, // exp mrt4 v0, v1, v2, v3
|
||||||
|
0xF800005F, 0x03020100, // exp mrt5 v0, v1, v2, v3
|
||||||
|
0xF800006F, 0x03020100, // exp mrt6 v0, v1, v2, v3
|
||||||
|
0xF800087F, 0x03020100, // exp mrt7 v0, v1, v2, v3 done
|
||||||
|
0xBF810000, // s_endpgm
|
||||||
|
]),
|
||||||
|
("mrt-partial", true, [
|
||||||
|
0x7E0002FF, 0x3F4CCCCD, // v_mov_b32 v0, 0.8f
|
||||||
|
0x7E0202FF, 0x3F333333, // v_mov_b32 v1, 0.7f
|
||||||
|
0xF8000803, 0x03020100, // exp mrt0 v0, v1, off, off done
|
||||||
|
0xBF810000, // s_endpgm
|
||||||
|
]),
|
||||||
|
("mrt-partial-merge", true, [
|
||||||
|
0x7E0002FF, 0x3DCCCCCD, // v_mov_b32 v0, 0.1f
|
||||||
|
0x7E0202FF, 0x3E4CCCCD, // v_mov_b32 v1, 0.2f
|
||||||
|
0x7E0C02FF, 0x3E99999A, // v_mov_b32 v6, 0.3f
|
||||||
|
0x7E0E02FF, 0x3ECCCCCD, // v_mov_b32 v7, 0.4f
|
||||||
|
0xF8000003, 0x03020100, // exp mrt0 v0, v1, off, off
|
||||||
|
0xF800080C, 0x07060504, // exp mrt0 off, off, v6, v7 done
|
||||||
|
0xBF810000, // s_endpgm
|
||||||
|
]),
|
||||||
("sopp-hints", true, [
|
("sopp-hints", true, [
|
||||||
0xBFA10001, // s_clause 0x1
|
0xBFA10001, // s_clause 0x1
|
||||||
0xBFA30000, // s_waitcnt_depctr 0x0
|
0xBFA30000, // s_waitcnt_depctr 0x0
|
||||||
@@ -102,10 +159,18 @@ var imageBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5ImageBinding")
|
|||||||
?? throw new InvalidOperationException("Gen5ImageBinding not found");
|
?? throw new InvalidOperationException("Gen5ImageBinding not found");
|
||||||
var globalBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5GlobalMemoryBinding")
|
var globalBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5GlobalMemoryBinding")
|
||||||
?? throw new InvalidOperationException("Gen5GlobalMemoryBinding not found");
|
?? throw new InvalidOperationException("Gen5GlobalMemoryBinding not found");
|
||||||
|
var pixelOutputBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5PixelOutputBinding")
|
||||||
|
?? throw new InvalidOperationException("Gen5PixelOutputBinding not found");
|
||||||
|
var pixelOutputKindType = assembly.GetType("SharpEmu.Libs.Agc.Gen5PixelOutputKind")
|
||||||
|
?? throw new InvalidOperationException("Gen5PixelOutputKind not found");
|
||||||
var tryCompile = spirvTranslator.GetMethod(
|
var tryCompile = spirvTranslator.GetMethod(
|
||||||
"TryCompileVertexShader",
|
"TryCompileVertexShader",
|
||||||
BindingFlags.Public | BindingFlags.Static)
|
BindingFlags.Public | BindingFlags.Static)
|
||||||
?? throw new InvalidOperationException("Gen5SpirvTranslator.TryCompileVertexShader not found");
|
?? throw new InvalidOperationException("Gen5SpirvTranslator.TryCompileVertexShader not found");
|
||||||
|
var tryCompilePixel = spirvTranslator.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.Single(method =>
|
||||||
|
method.Name == "TryCompilePixelShader" &&
|
||||||
|
method.GetParameters()[2].ParameterType.IsGenericType);
|
||||||
var tryCompileCompute = spirvTranslator.GetMethod(
|
var tryCompileCompute = spirvTranslator.GetMethod(
|
||||||
"TryCompileComputeShader",
|
"TryCompileComputeShader",
|
||||||
BindingFlags.Public | BindingFlags.Static)
|
BindingFlags.Public | BindingFlags.Static)
|
||||||
@@ -230,6 +295,94 @@ foreach (var (name, expectTranslate, words) in testPrograms)
|
|||||||
failures++;
|
failures++;
|
||||||
Console.WriteLine($"[{name}] compute emit: FAILED ({computeArgs[6]})");
|
Console.WriteLine($"[{name}] compute emit: FAILED ({computeArgs[6]})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name.StartsWith("mrt", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
(uint GuestSlot, uint HostLocation, string Kind)[] outputSpecs = name switch
|
||||||
|
{
|
||||||
|
"mrt" => new (uint GuestSlot, uint HostLocation, string Kind)[]
|
||||||
|
{
|
||||||
|
(0, 0, "Float"),
|
||||||
|
(3, 1, "Uint"),
|
||||||
|
(6, 2, "Sint"),
|
||||||
|
},
|
||||||
|
"mrt-float2" => [(0, 0, "Float"), (1, 1, "Float")],
|
||||||
|
"mrt8" => Enumerable.Range(0, 8)
|
||||||
|
.Select(index => ((uint)index, (uint)index, "Float"))
|
||||||
|
.ToArray(),
|
||||||
|
_ => [(0, 0, "Float")],
|
||||||
|
};
|
||||||
|
var pixelOutputs = Array.CreateInstance(pixelOutputBindingType, outputSpecs.Length);
|
||||||
|
for (var index = 0; index < outputSpecs.Length; index++)
|
||||||
|
{
|
||||||
|
var spec = outputSpecs[index];
|
||||||
|
pixelOutputs.SetValue(
|
||||||
|
Activator.CreateInstance(
|
||||||
|
pixelOutputBindingType,
|
||||||
|
spec.GuestSlot,
|
||||||
|
spec.HostLocation,
|
||||||
|
Enum.Parse(pixelOutputKindType, spec.Kind)),
|
||||||
|
index);
|
||||||
|
}
|
||||||
|
|
||||||
|
var pixelArgs = PadWithDefaults(
|
||||||
|
tryCompilePixel,
|
||||||
|
[state, evaluation, pixelOutputs, null, null]);
|
||||||
|
if ((bool)tryCompilePixel.Invoke(
|
||||||
|
null,
|
||||||
|
BindingFlags.OptionalParamBinding,
|
||||||
|
null,
|
||||||
|
pixelArgs,
|
||||||
|
null)!)
|
||||||
|
{
|
||||||
|
var shader = pixelArgs[3]!;
|
||||||
|
var spirv = (byte[])shader.GetType().GetProperty("Spirv")!.GetValue(shader)!;
|
||||||
|
var path = Path.Combine(outputDirectory, $"{name}-ps.spv");
|
||||||
|
File.WriteAllBytes(path, spirv);
|
||||||
|
Console.WriteLine($"[{name}] pixel emit: success, {spirv.Length} bytes -> {path}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
failures++;
|
||||||
|
Console.WriteLine($"[{name}] pixel emit: FAILED ({pixelArgs[4]})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "mrt")
|
||||||
|
{
|
||||||
|
var invalidOutputs = Array.CreateInstance(pixelOutputBindingType, 2);
|
||||||
|
invalidOutputs.SetValue(
|
||||||
|
Activator.CreateInstance(
|
||||||
|
pixelOutputBindingType,
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
Enum.Parse(pixelOutputKindType, "Float")),
|
||||||
|
0);
|
||||||
|
invalidOutputs.SetValue(
|
||||||
|
Activator.CreateInstance(
|
||||||
|
pixelOutputBindingType,
|
||||||
|
3u,
|
||||||
|
7u,
|
||||||
|
Enum.Parse(pixelOutputKindType, "Float")),
|
||||||
|
1);
|
||||||
|
var invalidPixelArgs = PadWithDefaults(
|
||||||
|
tryCompilePixel,
|
||||||
|
[state, evaluation, invalidOutputs, null, null]);
|
||||||
|
if ((bool)tryCompilePixel.Invoke(
|
||||||
|
null,
|
||||||
|
BindingFlags.OptionalParamBinding,
|
||||||
|
null,
|
||||||
|
invalidPixelArgs,
|
||||||
|
null)!)
|
||||||
|
{
|
||||||
|
failures++;
|
||||||
|
Console.WriteLine("[mrt] FAILED: sparse host locations were accepted");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[mrt] sparse host locations rejected as expected ({invalidPixelArgs[4]})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine(failures == 0
|
Console.WriteLine(failures == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user