From 5449fea881d1637eed58d40b4e90b14e01937b86 Mon Sep 17 00:00:00 2001 From: kuba Date: Wed, 30 Sep 2026 15:26:13 +0200 Subject: [PATCH] [Gpu] Declare every parameter the pixel program reads in the paired vertex program A vertex program declares outputs for the parameters it exports plus as many locations as the pixel program has inputs. SPI_PS_INPUT_CNTL can map a pixel input to any parameter number, though, so a pixel program with two inputs may read parameters 4 and 5. Metal requires every fragment input to be written by the vertex stage, so the pipeline failed to link ("fragment inputs user(locn4), user(locn5) ... not written by vertex shader"), the command stream slice failed and Silent Hill stopped rendering at a scene change. The required output count now covers the highest location the pixel program reads, resolved through the same input mapping its translator uses; missing exports are zero-filled as before. The pipeline creation error also names the vertex and pixel programs. (cherry picked from commit 80ec9fb53cfae922e458e2dd43d4b1010dbd2701) --- .../Gpu/Pipelines/ShaderPipelineCache.cs | 29 +++++++++++++++++++ .../VulkanVideoPresenter.Pipelines.cs | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/SharpEmu.Libs/Gpu/Pipelines/ShaderPipelineCache.cs b/src/SharpEmu.Libs/Gpu/Pipelines/ShaderPipelineCache.cs index dd1c64d..0ee85c1 100644 --- a/src/SharpEmu.Libs/Gpu/Pipelines/ShaderPipelineCache.cs +++ b/src/SharpEmu.Libs/Gpu/Pipelines/ShaderPipelineCache.cs @@ -111,6 +111,9 @@ internal sealed partial class ShaderPipelineCache : IShaderPipelineProvider pixelOutputs = ResolveBoundTargets(context, out var outputModes, out var outputMappings); pixelInfo = PixelStageInputResolver.Resolve(_context, pixelSource.Registered, shaderInterface, outputModes, outputMappings, inputCount); + // SPI_PS_INPUT_CNTL can map an input to any parameter export, beyond the input count; + // the vertex program must declare every location the pixel program reads. + attributeCount = Math.Max(attributeCount, ReadVertexOutputCount(pixelProgram, pixelInfo)); } ShaderProgram vertexProgram; @@ -204,6 +207,32 @@ internal sealed partial class ShaderPipelineCache : IShaderPipelineProvider shaderInterface.VertexOutputControl, clipSpace); } + // One past the highest parameter location the pixel program reads, resolved as its translator does. + private static uint ReadVertexOutputCount(Gen5ShaderProgram pixelProgram, PixelInputInfo info) + { + var attributes = pixelProgram.Instructions + .Select(static instruction => instruction.Control) + .OfType() + .Select(static control => control.Attribute) + .Distinct() + .Order() + .ToArray(); + if (attributes.Length == 0) + { + return 0; + } + + var controls = new uint[32]; + for (var index = 0u; index < (uint)controls.Length; index++) + { + controls[index] = index < info.InputCount && index < (uint)info.InterpolatorSettings.Length + ? info.InterpolatorSettings[index] + : index; + } + + return Gen5PixelInputMapping.ResolveLocations(controls, attributes).Max() + 1; + } + private static uint InterpolatedAttributeCount(Gen5ShaderProgram program) { var maxAttribute = -1; diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs index 3c69911..bf72db4 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Pipelines.cs @@ -769,7 +769,8 @@ internal static unsafe partial class VulkanVideoPresenter Layout = layout, }; var graphicsStart = Stopwatch.GetTimestamp(); - Check(_vk.CreateGraphicsPipelines(_device, _pipelineCache, 1, &pipelineInfo, null, out var pipeline), "vkCreateGraphicsPipelines(rendering)"); + Check(_vk.CreateGraphicsPipelines(_device, _pipelineCache, 1, &pipelineInfo, null, out var pipeline), + $"vkCreateGraphicsPipelines(rendering vs=0x{description.VertexStage.Hash:X16} ps=0x{description.PixelStage?.Hash ?? 0:X16})"); ReportPipelineCreation( (long)Stopwatch.GetElapsedTime(graphicsStart).TotalMilliseconds, "graphics",