mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 23:19:44 +08:00
[Gpu] Backend-neutral shader compiler and guest-GPU renderer seam (#200)
* [ShaderCompiler] Extract the backend-neutral shader compiler project Move the Gen5 (gfx10) microcode decoder, the scalar evaluator, the shader IR, and the metadata reader out of SharpEmu.Libs/Agc into a new SharpEmu.ShaderCompiler project — the half of shader compilation every codegen backend (SPIR-V today; MSL and DXIL later) consumes. Types go public: they are the contract now. Nothing in the project may depend on a host graphics API; the SPIR-V-specific artifact types (Gen5SpirvShader, Gen5SpirvStage) stay beside the emitter in Libs. Three couplings surfaced by the move, each resolved at the right depth: GuestDrawKind was defined inside VulkanVideoPresenter despite being a guest-domain, decoder-produced concept — it moves to the shared project; the evaluator's one HLE dependency (the tracked-libc-heap read fallback) becomes an injectable hook that a Libs module initializer installs before any caller can reach the evaluator; and the inline- constant table is promoted to a shared Gen5InlineConstants so backends cannot drift on constant semantics (the SPIR-V translator now delegates to it). The ShaderDump tool drops its reflection over the moved types in favor of direct typed calls; only the SPIR-V emitter, still internal to Libs until it moves to its own backend project, is reached via reflection. Verified by a clean solution build, the existing test suite, and a full ShaderDump conformance run. * [ShaderCompiler] Move the SPIR-V emitter into SharpEmu.ShaderCompiler.Vulkan Gen5SpirvTranslator (with its ALU partial), SpirvModuleBuilder, SpirvFixedShaders, and the Gen5SpirvShader/Gen5SpirvStage artifact types move whole from SharpEmu.Libs/Agc into the first per-backend codegen project. Notably it needs no Vulkan bindings reference: emitters produce bytes from the shared IR; renderers own graphics APIs. Types go public as the backend's contract; AgcExports and the presenter consume them exactly as before. The ShaderDump tool drops its last reflection: with both halves of the pipeline public it drives decode and all three emit entry points with direct typed calls, retiring the PadWithDefaults invoke shim — and it no longer references SharpEmu.Libs at all, making the conformance tool emulator-independent by design. Verified by a clean solution build, the test suite, a full ShaderDump conformance run, and a locked-mode restore under the pinned SDK. * [Gpu] Extract the guest-GPU backend seam (IGuestGpuBackend) The AGC/VideoOut/SystemService export layers now reach the renderer through IGuestGpuBackend via GuestGpu.Current (mirroring HostPlatform), instead of calling VulkanVideoPresenter statics. The Vulkan backend is a thin adapter over the existing presenter, so the extraction stays mechanical; only the adapter and the presenter itself reference the presenter now. The types crossing the seam move to Gpu/GuestGpuTypes.cs and drop their Vulkan prefixes, which an audit showed were misnomers: every field is a neutral primitive or a raw guest value (guest addresses, format and number-type codes, CB_BLEND register bitfields, verbatim sampler descriptor dwords). The one genuine Vulkan value in the old surface — the Silk.NET Format inside VulkanRenderTargetFormat, which callers never read — stops crossing: TryDecodeRenderTargetFormat is replaced at the seam by TryGetRenderTargetOutputKind, which surfaces only the Gen5PixelOutputKind callers actually consume, keeping native formats a backend-internal concern. ToVulkanSampler in AgcExports is renamed ToGuestSampler to match what it always produced. Seam rules are documented on the interface: no host-API value crosses, and submission stays coarse-grained with synchronization internal to backends. Interim exception, resolved next: shader parameters are still SPIR-V blobs. * [Gpu] Move shader compilation behind the guest-GPU backend The seam's interim exception is gone: AgcExports no longer calls Gen5SpirvTranslator or handles SPIR-V bytes. IGuestGpuBackend gains the three TryCompile entry points, which take the backend-neutral (Gen5ShaderState, Gen5ShaderEvaluation) contract plus the flat per-role resource-slot bases a multi-stage draw needs, and return opaque IGuestCompiledShader handles that only the producing backend can submit — the Vulkan backend wraps its SPIR-V in VulkanCompiledGuestShader and rejects foreign handles loudly. Draw and dispatch submissions take handles instead of byte arrays; the shader caches in AgcExports store handles. IGuestCompiledShader.Payload exposes the backend-defined compiled bytes for exactly two callers: the diagnostics dump and the size trace — documented as never-interpret. The unused _pixelSpirvCache is deleted. With this, a Metal or DX12 backend plugs in by implementing IGuestGpuBackend with its own codegen; nothing in the export layers knows which shader format exists. Verified by a clean solution build, the test suite, and a full ShaderDump conformance run under the pinned SDK. * [Gpu] Fix rename collateral from the seam extraction Address review findings: a doc comment picked up the mechanical VulkanVideoPresenter -> GuestGpu.Current rewrite and ended up naming members that do not exist on the interface, and CreateVulkanIndexBuffer kept its Vulkan prefix while every sibling factory was de-Vulkanized — it produces the neutral GuestIndexBuffer, so it is CreateGuestIndexBuffer. * [Gpu] Label diagnostics dumps with the backend's payload extension Address the review's altitude finding on DumpSpirv: the dump helper's IR-disassembly half is backend-neutral and stays put, but writing the opaque payload to a hardcoded .spv interpreted bytes the seam says never to interpret. IGuestCompiledShader now declares its payload's file extension, and the renamed DumpCompiledShader takes the handle and writes honestly-labeled dumps whichever backend produced them. * [Gpu] Make the shader-cache hit path allocation-free and lock-free Every translated draw built its cache key with a LINQ Select feeding string.Join plus one interpolated string per render target — steady per-draw allocation whether or not the shaders were already cached. The output layout is now packed exactly into a ulong (guest slot in 6 bits + output kind in 2 bits per target, host locations being the byte positions, target count in the key beside it), and the Gen5PixelOutputBinding array is only materialized on a cache miss, where compilation dwarfs it. The graphics/compute shader caches switch from Dictionary guarded by _submitTraceGate to ConcurrentDictionary, making the per-draw and per-dispatch hit paths lock-free and decoupling them from the tracing gate they coincidentally shared. And the seam-shaped render-target list is built once when a translated draw is created instead of a Select/ToArray per submission of a cached draw. * [Gpu] Replace LINQ with explicit loops in code this branch introduced Project rule going forward: no LINQ — it allocates enumerators, closures, and delegates, and this codebase is GC-pause-sensitive. The pixel-output and guest-render-target array builds and the ShaderDump store-PC collection become plain loops; pre-existing LINQ elsewhere is left for changes that already touch those lines. * [ShaderCompiler] Suppress CA2255 on the evaluator hook installer The analyzer coverage that arrived with the rebase flags ModuleInitializer in library code; this is the rule's intended advanced scenario — the hook must be installed before any code path can reach the evaluator, and every such path enters through this assembly — so suppress with that justification rather than weaken the guarantee to a static constructor's lazier timing. * [Gpu] Resolve rebase artifacts onto main Dedupe the System.Collections.Concurrent using in AgcExports that the rebase merge duplicated (main and this branch each added it), and regenerate the lock files for the new shader-compiler projects and SharpEmu.Libs against main's current package graph so --locked-mode restore matches at the branch tip. * [CI] Comment per-platform build artifact links on PRs Adds a workflow_run workflow that, after "Build and Release" finishes a pull-request build, posts (and keeps updated in place) a single PR comment linking the Windows, Linux, and macOS artifacts from that run. It runs via workflow_run rather than in the build workflow because PRs from forks build with a read-only token that cannot comment; the follow-on run executes in the base-repo context with write access and without checking out fork code. GitHub only triggers workflow_run from the default branch, so this takes effect once merged to main.
This commit is contained in:
committed by
GitHub
parent
c69ac6ddab
commit
30fdd8d6ed
+144
-134
@@ -1,11 +1,13 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Libs.Gpu;
|
||||
using SharpEmu.ShaderCompiler;
|
||||
using SharpEmu.Libs.Kernel;
|
||||
using SharpEmu.Libs.VideoOut;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SharpEmu.Libs.Agc;
|
||||
@@ -157,13 +159,14 @@ public static class AgcExports
|
||||
private static readonly HashSet<ulong> _tracedComputeShaders = new();
|
||||
private static readonly Dictionary<(ulong Address, uint Width, uint Height), ulong> _tracedTextureHashes = [];
|
||||
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, string OutputLayout, uint Attributes),
|
||||
(byte[] Vertex, byte[] Pixel)> _graphicsSpirvCache = new();
|
||||
private static readonly Dictionary<
|
||||
// Concurrent so the per-draw/per-dispatch hit path is lock-free (and no longer
|
||||
// shares _submitTraceGate with tracing).
|
||||
private static readonly ConcurrentDictionary<
|
||||
(ulong Es, ulong EsState, ulong Ps, ulong PsState, ulong OutputLayout, uint OutputCount, uint Attributes),
|
||||
(IGuestCompiledShader Vertex, IGuestCompiledShader Pixel)> _graphicsShaderCache = new();
|
||||
private static readonly ConcurrentDictionary<
|
||||
(ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ),
|
||||
byte[]> _computeSpirvCache = new();
|
||||
IGuestCompiledShader> _computeShaderCache = new();
|
||||
private static readonly Dictionary<ulong, ulong> _shaderHeadersByCode = new();
|
||||
private static readonly bool _traceAgc = string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"),
|
||||
@@ -334,17 +337,20 @@ public static class AgcExports
|
||||
ulong ExportShaderAddress,
|
||||
ulong PixelShaderAddress,
|
||||
uint PrimitiveType,
|
||||
byte[] VertexSpirv,
|
||||
byte[] PixelSpirv,
|
||||
IGuestCompiledShader VertexShader,
|
||||
IGuestCompiledShader PixelShader,
|
||||
uint AttributeCount,
|
||||
uint VertexCount,
|
||||
uint InstanceCount,
|
||||
VulkanGuestIndexBuffer? IndexBuffer,
|
||||
GuestIndexBuffer? IndexBuffer,
|
||||
IReadOnlyList<TranslatedImageBinding> Textures,
|
||||
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
|
||||
IReadOnlyList<Gen5VertexInputBinding> VertexInputs,
|
||||
IReadOnlyList<RenderTargetDescriptor> RenderTargets,
|
||||
VulkanGuestRenderState RenderState);
|
||||
// The seam-shaped view of RenderTargets, built once here so the per-frame
|
||||
// submit path does not rebuild it for every draw of a cached translation.
|
||||
IReadOnlyList<GuestRenderTarget> GuestTargets,
|
||||
GuestRenderState RenderState);
|
||||
|
||||
private sealed record TranslatedImageBinding(
|
||||
TextureDescriptor Descriptor,
|
||||
@@ -2942,7 +2948,7 @@ public static class AgcExports
|
||||
handle,
|
||||
displayBufferIndex,
|
||||
out var cachedDisplayBuffer) &&
|
||||
VulkanVideoPresenter.TrySubmitGuestImage(
|
||||
GuestGpu.Current.TrySubmitGuestImage(
|
||||
cachedDisplayBuffer.Address,
|
||||
cachedDisplayBuffer.Width,
|
||||
cachedDisplayBuffer.Height,
|
||||
@@ -2966,11 +2972,11 @@ public static class AgcExports
|
||||
displayBufferIndex,
|
||||
translatedDisplayBuffer,
|
||||
"draw-fallback");
|
||||
var textures = CreateVulkanGuestDrawTextures(ctx, translatedDraw.Textures, out var fallbackTextureCount);
|
||||
var textures = CreateGuestDrawTextures(ctx, translatedDraw.Textures, out var fallbackTextureCount);
|
||||
var globalMemoryBuffers =
|
||||
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
VulkanVideoPresenter.SubmitTranslatedDraw(
|
||||
translatedDraw.PixelSpirv,
|
||||
CreateGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
GuestGpu.Current.SubmitTranslatedDraw(
|
||||
translatedDraw.PixelShader,
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
translatedDisplayBuffer.Width,
|
||||
@@ -2978,7 +2984,7 @@ public static class AgcExports
|
||||
translatedDraw.AttributeCount);
|
||||
TraceAgcShader(
|
||||
$"agc.shader_present ps=0x{translatedDraw.PixelShaderAddress:X16} " +
|
||||
$"spirv={translatedDraw.PixelSpirv.Length} textures={textures.Count} " +
|
||||
$"spirv={translatedDraw.PixelShader.Payload.Length} textures={textures.Count} " +
|
||||
$"global_buffers={globalMemoryBuffers.Count} " +
|
||||
$"fallback={fallbackTextureCount} {translatedDisplayBuffer.Width}x{translatedDisplayBuffer.Height}");
|
||||
|
||||
@@ -3013,7 +3019,7 @@ public static class AgcExports
|
||||
displayBufferIndex,
|
||||
out var displayBuffer))
|
||||
{
|
||||
VulkanVideoPresenter.SubmitGuestDraw(
|
||||
GuestGpu.Current.SubmitGuestDraw(
|
||||
state.GuestDrawKind,
|
||||
displayBuffer.Width,
|
||||
displayBuffer.Height);
|
||||
@@ -3409,29 +3415,23 @@ public static class AgcExports
|
||||
var firstTarget = translatedDraw.RenderTargets.FirstOrDefault();
|
||||
if (firstTarget.Address != 0)
|
||||
{
|
||||
var textures = CreateVulkanGuestDrawTextures(
|
||||
var textures = CreateGuestDrawTextures(
|
||||
ctx,
|
||||
translatedDraw.Textures,
|
||||
out _);
|
||||
var globalMemoryBuffers =
|
||||
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
CreateGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
var vertexBuffers =
|
||||
CreateVulkanGuestVertexBuffers(translatedDraw.VertexInputs);
|
||||
CreateGuestVertexBuffers(translatedDraw.VertexInputs);
|
||||
TraceRectListVertices(translatedDraw, vertexBuffers);
|
||||
TraceGrassDrawVertices(translatedDraw, textures, vertexBuffers);
|
||||
VulkanVideoPresenter.SubmitOffscreenTranslatedDraw(
|
||||
translatedDraw.PixelSpirv,
|
||||
GuestGpu.Current.SubmitOffscreenTranslatedDraw(
|
||||
translatedDraw.PixelShader,
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
translatedDraw.AttributeCount,
|
||||
translatedDraw.RenderTargets.Select(target =>
|
||||
new VulkanGuestRenderTarget(
|
||||
target.Address,
|
||||
target.Width,
|
||||
target.Height,
|
||||
target.Format,
|
||||
target.NumberType)).ToArray(),
|
||||
translatedDraw.VertexSpirv,
|
||||
translatedDraw.GuestTargets,
|
||||
translatedDraw.VertexShader,
|
||||
translatedDraw.VertexCount,
|
||||
translatedDraw.InstanceCount,
|
||||
translatedDraw.PrimitiveType,
|
||||
@@ -3445,14 +3445,14 @@ public static class AgcExports
|
||||
.FirstOrDefault(binding => binding.IsStorage);
|
||||
if (storageTarget is not null)
|
||||
{
|
||||
var textures = CreateVulkanGuestDrawTextures(
|
||||
var textures = CreateGuestDrawTextures(
|
||||
ctx,
|
||||
translatedDraw.Textures,
|
||||
out _);
|
||||
var globalMemoryBuffers =
|
||||
CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
VulkanVideoPresenter.SubmitStorageTranslatedDraw(
|
||||
translatedDraw.PixelSpirv,
|
||||
CreateGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings);
|
||||
GuestGpu.Current.SubmitStorageTranslatedDraw(
|
||||
translatedDraw.PixelShader,
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
translatedDraw.AttributeCount,
|
||||
@@ -3561,14 +3561,14 @@ public static class AgcExports
|
||||
.Where(target => HasPixelColorExport(pixelState, target.Slot))
|
||||
.OrderBy(target => target.Slot)
|
||||
.ToArray();
|
||||
var renderTargetFormats = new VulkanRenderTargetFormat[renderTargets.Length];
|
||||
var renderTargetOutputKinds = new Gen5PixelOutputKind[renderTargets.Length];
|
||||
for (var index = 0; index < renderTargets.Length; index++)
|
||||
{
|
||||
var target = renderTargets[index];
|
||||
if (!VulkanVideoPresenter.TryDecodeRenderTargetFormat(
|
||||
if (!GuestGpu.Current.TryGetRenderTargetOutputKind(
|
||||
target.Format,
|
||||
target.NumberType,
|
||||
out renderTargetFormats[index]))
|
||||
out renderTargetOutputKinds[index]))
|
||||
{
|
||||
error =
|
||||
$"unsupported color target format={target.Format} number_type={target.NumberType}";
|
||||
@@ -3576,16 +3576,17 @@ public static class AgcExports
|
||||
}
|
||||
}
|
||||
|
||||
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}"));
|
||||
// Exact packed encoding of the output layout — guest slot (6 bits, CB targets are
|
||||
// 0-7) plus output kind (2 bits) per target, host locations being the sequential
|
||||
// byte positions. Replaces a per-draw LINQ + string build that allocated on every
|
||||
// draw, cache hit or not; the target count disambiguates trailing zero bytes.
|
||||
var outputLayout = 0UL;
|
||||
for (var index = 0; index < renderTargets.Length; index++)
|
||||
{
|
||||
outputLayout |= (ulong)(((renderTargets[index].Slot & 0x3Fu) << 2) |
|
||||
(uint)renderTargetOutputKinds[index]) << (index * 8);
|
||||
}
|
||||
|
||||
var attributeCount = GetInterpolatedAttributeCount(pixelState);
|
||||
var exportStateFingerprint = ComputeShaderStructureFingerprint(exportEvaluation);
|
||||
var pixelStateFingerprint = ComputeShaderStructureFingerprint(pixelEvaluation);
|
||||
@@ -3595,19 +3596,25 @@ public static class AgcExports
|
||||
pixelShaderAddress,
|
||||
pixelStateFingerprint,
|
||||
outputLayout,
|
||||
(uint)renderTargets.Length,
|
||||
attributeCount);
|
||||
var totalGlobalBuffers =
|
||||
pixelEvaluation.GlobalMemoryBindings.Count +
|
||||
exportEvaluation.GlobalMemoryBindings.Count;
|
||||
(byte[] Vertex, byte[] Pixel) compiled;
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
_graphicsSpirvCache.TryGetValue(shaderKey, out compiled);
|
||||
}
|
||||
_graphicsShaderCache.TryGetValue(shaderKey, out var compiled);
|
||||
|
||||
if (compiled.Vertex is null || compiled.Pixel is null)
|
||||
{
|
||||
if (!Gen5SpirvTranslator.TryCompilePixelShader(
|
||||
var pixelOutputs = new Gen5PixelOutputBinding[renderTargets.Length];
|
||||
for (var location = 0; location < renderTargets.Length; location++)
|
||||
{
|
||||
pixelOutputs[location] = new Gen5PixelOutputBinding(
|
||||
renderTargets[location].Slot,
|
||||
(uint)location,
|
||||
renderTargetOutputKinds[location]);
|
||||
}
|
||||
|
||||
if (!GuestGpu.Current.TryCompilePixelShader(
|
||||
pixelState,
|
||||
pixelEvaluation,
|
||||
pixelOutputs,
|
||||
@@ -3617,7 +3624,7 @@ public static class AgcExports
|
||||
totalGlobalBufferCount: totalGlobalBuffers + 2,
|
||||
imageBindingBase: 0,
|
||||
scalarRegisterBufferIndex: totalGlobalBuffers) ||
|
||||
!Gen5SpirvTranslator.TryCompileVertexShader(
|
||||
!GuestGpu.Current.TryCompileVertexShader(
|
||||
exportState,
|
||||
exportEvaluation,
|
||||
out var vertexShader,
|
||||
@@ -3630,23 +3637,20 @@ public static class AgcExports
|
||||
return false;
|
||||
}
|
||||
|
||||
compiled = (vertexShader.Spirv, pixelShader.Spirv);
|
||||
DumpSpirv(
|
||||
compiled = (vertexShader!, pixelShader!);
|
||||
DumpCompiledShader(
|
||||
"vs",
|
||||
exportShaderAddress,
|
||||
exportStateFingerprint,
|
||||
compiled.Vertex,
|
||||
exportState.Program);
|
||||
DumpSpirv(
|
||||
DumpCompiledShader(
|
||||
"ps",
|
||||
pixelShaderAddress,
|
||||
pixelStateFingerprint,
|
||||
compiled.Pixel,
|
||||
pixelState.Program);
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
_graphicsSpirvCache.TryAdd(shaderKey, compiled);
|
||||
}
|
||||
_graphicsShaderCache.TryAdd(shaderKey, compiled);
|
||||
}
|
||||
|
||||
var imageBindings = pixelEvaluation.ImageBindings
|
||||
@@ -3683,6 +3687,17 @@ public static class AgcExports
|
||||
IReadOnlyList<Gen5VertexInputBinding> vertexInputs =
|
||||
exportEvaluation.VertexInputs ?? [];
|
||||
state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType);
|
||||
var guestTargets = new GuestRenderTarget[renderTargets.Length];
|
||||
for (var index = 0; index < renderTargets.Length; index++)
|
||||
{
|
||||
guestTargets[index] = new GuestRenderTarget(
|
||||
renderTargets[index].Address,
|
||||
renderTargets[index].Width,
|
||||
renderTargets[index].Height,
|
||||
renderTargets[index].Format,
|
||||
renderTargets[index].NumberType);
|
||||
}
|
||||
|
||||
draw = new TranslatedGuestDraw(
|
||||
exportShaderAddress,
|
||||
pixelShaderAddress,
|
||||
@@ -3692,11 +3707,12 @@ public static class AgcExports
|
||||
attributeCount,
|
||||
vertexCount,
|
||||
state.InstanceCount,
|
||||
indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null,
|
||||
indexed ? CreateGuestIndexBuffer(ctx, state, vertexCount) : null,
|
||||
textures,
|
||||
globalMemoryBindings,
|
||||
vertexInputs,
|
||||
renderTargets,
|
||||
guestTargets,
|
||||
ApplyTransparentPremultipliedFillClear(
|
||||
CreateRenderState(state.CxRegisters, renderTargets, pixelState),
|
||||
textures,
|
||||
@@ -3717,8 +3733,8 @@ public static class AgcExports
|
||||
/// 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,
|
||||
private static GuestRenderState ApplyTransparentPremultipliedFillClear(
|
||||
GuestRenderState renderState,
|
||||
IReadOnlyList<TranslatedImageBinding> textures,
|
||||
IReadOnlyList<Gen5VertexInputBinding> vertexInputs,
|
||||
IReadOnlyList<uint> pixelUserData)
|
||||
@@ -3748,7 +3764,7 @@ public static class AgcExports
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsTransparentPremultipliedFillBlend(VulkanGuestBlendState blend) =>
|
||||
private static bool IsTransparentPremultipliedFillBlend(GuestBlendState blend) =>
|
||||
blend is
|
||||
{
|
||||
Enable: true,
|
||||
@@ -3757,7 +3773,7 @@ public static class AgcExports
|
||||
ColorFunc: 0,
|
||||
};
|
||||
|
||||
private static VulkanGuestIndexBuffer? CreateVulkanIndexBuffer(
|
||||
private static GuestIndexBuffer? CreateGuestIndexBuffer(
|
||||
CpuContext ctx,
|
||||
SubmittedDcbState state,
|
||||
uint indexCount)
|
||||
@@ -3775,7 +3791,7 @@ public static class AgcExports
|
||||
var address = state.IndexBufferAddress + byteOffset;
|
||||
return (ctx.Memory.TryRead(address, data) ||
|
||||
KernelMemoryCompatExports.TryReadTrackedLibcHeap(address, data))
|
||||
? new VulkanGuestIndexBuffer(data, is32Bit)
|
||||
? new GuestIndexBuffer(data, is32Bit)
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -3943,19 +3959,19 @@ public static class AgcExports
|
||||
return targets;
|
||||
}
|
||||
|
||||
private static VulkanGuestRenderState CreateRenderState(
|
||||
private static GuestRenderState CreateRenderState(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
IReadOnlyList<RenderTargetDescriptor> targets,
|
||||
Gen5ShaderState pixelState)
|
||||
{
|
||||
if (targets.Count == 0)
|
||||
{
|
||||
return VulkanGuestRenderState.Default;
|
||||
return GuestRenderState.Default;
|
||||
}
|
||||
|
||||
var target = targets[0];
|
||||
var scissor = DecodeScissor(registers, target.Width, target.Height);
|
||||
return new VulkanGuestRenderState(
|
||||
return new GuestRenderState(
|
||||
targets.Select(target =>
|
||||
{
|
||||
var blend = DecodeBlendState(registers, target.Slot);
|
||||
@@ -3968,7 +3984,7 @@ public static class AgcExports
|
||||
DecodeViewport(registers, target.Width, target.Height, scissor));
|
||||
}
|
||||
|
||||
private static VulkanGuestBlendState DecodeBlendState(
|
||||
private static GuestBlendState DecodeBlendState(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
uint slot)
|
||||
{
|
||||
@@ -3979,7 +3995,7 @@ public static class AgcExports
|
||||
}
|
||||
|
||||
registers.TryGetValue(CbBlend0Control + slot, out var control);
|
||||
return new VulkanGuestBlendState(
|
||||
return new GuestBlendState(
|
||||
((control >> 30) & 1u) != 0,
|
||||
control & 0x1Fu,
|
||||
(control >> 8) & 0x1Fu,
|
||||
@@ -3991,14 +4007,14 @@ public static class AgcExports
|
||||
writeMask);
|
||||
}
|
||||
|
||||
private static VulkanGuestRect? DecodeScissor(
|
||||
private static GuestRect? DecodeScissor(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
uint targetWidth,
|
||||
uint targetHeight)
|
||||
{
|
||||
if (targetWidth == 0 || targetHeight == 0)
|
||||
{
|
||||
return new VulkanGuestRect(0, 0, 0, 0);
|
||||
return new GuestRect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
var left = 0;
|
||||
@@ -4063,22 +4079,22 @@ public static class AgcExports
|
||||
return null;
|
||||
}
|
||||
|
||||
return new VulkanGuestRect(
|
||||
return new GuestRect(
|
||||
left,
|
||||
top,
|
||||
checked((uint)(right - left)),
|
||||
checked((uint)(bottom - top)));
|
||||
}
|
||||
|
||||
private static VulkanGuestViewport? DecodeViewport(
|
||||
private static GuestViewport? DecodeViewport(
|
||||
IReadOnlyDictionary<uint, uint> registers,
|
||||
uint targetWidth,
|
||||
uint targetHeight,
|
||||
VulkanGuestRect? scissor)
|
||||
GuestRect? scissor)
|
||||
{
|
||||
if (targetWidth == 0 || targetHeight == 0)
|
||||
{
|
||||
return new VulkanGuestViewport(0, 0, 0, 0, 0, 1);
|
||||
return new GuestViewport(0, 0, 0, 0, 0, 1);
|
||||
}
|
||||
|
||||
var minDepth = 0f;
|
||||
@@ -4104,7 +4120,7 @@ public static class AgcExports
|
||||
xScale > 0f &&
|
||||
yScale != 0f)
|
||||
{
|
||||
return new VulkanGuestViewport(
|
||||
return new GuestViewport(
|
||||
xOffset - xScale,
|
||||
yOffset - yScale,
|
||||
xScale * 2f,
|
||||
@@ -4117,10 +4133,10 @@ public static class AgcExports
|
||||
{
|
||||
return minDepth == 0f && maxDepth == 1f
|
||||
? null
|
||||
: new VulkanGuestViewport(0, 0, targetWidth, targetHeight, minDepth, maxDepth);
|
||||
: new GuestViewport(0, 0, targetWidth, targetHeight, minDepth, maxDepth);
|
||||
}
|
||||
|
||||
return new VulkanGuestViewport(
|
||||
return new GuestViewport(
|
||||
rect.X,
|
||||
rect.Y,
|
||||
rect.Width,
|
||||
@@ -4297,7 +4313,7 @@ public static class AgcExports
|
||||
var blend = draw.RenderState.Blend;
|
||||
TraceAgcShader(
|
||||
$"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " +
|
||||
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.Length} " +
|
||||
$"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelShader.Payload.Length} " +
|
||||
$"primitive=0x{draw.PrimitiveType:X} " +
|
||||
$"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " +
|
||||
$"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " +
|
||||
@@ -4307,16 +4323,16 @@ public static class AgcExports
|
||||
$"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<VulkanGuestDrawTexture> CreateVulkanGuestDrawTextures(
|
||||
private static IReadOnlyList<GuestDrawTexture> CreateGuestDrawTextures(
|
||||
CpuContext ctx,
|
||||
IReadOnlyList<TranslatedImageBinding> bindings,
|
||||
out int fallbackTextureCount)
|
||||
{
|
||||
var textures = new List<VulkanGuestDrawTexture>(bindings.Count);
|
||||
var textures = new List<GuestDrawTexture>(bindings.Count);
|
||||
fallbackTextureCount = 0;
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
if (TryCreateVulkanGuestDrawTexture(
|
||||
if (TryCreateGuestDrawTexture(
|
||||
ctx,
|
||||
binding.Descriptor,
|
||||
binding.IsStorage,
|
||||
@@ -4335,13 +4351,13 @@ public static class AgcExports
|
||||
return textures;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<VulkanGuestMemoryBuffer> CreateVulkanGuestMemoryBuffers(
|
||||
private static IReadOnlyList<GuestMemoryBuffer> CreateGuestMemoryBuffers(
|
||||
IReadOnlyList<Gen5GlobalMemoryBinding> bindings)
|
||||
{
|
||||
var buffers = new VulkanGuestMemoryBuffer[bindings.Count];
|
||||
var buffers = new GuestMemoryBuffer[bindings.Count];
|
||||
for (var index = 0; index < bindings.Count; index++)
|
||||
{
|
||||
buffers[index] = new VulkanGuestMemoryBuffer(
|
||||
buffers[index] = new GuestMemoryBuffer(
|
||||
bindings[index].BaseAddress,
|
||||
bindings[index].Data);
|
||||
}
|
||||
@@ -4349,14 +4365,14 @@ public static class AgcExports
|
||||
return buffers;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<VulkanGuestVertexBuffer> CreateVulkanGuestVertexBuffers(
|
||||
private static IReadOnlyList<GuestVertexBuffer> CreateGuestVertexBuffers(
|
||||
IReadOnlyList<Gen5VertexInputBinding> bindings)
|
||||
{
|
||||
var buffers = new VulkanGuestVertexBuffer[bindings.Count];
|
||||
var buffers = new GuestVertexBuffer[bindings.Count];
|
||||
for (var index = 0; index < bindings.Count; index++)
|
||||
{
|
||||
var binding = bindings[index];
|
||||
buffers[index] = new VulkanGuestVertexBuffer(
|
||||
buffers[index] = new GuestVertexBuffer(
|
||||
binding.Location,
|
||||
binding.ComponentCount,
|
||||
binding.DataFormat,
|
||||
@@ -4370,13 +4386,13 @@ public static class AgcExports
|
||||
return buffers;
|
||||
}
|
||||
|
||||
private static bool TryCreateVulkanGuestDrawTexture(
|
||||
private static bool TryCreateGuestDrawTexture(
|
||||
CpuContext ctx,
|
||||
TextureDescriptor descriptor,
|
||||
bool isStorage,
|
||||
uint mipLevel,
|
||||
IReadOnlyList<uint> samplerDescriptor,
|
||||
out VulkanGuestDrawTexture texture)
|
||||
out GuestDrawTexture texture)
|
||||
{
|
||||
texture = default!;
|
||||
if (descriptor.Type != Gen5TextureType2D ||
|
||||
@@ -4412,12 +4428,12 @@ public static class AgcExports
|
||||
|
||||
if (!isStorage &&
|
||||
descriptor.Address != 0 &&
|
||||
VulkanVideoPresenter.IsGpuGuestImageAvailable(
|
||||
GuestGpu.Current.IsGpuGuestImageAvailable(
|
||||
descriptor.Address,
|
||||
descriptor.Format,
|
||||
descriptor.NumberType))
|
||||
{
|
||||
texture = new VulkanGuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
@@ -4431,7 +4447,7 @@ public static class AgcExports
|
||||
Pitch: sourceWidth,
|
||||
TileMode: descriptor.TileMode,
|
||||
DstSelect: descriptor.DstSelect,
|
||||
Sampler: ToVulkanSampler(samplerDescriptor));
|
||||
Sampler: ToGuestSampler(samplerDescriptor));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4451,7 +4467,7 @@ public static class AgcExports
|
||||
}
|
||||
}
|
||||
|
||||
texture = new VulkanGuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
@@ -4465,7 +4481,7 @@ public static class AgcExports
|
||||
Pitch: sourceWidth,
|
||||
TileMode: descriptor.TileMode,
|
||||
DstSelect: descriptor.DstSelect,
|
||||
Sampler: ToVulkanSampler(samplerDescriptor));
|
||||
Sampler: ToGuestSampler(samplerDescriptor));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4506,7 +4522,7 @@ public static class AgcExports
|
||||
DumpTextureSourceIfRequested(descriptor, sourceWidth, source);
|
||||
|
||||
var rgba = source;
|
||||
texture = new VulkanGuestDrawTexture(
|
||||
texture = new GuestDrawTexture(
|
||||
descriptor.Address,
|
||||
descriptor.Width,
|
||||
descriptor.Height,
|
||||
@@ -4520,7 +4536,7 @@ public static class AgcExports
|
||||
Pitch: sourceWidth,
|
||||
TileMode: descriptor.TileMode,
|
||||
DstSelect: descriptor.DstSelect,
|
||||
Sampler: ToVulkanSampler(samplerDescriptor));
|
||||
Sampler: ToGuestSampler(samplerDescriptor));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4553,8 +4569,8 @@ public static class AgcExports
|
||||
|
||||
private static void TraceGrassDrawVertices(
|
||||
TranslatedGuestDraw draw,
|
||||
IReadOnlyList<VulkanGuestDrawTexture> textures,
|
||||
IReadOnlyList<VulkanGuestVertexBuffer> vertexBuffers)
|
||||
IReadOnlyList<GuestDrawTexture> textures,
|
||||
IReadOnlyList<GuestVertexBuffer> vertexBuffers)
|
||||
{
|
||||
if (_grassTraceCount >= 6 ||
|
||||
!textures.Any(texture => texture.Width == 288 && texture.Height == 160) ||
|
||||
@@ -4593,7 +4609,7 @@ public static class AgcExports
|
||||
|
||||
private static void TraceRectListVertices(
|
||||
TranslatedGuestDraw draw,
|
||||
IReadOnlyList<VulkanGuestVertexBuffer> vertexBuffers)
|
||||
IReadOnlyList<GuestVertexBuffer> vertexBuffers)
|
||||
{
|
||||
if (draw.PrimitiveType != 0x11 ||
|
||||
draw.IndexBuffer is not null ||
|
||||
@@ -4676,7 +4692,7 @@ public static class AgcExports
|
||||
}
|
||||
}
|
||||
|
||||
private static VulkanGuestDrawTexture CreateFallbackGuestDrawTexture(
|
||||
private static GuestDrawTexture CreateFallbackGuestDrawTexture(
|
||||
bool isStorage,
|
||||
uint format,
|
||||
uint numberType)
|
||||
@@ -4724,9 +4740,9 @@ public static class AgcExports
|
||||
$"size={descriptor.Width}x{descriptor.Height} bytes={source.Length} hash=0x{hash:X16}");
|
||||
}
|
||||
|
||||
private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList<uint> descriptor) =>
|
||||
private static GuestSampler ToGuestSampler(IReadOnlyList<uint> descriptor) =>
|
||||
descriptor.Count >= 4
|
||||
? new VulkanGuestSampler(
|
||||
? new GuestSampler(
|
||||
descriptor[0],
|
||||
descriptor[1],
|
||||
descriptor[2],
|
||||
@@ -4924,47 +4940,39 @@ public static class AgcExports
|
||||
localSizeX,
|
||||
localSizeY,
|
||||
localSizeZ);
|
||||
byte[] computeSpirv;
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
_computeSpirvCache.TryGetValue(shaderKey, out computeSpirv!);
|
||||
}
|
||||
_computeShaderCache.TryGetValue(shaderKey, out var computeShader);
|
||||
|
||||
if (computeSpirv is null &&
|
||||
Gen5SpirvTranslator.TryCompileComputeShader(
|
||||
if (computeShader is null &&
|
||||
GuestGpu.Current.TryCompileComputeShader(
|
||||
shaderState,
|
||||
evaluation,
|
||||
localSizeX,
|
||||
localSizeY,
|
||||
localSizeZ,
|
||||
out var compiledCompute,
|
||||
out computeShader,
|
||||
out computeError))
|
||||
{
|
||||
computeSpirv = compiledCompute.Spirv;
|
||||
DumpSpirv(
|
||||
DumpCompiledShader(
|
||||
"cs",
|
||||
shaderAddress,
|
||||
shaderKey.Item2,
|
||||
computeSpirv,
|
||||
computeShader!,
|
||||
shaderState.Program);
|
||||
}
|
||||
|
||||
if (computeSpirv is not null)
|
||||
if (computeShader is not null)
|
||||
{
|
||||
lock (_submitTraceGate)
|
||||
{
|
||||
_computeSpirvCache.TryAdd(shaderKey, computeSpirv);
|
||||
}
|
||||
_computeShaderCache.TryAdd(shaderKey, computeShader);
|
||||
|
||||
var textures = CreateVulkanGuestDrawTextures(
|
||||
var textures = CreateGuestDrawTextures(
|
||||
ctx,
|
||||
translatedBindings,
|
||||
out _);
|
||||
var globalMemoryBuffers =
|
||||
CreateVulkanGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
|
||||
VulkanVideoPresenter.SubmitComputeDispatch(
|
||||
CreateGuestMemoryBuffers(evaluation.GlobalMemoryBindings);
|
||||
GuestGpu.Current.SubmitComputeDispatch(
|
||||
shaderAddress,
|
||||
computeSpirv,
|
||||
computeShader,
|
||||
textures,
|
||||
globalMemoryBuffers,
|
||||
dispatch.GroupCountX,
|
||||
@@ -5135,7 +5143,7 @@ public static class AgcExports
|
||||
}
|
||||
}
|
||||
else if (source is { } cachedSourceTexture &&
|
||||
VulkanVideoPresenter.TrySubmitGuestImageBlit(
|
||||
GuestGpu.Current.TrySubmitGuestImageBlit(
|
||||
cachedSourceTexture.Address,
|
||||
cachedSourceTexture.Width,
|
||||
cachedSourceTexture.Height,
|
||||
@@ -5489,7 +5497,7 @@ public static class AgcExports
|
||||
$"pcs={string.Join(',', binding.InstructionPcs.Select(pc => $"0x{pc:X}"))}");
|
||||
}
|
||||
|
||||
if (Gen5SpirvTranslator.TryCompilePixelShader(
|
||||
if (GuestGpu.Current.TryCompilePixelShader(
|
||||
pixelState,
|
||||
evaluation,
|
||||
[new(0, 0, Gen5PixelOutputKind.Float)],
|
||||
@@ -5498,7 +5506,7 @@ public static class AgcExports
|
||||
{
|
||||
TraceAgcShader(
|
||||
$"agc.shader_spirv ps=0x{pixelShaderAddress:X16} " +
|
||||
$"bytes={compiledPixel.Spirv.Length} bindings={evaluation.ImageBindings.Count} " +
|
||||
$"bytes={compiledPixel!.Payload.Length} bindings={evaluation.ImageBindings.Count} " +
|
||||
$"global_buffers={evaluation.GlobalMemoryBindings.Count}");
|
||||
}
|
||||
else
|
||||
@@ -6388,14 +6396,14 @@ public static class AgcExports
|
||||
$"type={descriptor.Type} levels={descriptor.BaseLevel}-{descriptor.LastLevel} " +
|
||||
$"pitch={descriptor.Pitch} dst=0x{descriptor.DstSelect:X3}";
|
||||
|
||||
private static void DumpSpirv(
|
||||
private static void DumpCompiledShader(
|
||||
string stage,
|
||||
ulong shaderAddress,
|
||||
ulong stateFingerprint,
|
||||
byte[] spirv,
|
||||
IGuestCompiledShader shader,
|
||||
Gen5ShaderProgram program)
|
||||
{
|
||||
if (spirv.Length == 0 ||
|
||||
if (shader.Payload.Length == 0 ||
|
||||
!string.Equals(
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_DUMP_SPIRV"),
|
||||
"1",
|
||||
@@ -6407,7 +6415,9 @@ public static class AgcExports
|
||||
var directory = Path.Combine(AppContext.BaseDirectory, "shader-dumps");
|
||||
Directory.CreateDirectory(directory);
|
||||
var name = $"{shaderAddress:X16}-{stateFingerprint:X16}.{stage}";
|
||||
File.WriteAllBytes(Path.Combine(directory, $"{name}.spv"), spirv);
|
||||
File.WriteAllBytes(
|
||||
Path.Combine(directory, $"{name}.{shader.PayloadFileExtension}"),
|
||||
shader.Payload);
|
||||
|
||||
var lines = new List<string>(program.Instructions.Count + 2)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user