mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-02 07:59:44 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 081760be3f |
@@ -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,
|
||||||
|
translatedDraw.RenderTargets.Select(target =>
|
||||||
new VulkanGuestRenderTarget(
|
new VulkanGuestRenderTarget(
|
||||||
firstTarget.Address,
|
target.Address,
|
||||||
firstTarget.Width,
|
target.Width,
|
||||||
firstTarget.Height,
|
target.Height,
|
||||||
firstTarget.Format,
|
target.Format,
|
||||||
firstTarget.NumberType),
|
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),
|
|
||||||
_ => _vec4Type,
|
|
||||||
};
|
|
||||||
var outputPointer =
|
var outputPointer =
|
||||||
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
_module.TypePointer(SpirvStorageClass.Output, outputType);
|
||||||
_pixelOutput = _module.AddGlobalVariable(
|
var variable = _module.AddGlobalVariable(
|
||||||
outputPointer,
|
outputPointer,
|
||||||
SpirvStorageClass.Output);
|
SpirvStorageClass.Output);
|
||||||
_module.AddDecoration(_pixelOutput, SpirvDecoration.Location, 0);
|
_module.AddName(variable, $"mrt{binding.GuestSlot}");
|
||||||
_interfaces.Add(_pixelOutput);
|
_module.AddDecoration(
|
||||||
|
variable,
|
||||||
|
SpirvDecoration.Location,
|
||||||
|
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(
|
||||||
@@ -464,11 +474,51 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
IReadOnlyList<VulkanGuestVertexBuffer>? vertexBuffers = null,
|
||||||
VulkanGuestRenderState? renderState = null)
|
VulkanGuestRenderState? renderState = null)
|
||||||
{
|
{
|
||||||
if (pixelSpirv.Length == 0 ||
|
SubmitOffscreenTranslatedDraw(
|
||||||
target.Address == 0 ||
|
pixelSpirv,
|
||||||
target.Width == 0 ||
|
textures,
|
||||||
target.Height == 0)
|
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 ||
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +526,17 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
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,6 +546,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
var guestTextureFormat = GetGuestTextureFormat(
|
var guestTextureFormat = GetGuestTextureFormat(
|
||||||
target.Format,
|
target.Format,
|
||||||
target.NumberType);
|
target.NumberType);
|
||||||
@@ -493,6 +555,7 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
{
|
{
|
||||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
_availableGuestImages[target.Address] = guestTextureFormat;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EnqueueGuestWorkLocked(
|
EnqueueGuestWorkLocked(
|
||||||
new VulkanOffscreenGuestDraw(
|
new VulkanOffscreenGuestDraw(
|
||||||
@@ -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
|
|
||||||
{
|
{
|
||||||
(9, _) => 9,
|
if (!TryDecodeRenderTargetFormat(format, numberType, out var decoded))
|
||||||
(4, 4) => GuestFormatR32Uint,
|
{
|
||||||
(4, 5) => GuestFormatR32Sint,
|
return 0;
|
||||||
(4, 7) => GuestFormatR32Sfloat,
|
}
|
||||||
(5, 4) => GuestFormatR16G16Uint,
|
|
||||||
(5, 5) => GuestFormatR16G16Sint,
|
return decoded.Format switch
|
||||||
(5, 7) => GuestFormatR16G16Sfloat,
|
{
|
||||||
(10, 4) => GuestFormatR8G8B8A8Uint,
|
Format.R8Unorm => 36,
|
||||||
(10, 5) => GuestFormatR8G8B8A8Sint,
|
Format.R8Uint => 49,
|
||||||
(10, _) => 56,
|
Format.R8G8Unorm => 3,
|
||||||
(12, 4) => GuestFormatR16G16B16A16Uint,
|
Format.A2R10G10B10UnormPack32 => 9,
|
||||||
(12, 5) => GuestFormatR16G16B16A16Sint,
|
Format.B10G11R11UfloatPack32 => 7,
|
||||||
(12, 7) => 71,
|
Format.R32Uint => GuestFormatR32Uint,
|
||||||
(_, 0) when IsKnownGuestTextureFormat(format) => format,
|
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,
|
_ => 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,14 +2346,16 @@ 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))
|
{
|
||||||
|
if (ShouldTraceGuestImageContents(renderTarget))
|
||||||
{
|
{
|
||||||
images.Add(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++)
|
||||||
|
{
|
||||||
|
var hasPriorContents = targets[index].Initialized || targets[index].InitialUploadPending;
|
||||||
|
anyPriorContents |= hasPriorContents;
|
||||||
|
toColorAttachments[index] = new ImageMemoryBarrier
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageMemoryBarrier,
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
SrcAccessMask = targetHasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
SrcAccessMask = hasPriorContents ? AccessFlags.ShaderReadBit : 0,
|
||||||
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
DstAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||||
OldLayout = targetHasPriorContents
|
OldLayout = hasPriorContents
|
||||||
? ImageLayout.ShaderReadOnlyOptimal
|
? ImageLayout.ShaderReadOnlyOptimal
|
||||||
: ImageLayout.Undefined,
|
: ImageLayout.Undefined,
|
||||||
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
NewLayout = ImageLayout.ColorAttachmentOptimal,
|
||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
Image = target.Image,
|
Image = targets[index].Image,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
SubresourceRange = ColorSubresourceRange(),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
targetHasPriorContents
|
anyPriorContents
|
||||||
? PipelineStageFlags.AllCommandsBit
|
? PipelineStageFlags.AllCommandsBit
|
||||||
: PipelineStageFlags.TopOfPipeBit,
|
: PipelineStageFlags.TopOfPipeBit,
|
||||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
@@ -4777,17 +5004,20 @@ 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++)
|
||||||
|
{
|
||||||
|
toShaderRead[index] = new ImageMemoryBarrier
|
||||||
{
|
{
|
||||||
SType = StructureType.ImageMemoryBarrier,
|
SType = StructureType.ImageMemoryBarrier,
|
||||||
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
SrcAccessMask = AccessFlags.ColorAttachmentWriteBit,
|
||||||
@@ -4796,9 +5026,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||||
Image = target.Image,
|
Image = targets[index].Image,
|
||||||
SubresourceRange = ColorSubresourceRange(),
|
SubresourceRange = ColorSubresourceRange(),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
_vk.CmdPipelineBarrier(
|
_vk.CmdPipelineBarrier(
|
||||||
_commandBuffer,
|
_commandBuffer,
|
||||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
@@ -4808,31 +5039,45 @@ 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;
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
target.Initialized = true;
|
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)
|
|
||||||
{
|
{
|
||||||
|
for (var index = 0; index < targets.Length; index++)
|
||||||
|
{
|
||||||
|
var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat(
|
||||||
|
work.Targets[index].Format,
|
||||||
|
work.Targets[index].NumberType);
|
||||||
|
if (guestTextureFormat == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
_availableGuestImages[target.Address] = guestTextureFormat;
|
_availableGuestImages[targets[index].Address] = guestTextureFormat;
|
||||||
_gpuGuestImages[target.Address] = guestTextureFormat;
|
_gpuGuestImages[targets[index].Address] = guestTextureFormat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
|
if (ShouldTraceGuestImageWriteForDiagnostics(target.Address))
|
||||||
{
|
{
|
||||||
var writeCount = _tracedGuestWriteCounts.TryGetValue(
|
var writeCount = _tracedGuestWriteCounts.TryGetValue(
|
||||||
@@ -4854,9 +5099,10 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
TraceGuestImageContents(target);
|
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) ||
|
|
||||||
!failedTarget.Initialized)
|
|
||||||
{
|
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
_availableGuestImages.Remove(work.Target.Address);
|
foreach (var target in work.Targets)
|
||||||
_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(
|
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,11 +5313,28 @@ 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
colorAttachments[index] = new AttachmentDescription
|
||||||
|
{
|
||||||
|
Format = formats[index],
|
||||||
Samples = SampleCountFlags.Count1Bit,
|
Samples = SampleCountFlags.Count1Bit,
|
||||||
LoadOp = AttachmentLoadOp.Load,
|
LoadOp = AttachmentLoadOp.Load,
|
||||||
StoreOp = AttachmentStoreOp.Store,
|
StoreOp = AttachmentStoreOp.Store,
|
||||||
@@ -5065,22 +5343,25 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
InitialLayout = ImageLayout.ColorAttachmentOptimal,
|
InitialLayout = ImageLayout.ColorAttachmentOptimal,
|
||||||
FinalLayout = ImageLayout.ColorAttachmentOptimal,
|
FinalLayout = ImageLayout.ColorAttachmentOptimal,
|
||||||
};
|
};
|
||||||
var colorReference = new AttachmentReference
|
colorReferences[index] = new AttachmentReference
|
||||||
{
|
{
|
||||||
Attachment = 0,
|
Attachment = (uint)index,
|
||||||
Layout = ImageLayout.ColorAttachmentOptimal,
|
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