mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-26 12:48:39 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 081760be3f |
@@ -100,6 +100,9 @@ jobs:
|
||||
- name: Build solution
|
||||
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
|
||||
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 Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new();
|
||||
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();
|
||||
private static readonly Dictionary<
|
||||
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
|
||||
@@ -3329,12 +3329,13 @@ public static class AgcExports
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
translatedDraw.AttributeCount,
|
||||
new VulkanGuestRenderTarget(
|
||||
firstTarget.Address,
|
||||
firstTarget.Width,
|
||||
firstTarget.Height,
|
||||
firstTarget.Format,
|
||||
firstTarget.NumberType),
|
||||
translatedDraw.RenderTargets.Select(target =>
|
||||
new VulkanGuestRenderTarget(
|
||||
target.Address,
|
||||
target.Width,
|
||||
target.Height,
|
||||
target.Format,
|
||||
target.NumberType)).ToArray(),
|
||||
translatedDraw.VertexSpirv,
|
||||
translatedDraw.VertexCount,
|
||||
translatedDraw.InstanceCount,
|
||||
@@ -3462,11 +3463,34 @@ public static class AgcExports
|
||||
}
|
||||
|
||||
var renderTargets = GetRenderTargets(state.CxRegisters)
|
||||
.Where(target =>
|
||||
target.Slot == 0 &&
|
||||
HasPixelColorExport(pixelState, target.Slot))
|
||||
.Where(target => HasPixelColorExport(pixelState, target.Slot))
|
||||
.OrderBy(target => target.Slot)
|
||||
.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 exportStateFingerprint = ComputeShaderStructureFingerprint(exportEvaluation);
|
||||
var pixelStateFingerprint = ComputeShaderStructureFingerprint(pixelEvaluation);
|
||||
@@ -3475,7 +3499,7 @@ public static class AgcExports
|
||||
exportStateFingerprint,
|
||||
pixelShaderAddress,
|
||||
pixelStateFingerprint,
|
||||
outputKind,
|
||||
outputLayout,
|
||||
attributeCount);
|
||||
var totalGlobalBuffers =
|
||||
pixelEvaluation.GlobalMemoryBindings.Count +
|
||||
@@ -3491,7 +3515,7 @@ public static class AgcExports
|
||||
if (!Gen5SpirvTranslator.TryCompilePixelShader(
|
||||
pixelState,
|
||||
pixelEvaluation,
|
||||
outputKind,
|
||||
pixelOutputs,
|
||||
out var pixelShader,
|
||||
out error,
|
||||
globalBufferBase: 0,
|
||||
@@ -3579,7 +3603,7 @@ public static class AgcExports
|
||||
vertexInputs,
|
||||
renderTargets,
|
||||
ApplyTransparentPremultipliedFillClear(
|
||||
CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault()),
|
||||
CreateRenderState(state.CxRegisters, renderTargets, pixelState),
|
||||
textures,
|
||||
vertexInputs,
|
||||
pixelEvaluation.InitialScalarRegisters));
|
||||
@@ -3595,7 +3619,8 @@ public static class AgcExports
|
||||
/// Chowdren resets its effect layers with an untextured transparent-black
|
||||
/// fill using premultiplied blending. With One/OneMinusSrcAlpha that draw
|
||||
/// 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>
|
||||
private static VulkanGuestRenderState ApplyTransparentPremultipliedFillClear(
|
||||
VulkanGuestRenderState renderState,
|
||||
@@ -3607,13 +3632,7 @@ public static class AgcExports
|
||||
textures.Count != 0 ||
|
||||
vertexInputs.Count != 0 ||
|
||||
pixelUserData.Count < 4 ||
|
||||
renderState.Blend is not
|
||||
{
|
||||
Enable: true,
|
||||
ColorSrcFactor: 1,
|
||||
ColorDstFactor: 5,
|
||||
ColorFunc: 0,
|
||||
})
|
||||
!renderState.Blends.All(IsTransparentPremultipliedFillBlend))
|
||||
{
|
||||
return renderState;
|
||||
}
|
||||
@@ -3628,10 +3647,21 @@ public static class AgcExports
|
||||
|
||||
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(
|
||||
CpuContext ctx,
|
||||
SubmittedDcbState state,
|
||||
@@ -3654,19 +3684,15 @@ public static class AgcExports
|
||||
: 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) =>
|
||||
state.Program.Instructions.Any(instruction =>
|
||||
instruction.Control is Gen5ExportControl export &&
|
||||
export.Target == target &&
|
||||
export.EnableMask != 0);
|
||||
GetPixelColorExportMask(state, target) != 0;
|
||||
|
||||
private static uint GetPixelColorExportMask(Gen5ShaderState state, uint target) =>
|
||||
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)
|
||||
{
|
||||
@@ -3824,11 +3850,25 @@ public static class AgcExports
|
||||
|
||||
private static VulkanGuestRenderState CreateRenderState(
|
||||
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);
|
||||
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,
|
||||
DecodeViewport(registers, target.Width, target.Height, scissor));
|
||||
}
|
||||
@@ -3853,7 +3893,7 @@ public static class AgcExports
|
||||
(control >> 24) & 0x1Fu,
|
||||
(control >> 21) & 0x7u,
|
||||
((control >> 29) & 1u) != 0,
|
||||
writeMask == 0 ? 0xFu : writeMask);
|
||||
writeMask);
|
||||
}
|
||||
|
||||
private static VulkanGuestRect? DecodeScissor(
|
||||
@@ -5191,7 +5231,7 @@ public static class AgcExports
|
||||
if (Gen5SpirvTranslator.TryCompilePixelShader(
|
||||
pixelState,
|
||||
evaluation,
|
||||
Gen5PixelOutputKind.Float,
|
||||
[new(0, 0, Gen5PixelOutputKind.Float)],
|
||||
out var compiledPixel,
|
||||
out var compileError))
|
||||
{
|
||||
|
||||
@@ -49,6 +49,11 @@ internal enum Gen5PixelOutputKind
|
||||
Sint,
|
||||
}
|
||||
|
||||
internal readonly record struct Gen5PixelOutputBinding(
|
||||
uint GuestSlot,
|
||||
uint HostLocation,
|
||||
Gen5PixelOutputKind Kind);
|
||||
|
||||
internal enum Gen5SpirvStage
|
||||
{
|
||||
Vertex,
|
||||
|
||||
@@ -19,13 +19,59 @@ internal static partial class Gen5SpirvTranslator
|
||||
int globalBufferBase = 0,
|
||||
int totalGlobalBufferCount = -1,
|
||||
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)
|
||||
{
|
||||
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(
|
||||
Gen5SpirvStage.Pixel,
|
||||
state,
|
||||
evaluation,
|
||||
outputKind,
|
||||
outputs,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
@@ -50,7 +96,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
Gen5SpirvStage.Vertex,
|
||||
state,
|
||||
evaluation,
|
||||
Gen5PixelOutputKind.Float,
|
||||
[],
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
@@ -74,7 +120,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
Gen5SpirvStage.Compute,
|
||||
state,
|
||||
evaluation,
|
||||
Gen5PixelOutputKind.Float,
|
||||
[],
|
||||
Math.Max(localSizeX, 1),
|
||||
Math.Max(localSizeY, 1),
|
||||
Math.Max(localSizeZ, 1),
|
||||
@@ -91,7 +137,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
private readonly Gen5SpirvStage _stage;
|
||||
private readonly Gen5ShaderState _state;
|
||||
private readonly Gen5ShaderEvaluation _evaluation;
|
||||
private readonly Gen5PixelOutputKind _outputKind;
|
||||
private readonly IReadOnlyList<Gen5PixelOutputBinding> _pixelOutputBindings;
|
||||
private readonly uint _localSizeX;
|
||||
private readonly uint _localSizeY;
|
||||
private readonly uint _localSizeZ;
|
||||
@@ -101,6 +147,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
private readonly int _scalarRegisterBufferIndex;
|
||||
private readonly List<uint> _interfaces = [];
|
||||
private readonly Dictionary<uint, uint> _pixelInputs = [];
|
||||
private readonly Dictionary<uint, SpirvPixelOutput> _pixelOutputs = [];
|
||||
private readonly Dictionary<uint, uint> _vertexOutputs = [];
|
||||
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
|
||||
private readonly List<SpirvImageResource> _imageResources = [];
|
||||
@@ -133,7 +180,6 @@ internal static partial class Gen5SpirvTranslator
|
||||
private uint _lds;
|
||||
private uint _workgroupUintPointer;
|
||||
private uint _positionOutput;
|
||||
private uint _pixelOutput;
|
||||
private uint _vertexIndexInput;
|
||||
private uint _instanceIndexInput;
|
||||
private uint _fragCoordInput;
|
||||
@@ -163,11 +209,16 @@ internal static partial class Gen5SpirvTranslator
|
||||
uint Type,
|
||||
uint ComponentCount);
|
||||
|
||||
private readonly record struct SpirvPixelOutput(
|
||||
uint Variable,
|
||||
uint Type,
|
||||
Gen5PixelOutputKind Kind);
|
||||
|
||||
public CompilationContext(
|
||||
Gen5SpirvStage stage,
|
||||
Gen5ShaderState state,
|
||||
Gen5ShaderEvaluation evaluation,
|
||||
Gen5PixelOutputKind outputKind,
|
||||
IReadOnlyList<Gen5PixelOutputBinding> pixelOutputBindings,
|
||||
uint localSizeX,
|
||||
uint localSizeY,
|
||||
uint localSizeZ,
|
||||
@@ -179,7 +230,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
_stage = stage;
|
||||
_state = state;
|
||||
_evaluation = evaluation;
|
||||
_outputKind = outputKind;
|
||||
_pixelOutputBindings = pixelOutputBindings;
|
||||
_localSizeX = localSizeX;
|
||||
_localSizeY = localSizeY;
|
||||
_localSizeZ = localSizeZ;
|
||||
@@ -700,19 +751,24 @@ internal static partial class Gen5SpirvTranslator
|
||||
(uint)SpirvBuiltIn.FragCoord);
|
||||
_interfaces.Add(_fragCoordInput);
|
||||
|
||||
var outputType = _outputKind switch
|
||||
foreach (var binding in _pixelOutputBindings)
|
||||
{
|
||||
Gen5PixelOutputKind.Uint => _uvec4Type,
|
||||
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
|
||||
_ => _vec4Type,
|
||||
};
|
||||
var outputPointer =
|
||||
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
||||
_pixelOutput = _module.AddGlobalVariable(
|
||||
outputPointer,
|
||||
SpirvStorageClass.Output);
|
||||
_module.AddDecoration(_pixelOutput, SpirvDecoration.Location, 0);
|
||||
_interfaces.Add(_pixelOutput);
|
||||
var outputType = GetPixelOutputType(binding.Kind);
|
||||
var outputPointer =
|
||||
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
||||
var variable = _module.AddGlobalVariable(
|
||||
outputPointer,
|
||||
SpirvStorageClass.Output);
|
||||
_module.AddName(variable, $"mrt{binding.GuestSlot}");
|
||||
_module.AddDecoration(
|
||||
variable,
|
||||
SpirvDecoration.Location,
|
||||
binding.HostLocation);
|
||||
_pixelOutputs.Add(
|
||||
binding.GuestSlot,
|
||||
new SpirvPixelOutput(variable, outputType, binding.Kind));
|
||||
_interfaces.Add(variable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -831,7 +887,10 @@ internal static partial class Gen5SpirvTranslator
|
||||
1);
|
||||
StoreV(2, Bitcast(_uintType, x), 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
|
||||
{
|
||||
@@ -2084,21 +2143,27 @@ internal static partial class Gen5SpirvTranslator
|
||||
|
||||
if (_stage == Gen5SpirvStage.Pixel)
|
||||
{
|
||||
if (export.Target != 0)
|
||||
if (!_pixelOutputs.TryGetValue(export.Target, out var output))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var outputType = GetPixelOutputType();
|
||||
var values = new uint[4];
|
||||
for (var component = 0; component < 4; component++)
|
||||
{
|
||||
var enabled = (export.EnableMask & (1u << component)) != 0;
|
||||
if (!enabled)
|
||||
{
|
||||
values[component] = _outputKind == Gen5PixelOutputKind.Sint
|
||||
? Bitcast(_intType, UInt(0))
|
||||
: UInt(0);
|
||||
values[component] = _module.AddInstruction(
|
||||
SpirvOp.CompositeExtract,
|
||||
output.Kind switch
|
||||
{
|
||||
Gen5PixelOutputKind.Uint => _uintType,
|
||||
Gen5PixelOutputKind.Sint => _intType,
|
||||
_ => _floatType,
|
||||
},
|
||||
Load(output.Type, output.Variable),
|
||||
(uint)component);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2107,7 +2172,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
var value = LoadCompressedExportComponent(
|
||||
instruction,
|
||||
component);
|
||||
values[component] = _outputKind switch
|
||||
values[component] = output.Kind switch
|
||||
{
|
||||
Gen5PixelOutputKind.Uint => _module.AddInstruction(
|
||||
SpirvOp.ConvertFToU,
|
||||
@@ -2123,7 +2188,7 @@ internal static partial class Gen5SpirvTranslator
|
||||
}
|
||||
|
||||
var raw = LoadV(instruction.Sources[component].Value);
|
||||
values[component] = _outputKind switch
|
||||
values[component] = output.Kind switch
|
||||
{
|
||||
Gen5PixelOutputKind.Uint => raw,
|
||||
Gen5PixelOutputKind.Sint => Bitcast(_intType, raw),
|
||||
@@ -2133,15 +2198,15 @@ internal static partial class Gen5SpirvTranslator
|
||||
|
||||
var vector = _module.AddInstruction(
|
||||
SpirvOp.CompositeConstruct,
|
||||
outputType,
|
||||
output.Type,
|
||||
values);
|
||||
vector = _module.AddInstruction(
|
||||
SpirvOp.Select,
|
||||
outputType,
|
||||
output.Type,
|
||||
Load(_boolType, _exec),
|
||||
vector,
|
||||
Load(outputType, _pixelOutput));
|
||||
Store(_pixelOutput, vector);
|
||||
Load(output.Type, output.Variable));
|
||||
Store(output.Variable, vector);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2209,8 +2274,8 @@ internal static partial class Gen5SpirvTranslator
|
||||
(uint)(component & 1));
|
||||
}
|
||||
|
||||
private uint GetPixelOutputType() =>
|
||||
_outputKind switch
|
||||
private uint GetPixelOutputType(Gen5PixelOutputKind kind) =>
|
||||
kind switch
|
||||
{
|
||||
Gen5PixelOutputKind.Uint => _uvec4Type,
|
||||
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
// Feeds hand-assembled Gen5 (gfx10) instruction words through the real
|
||||
// decode -> SPIR-V pipeline (Gen5ShaderTranslator / Gen5SpirvTranslator, via
|
||||
// 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.
|
||||
//
|
||||
// Programs that contain buffer_store_dword automatically get a single
|
||||
@@ -14,7 +14,7 @@
|
||||
// guestBuffers[0] (descriptor set 0, binding 0).
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
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, [
|
||||
0xBFA10001, // s_clause 0x1
|
||||
0xBFA30000, // s_waitcnt_depctr 0x0
|
||||
@@ -102,10 +159,18 @@ var imageBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5ImageBinding")
|
||||
?? throw new InvalidOperationException("Gen5ImageBinding not found");
|
||||
var globalBindingType = assembly.GetType("SharpEmu.Libs.Agc.Gen5GlobalMemoryBinding")
|
||||
?? 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(
|
||||
"TryCompileVertexShader",
|
||||
BindingFlags.Public | BindingFlags.Static)
|
||||
?? 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(
|
||||
"TryCompileComputeShader",
|
||||
BindingFlags.Public | BindingFlags.Static)
|
||||
@@ -230,6 +295,94 @@ foreach (var (name, expectTranslate, words) in testPrograms)
|
||||
failures++;
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user