diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index b8cce7f9..ef37f955 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -68,13 +68,35 @@ public static class AgcExports private const uint ComputeNumThreadZ = 0x209; private const uint SpiPsInputCntl0 = 0x191; private const uint VgtPrimitiveType = 0x242; + private const uint PaScScreenScissorTl = 0x0C; + private const uint PaScScreenScissorBr = 0x0D; private const uint CbTargetMask = 0x8E; + private const uint PaScWindowOffset = 0x80; + private const uint PaScWindowScissorTl = 0x81; + private const uint PaScWindowScissorBr = 0x82; + private const uint PaScGenericScissorTl = 0x90; + private const uint PaScGenericScissorBr = 0x91; + private const uint PaScVportScissor0Tl = 0x94; + private const uint PaScVportScissor0Br = 0x95; + private const uint PaClVportXScale = 0x10F; + private const uint PaClVportXOffset = 0x110; + private const uint PaClVportYScale = 0x111; + private const uint PaClVportYOffset = 0x112; + private const uint PaScVportZMin0 = 0xB4; + private const uint PaScVportZMax0 = 0xB5; + private const uint CbBlendRed = 0x105; + private const uint CbBlendGreen = 0x106; + private const uint CbBlendBlue = 0x107; + private const uint CbBlendAlpha = 0x108; + private const uint CbColorControl = 0x202; private const uint CbColor0Base = 0x318; private const uint CbColorRegisterStride = 15; private const uint CbColor0Info = 0x31C; private const uint CbColor0BaseExt = 0x390; private const uint CbColor0Attrib2 = 0x3B0; private const uint CbColor0Attrib3 = 0x3B8; + private const uint CbBlend0Control = 0x1E0; + private const uint PaScModeCntl0 = 0x292; private const int ColorTargetCount = 8; private const uint PsTextureUserDataRegister = 0xC; private const uint VsUserDataRegister = 0x4C; @@ -151,9 +173,25 @@ public static class AgcExports private static readonly RegisterDefaultGroup[] PrimaryRegisterDefaults = [ + new(0, 0, 0xE24F806D, [new(CbColorControl, 0x00CC0010)]), new(0, 3, 0x0BC65DA4, [new(0x08F, 0)]), new(0, 4, 0x9E5AD592, [new(0x08E, 0)]), new(0, 12, 0x6DE4C312, [new(0x203, 0)]), + new(0, 28, 0x1EB8D73A, [new(PaScModeCntl0, 0x00000002)]), + new(0, 31, 0xA20EFC70, [new(PaScWindowOffset, 0)]), + new(0, 58, 0x43FBD769, + [ + new(CbBlendRed, 0), + new(CbBlendBlue, 0), + new(CbBlendGreen, 0), + new(CbBlendAlpha, 0), + ]), + new(0, 59, 0xEF550356, [new(CbBlend0Control, 0x20010001)]), + new(0, 67, 0x918106BB, + [ + new(PaScGenericScissorTl, 0x80000000), + new(PaScGenericScissorBr, 0x40004000), + ]), new(0, 72, 0x38E92C91, [ new(0x318, 0), @@ -188,6 +226,11 @@ public static class AgcExports new(0x0B4, 0), new(0x0B5, 0), ]), + new(0, 77, 0x078D7060, + [ + new(PaScWindowScissorTl, 0x80000000), + new(PaScWindowScissorBr, 0x40004000), + ]), new(1, 13, 0xC918DF3E, [new(0x20C, 0), new(0x20D, 0)]), new(1, 14, 0xC9751C9C, [new(0x0C8, 0), new(0x0C9, 0)]), new(1, 18, 0xC9E01B31, [new(0x008, 0), new(0x009, 0)]), @@ -231,7 +274,9 @@ public static class AgcExports uint TileMode, uint Type, uint BaseLevel, - uint LastLevel) + uint LastLevel, + uint Pitch, + uint DstSelect) { public uint MipLevels { @@ -274,12 +319,15 @@ public static class AgcExports VulkanGuestIndexBuffer? IndexBuffer, IReadOnlyList Textures, IReadOnlyList GlobalMemoryBindings, - IReadOnlyList RenderTargets); + IReadOnlyList VertexInputs, + IReadOnlyList RenderTargets, + VulkanGuestRenderState RenderState); private sealed record TranslatedImageBinding( TextureDescriptor Descriptor, bool IsStorage, - uint MipLevel); + uint MipLevel, + IReadOnlyList SamplerDescriptor); private readonly record struct RenderTargetWriter( ulong Sequence, @@ -2770,11 +2818,13 @@ public static class AgcExports out _); var globalMemoryBuffers = CreateVulkanGuestMemoryBuffers(translatedDraw.GlobalMemoryBindings); - VulkanVideoPresenter.SubmitOffscreenTranslatedDraw( - translatedDraw.PixelSpirv, - textures, - globalMemoryBuffers, - translatedDraw.AttributeCount, + var vertexBuffers = + CreateVulkanGuestVertexBuffers(translatedDraw.VertexInputs); + VulkanVideoPresenter.SubmitOffscreenTranslatedDraw( + translatedDraw.PixelSpirv, + textures, + globalMemoryBuffers, + translatedDraw.AttributeCount, new VulkanGuestRenderTarget( firstTarget.Address, firstTarget.Width, @@ -2785,7 +2835,9 @@ public static class AgcExports translatedDraw.VertexCount, translatedDraw.InstanceCount, translatedDraw.PrimitiveType, - translatedDraw.IndexBuffer); + translatedDraw.IndexBuffer, + vertexBuffers, + translatedDraw.RenderState); } else { @@ -2884,7 +2936,8 @@ public static class AgcExports ctx, exportState, out var exportEvaluation, - out error) || + out error, + resolveVertexInputs: true) || !Gen5ShaderTranslator.TryCreateState( ctx, pixelShaderAddress, @@ -2984,12 +3037,15 @@ public static class AgcExports new TranslatedImageBinding( texture, Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode), - binding.MipLevel ?? 0)); + binding.MipLevel ?? 0, + binding.SamplerDescriptor)); } var globalMemoryBindings = pixelEvaluation.GlobalMemoryBindings .Concat(exportEvaluation.GlobalMemoryBindings) .ToArray(); + IReadOnlyList vertexInputs = + exportEvaluation.VertexInputs ?? []; state.UcRegisters.TryGetValue(VgtPrimitiveType, out var primitiveType); draw = new TranslatedGuestDraw( exportShaderAddress, @@ -3003,7 +3059,9 @@ public static class AgcExports indexed ? CreateVulkanIndexBuffer(ctx, state, vertexCount) : null, textures, globalMemoryBindings, - renderTargets); + vertexInputs, + renderTargets, + CreateRenderState(state.CxRegisters, renderTargets.FirstOrDefault())); return true; } @@ -3115,6 +3173,246 @@ public static class AgcExports return targets; } + private static VulkanGuestRenderState CreateRenderState( + IReadOnlyDictionary registers, + RenderTargetDescriptor target) + { + var scissor = DecodeScissor(registers, target.Width, target.Height); + return new VulkanGuestRenderState( + DecodeBlendState(registers, target.Slot), + scissor, + DecodeViewport(registers, target.Width, target.Height, scissor)); + } + + private static VulkanGuestBlendState DecodeBlendState( + IReadOnlyDictionary registers, + uint slot) + { + var writeMask = 0xFu; + if (registers.TryGetValue(CbTargetMask, out var targetMask)) + { + writeMask = (targetMask >> checked((int)(slot * 4))) & 0xFu; + } + + registers.TryGetValue(CbBlend0Control + slot, out var control); + return new VulkanGuestBlendState( + ((control >> 30) & 1u) != 0, + control & 0x1Fu, + (control >> 8) & 0x1Fu, + (control >> 5) & 0x7u, + (control >> 16) & 0x1Fu, + (control >> 24) & 0x1Fu, + (control >> 21) & 0x7u, + ((control >> 29) & 1u) != 0, + writeMask == 0 ? 0xFu : writeMask); + } + + private static VulkanGuestRect? DecodeScissor( + IReadOnlyDictionary registers, + uint targetWidth, + uint targetHeight) + { + if (targetWidth == 0 || targetHeight == 0) + { + return new VulkanGuestRect(0, 0, 0, 0); + } + + var left = 0; + var top = 0; + var right = checked((int)Math.Min(targetWidth, int.MaxValue)); + var bottom = checked((int)Math.Min(targetHeight, int.MaxValue)); + + var windowOffsetX = 0; + var windowOffsetY = 0; + var enableWindowOffset = true; + if (registers.TryGetValue(PaScWindowScissorTl, out var windowScissorTl)) + { + enableWindowOffset = (windowScissorTl & 0x80000000u) == 0; + } + + if (enableWindowOffset && + registers.TryGetValue(PaScWindowOffset, out var windowOffset)) + { + windowOffsetX = (short)(windowOffset & 0xFFFFu); + windowOffsetY = (short)(windowOffset >> 16); + } + + IntersectScissorPair(registers, PaScScreenScissorTl, PaScScreenScissorBr, ref left, ref top, ref right, ref bottom); + IntersectScissorPair( + registers, + PaScWindowScissorTl, + PaScWindowScissorBr, + ref left, + ref top, + ref right, + ref bottom, + windowOffsetX, + windowOffsetY); + IntersectScissorPair( + registers, + PaScGenericScissorTl, + PaScGenericScissorBr, + ref left, + ref top, + ref right, + ref bottom, + windowOffsetX, + windowOffsetY); + var vportScissorEnabled = + !registers.TryGetValue(PaScModeCntl0, out var modeControl) || + ((modeControl >> 1) & 1u) != 0; + if (vportScissorEnabled) + { + IntersectScissorPair(registers, PaScVportScissor0Tl, PaScVportScissor0Br, ref left, ref top, ref right, ref bottom); + } + + left = Math.Clamp(left, 0, checked((int)targetWidth)); + top = Math.Clamp(top, 0, checked((int)targetHeight)); + right = Math.Clamp(right, left, checked((int)targetWidth)); + bottom = Math.Clamp(bottom, top, checked((int)targetHeight)); + + if (left == 0 && + top == 0 && + right == (int)targetWidth && + bottom == (int)targetHeight) + { + return null; + } + + return new VulkanGuestRect( + left, + top, + checked((uint)(right - left)), + checked((uint)(bottom - top))); + } + + private static VulkanGuestViewport? DecodeViewport( + IReadOnlyDictionary registers, + uint targetWidth, + uint targetHeight, + VulkanGuestRect? scissor) + { + if (targetWidth == 0 || targetHeight == 0) + { + return new VulkanGuestViewport(0, 0, 0, 0, 0, 1); + } + + var minDepth = 0f; + var maxDepth = 1f; + if (registers.TryGetValue(PaScVportZMin0, out var zMinBits) && + registers.TryGetValue(PaScVportZMax0, out var zMaxBits)) + { + var decodedMin = BitConverter.UInt32BitsToSingle(zMinBits); + var decodedMax = BitConverter.UInt32BitsToSingle(zMaxBits); + if (float.IsFinite(decodedMin) && + float.IsFinite(decodedMax) && + decodedMax > decodedMin) + { + minDepth = decodedMin; + maxDepth = decodedMax; + } + } + + if (TryDecodeFiniteFloat(registers, PaClVportXScale, out var xScale) && + TryDecodeFiniteFloat(registers, PaClVportXOffset, out var xOffset) && + TryDecodeFiniteFloat(registers, PaClVportYScale, out var yScale) && + TryDecodeFiniteFloat(registers, PaClVportYOffset, out var yOffset) && + xScale > 0f && + yScale != 0f) + { + return new VulkanGuestViewport( + xOffset - xScale, + yOffset - yScale, + xScale * 2f, + yScale * 2f, + minDepth, + maxDepth); + } + + if (scissor is not { } rect) + { + return minDepth == 0f && maxDepth == 1f + ? null + : new VulkanGuestViewport(0, 0, targetWidth, targetHeight, minDepth, maxDepth); + } + + return new VulkanGuestViewport( + rect.X, + rect.Y, + rect.Width, + rect.Height, + minDepth, + maxDepth); + } + + private static bool TryDecodeFiniteFloat( + IReadOnlyDictionary registers, + uint register, + out float value) + { + value = 0; + if (!registers.TryGetValue(register, out var bits)) + { + return false; + } + + value = BitConverter.UInt32BitsToSingle(bits); + return float.IsFinite(value); + } + + private static void IntersectScissorPair( + IReadOnlyDictionary registers, + uint tlRegister, + uint brRegister, + ref int left, + ref int top, + ref int right, + ref int bottom, + int offsetX = 0, + int offsetY = 0) + { + if (!TryDecodeScissorPair(registers, tlRegister, brRegister, out var pairLeft, out var pairTop, out var pairRight, out var pairBottom)) + { + return; + } + + pairLeft += offsetX; + pairTop += offsetY; + pairRight += offsetX; + pairBottom += offsetY; + + left = Math.Max(left, pairLeft); + top = Math.Max(top, pairTop); + right = Math.Min(right, pairRight); + bottom = Math.Min(bottom, pairBottom); + } + + private static bool TryDecodeScissorPair( + IReadOnlyDictionary registers, + uint tlRegister, + uint brRegister, + out int left, + out int top, + out int right, + out int bottom) + { + left = 0; + top = 0; + right = 0; + bottom = 0; + if (!registers.TryGetValue(tlRegister, out var tl) || + !registers.TryGetValue(brRegister, out var br)) + { + return false; + } + + left = (int)(tl & 0x7FFFu); + top = (int)((tl >> 16) & 0x7FFFu); + right = (int)(br & 0x7FFFu); + bottom = (int)((br >> 16) & 0x7FFFu); + return true; + } + private static void TraceTranslatedGuestDraw( CpuContext ctx, SubmittedGpuState gpuState, @@ -3173,13 +3471,31 @@ public static class AgcExports ? $"{(indexBuffer.Is32Bit ? 32 : 16)}:" + Convert.ToHexString(indexBuffer.Data.AsSpan(0, Math.Min(indexBuffer.Data.Length, 32))) : "none"; + var vertexInputs = draw.VertexInputs.Count == 0 + ? "none" + : string.Join( + ',', + draw.VertexInputs.Select(input => + $"{input.Location}:pc=0x{input.Pc:X}:0x{input.BaseAddress:X16}" + + $":stride{input.Stride}:off{input.OffsetBytes}:c{input.ComponentCount}")); + var scissor = draw.RenderState.Scissor is { } drawScissor + ? $"{drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height}" + : "full"; + var viewport = draw.RenderState.Viewport is { } drawViewport + ? $"{drawViewport.X:0.###},{drawViewport.Y:0.###}," + + $"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" + + $"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}" + : "full"; + var blend = draw.RenderState.Blend; TraceAgcShader( $"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " + $"ps=0x{draw.PixelShaderAddress:X16} spirv={draw.PixelSpirv.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} " + $"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " + $"targets=[{targets}] textures=[{textures}] " + - $"buffers=[{buffers}] indices=[{indices}]"); + $"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]"); } private static IReadOnlyList CreateVulkanGuestDrawTextures( @@ -3196,6 +3512,7 @@ public static class AgcExports binding.Descriptor, binding.IsStorage, binding.MipLevel, + binding.SamplerDescriptor, out var texture)) { textures.Add(texture); @@ -3223,11 +3540,31 @@ public static class AgcExports return buffers; } + private static IReadOnlyList CreateVulkanGuestVertexBuffers( + IReadOnlyList bindings) + { + var buffers = new VulkanGuestVertexBuffer[bindings.Count]; + for (var index = 0; index < bindings.Count; index++) + { + var binding = bindings[index]; + buffers[index] = new VulkanGuestVertexBuffer( + binding.Location, + binding.ComponentCount, + binding.BaseAddress, + binding.Stride, + binding.OffsetBytes, + binding.Data); + } + + return buffers; + } + private static bool TryCreateVulkanGuestDrawTexture( CpuContext ctx, TextureDescriptor descriptor, bool isStorage, uint mipLevel, + IReadOnlyList samplerDescriptor, out VulkanGuestDrawTexture texture) { texture = default!; @@ -3241,9 +3578,12 @@ public static class AgcExports return true; } + var sourceWidth = descriptor.TileMode == 0 + ? Math.Max(descriptor.Width, descriptor.Pitch) + : descriptor.Width; var sourceByteCount = GetTextureByteCount( descriptor.Format, - descriptor.Width, + sourceWidth, descriptor.Height); if (sourceByteCount == 0 || sourceByteCount > MaxPresentedTextureBytes || @@ -3276,7 +3616,11 @@ public static class AgcExports IsFallback: descriptor.Address == 0, IsStorage: true, MipLevels: descriptor.MipLevels, - MipLevel: mipLevel); + MipLevel: mipLevel, + Pitch: descriptor.Pitch, + TileMode: descriptor.TileMode, + DstSelect: descriptor.DstSelect, + Sampler: ToVulkanSampler(samplerDescriptor)); return true; } @@ -3303,7 +3647,8 @@ public static class AgcExports TraceAgcShader( $"agc.texture_source addr=0x{descriptor.Address:X16} " + $"fmt={descriptor.Format} num={descriptor.NumberType} tile={descriptor.TileMode} " + - $"size={descriptor.Width}x{descriptor.Height} " + + $"size={descriptor.Width}x{descriptor.Height} pitch={descriptor.Pitch} " + + $"dst=0x{descriptor.DstSelect:X3} " + $"bytes={source.Length} nonzero64={nonZero}"); var rgba = source; @@ -3317,7 +3662,11 @@ public static class AgcExports IsFallback: false, IsStorage: isStorage, MipLevels: descriptor.MipLevels, - MipLevel: mipLevel); + MipLevel: mipLevel, + Pitch: descriptor.Pitch, + TileMode: descriptor.TileMode, + DstSelect: descriptor.DstSelect, + Sampler: ToVulkanSampler(samplerDescriptor)); return true; } @@ -3334,6 +3683,15 @@ public static class AgcExports MipLevels: 1, MipLevel: 0); + private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList descriptor) => + descriptor.Count >= 4 + ? new VulkanGuestSampler( + descriptor[0], + descriptor[1], + descriptor[2], + descriptor[3]) + : default; + private static byte[] ConvertRgba16FloatToRgba8(ReadOnlySpan source, uint width, uint height) { var destination = new byte[checked((int)((ulong)width * height * 4))]; @@ -3486,7 +3844,8 @@ public static class AgcExports new TranslatedImageBinding( texture, isStorage, - binding.MipLevel ?? 0)); + binding.MipLevel ?? 0, + binding.SamplerDescriptor)); hasStorageBinding |= isStorage; var descriptorState = descriptorValid ? string.Empty : "/invalid-desc"; @@ -4248,6 +4607,10 @@ public static class AgcExports var type = (fields[3] >> 28) & 0xFu; var baseLevel = (fields[3] >> 12) & 0xFu; var lastLevel = (fields[3] >> 16) & 0xFu; + var pitch = fields.Count >= 5 + ? ((fields[4] >> 13) & 0x3FFFu) + 1 + : width; + var dstSelect = fields[3] & 0xFFFu; if (address == 0 || width == 0 || height == 0) { return false; @@ -4262,7 +4625,9 @@ public static class AgcExports tileMode, type, baseLevel, - lastLevel); + lastLevel, + pitch, + dstSelect); return true; } @@ -4291,7 +4656,9 @@ public static class AgcExports TileMode: tileMode, Type: Gen5TextureType2D, BaseLevel: 0, - LastLevel: 0); + LastLevel: 0, + Pitch: 1, + DstSelect: 0xFAC); } private static bool TrySoftwarePresent( diff --git a/src/SharpEmu.Libs/Agc/Gen5ShaderIr.cs b/src/SharpEmu.Libs/Agc/Gen5ShaderIr.cs index 6c6283e0..d7ddc76d 100644 --- a/src/SharpEmu.Libs/Agc/Gen5ShaderIr.cs +++ b/src/SharpEmu.Libs/Agc/Gen5ShaderIr.cs @@ -60,7 +60,8 @@ internal sealed record Gen5SpirvShader( byte[] Spirv, IReadOnlyList GlobalMemoryBindings, IReadOnlyList ImageBindings, - uint AttributeCount); + uint AttributeCount, + IReadOnlyList VertexInputs); internal readonly record struct Gen5ShaderResourceMapping( Gen5ShaderResourceKind Kind, @@ -271,6 +272,15 @@ internal sealed record Gen5GlobalMemoryBinding( IReadOnlyList InstructionPcs, byte[] Data); +internal sealed record Gen5VertexInputBinding( + uint Pc, + uint Location, + uint ComponentCount, + ulong BaseAddress, + uint Stride, + uint OffsetBytes, + byte[] Data); + internal sealed record Gen5ShaderEvaluation( IReadOnlyList InitialScalarRegisters, IReadOnlyList ScalarRegisters, @@ -278,7 +288,8 @@ internal sealed record Gen5ShaderEvaluation( IReadOnlyList ImageBindings, IReadOnlyList GlobalMemoryBindings, Gen5ComputeSystemRegisters? ComputeSystemRegisters = null, - IReadOnlySet? RuntimeScalarRegisters = null); + IReadOnlySet? RuntimeScalarRegisters = null, + IReadOnlyList? VertexInputs = null); internal sealed record Gen5ShaderInstruction( uint Pc, diff --git a/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs b/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs index 0f7e3dfb..269d596a 100644 --- a/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs +++ b/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs @@ -14,6 +14,7 @@ internal static class Gen5ShaderScalarEvaluator private const int ImageDescriptorDwords = 8; private const int SamplerDescriptorDwords = 4; private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024; + private const ulong RdnaWaveMask = 0xFFFF_FFFFUL; private readonly record struct BufferDescriptor( ulong BaseAddress, @@ -41,7 +42,8 @@ internal static class Gen5ShaderScalarEvaluator CpuContext ctx, Gen5ShaderState state, out Gen5ShaderEvaluation evaluation, - out string error) + out string error, + bool resolveVertexInputs = false) { evaluation = default!; error = string.Empty; @@ -60,14 +62,15 @@ internal static class Gen5ShaderScalarEvaluator computeSystemRegisters.ClearStaticValues(scalarRegisters); } - var execMask = ulong.MaxValue; - WriteScalarPair(scalarRegisters, 106, ulong.MaxValue, ref execMask); + var execMask = RdnaWaveMask; + WriteScalarPair(scalarRegisters, 106, 0, ref execMask); WriteScalarPair(scalarRegisters, 126, execMask, ref execMask); var initialScalarRegisters = (uint[])scalarRegisters.Clone(); var resolved = new List(); var globalMemoryBindings = new List(); var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>(); + var vertexInputBindings = new List(); var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program); var scalarRegisterSnapshots = new Dictionary>(); var scalarConditionCode = false; @@ -248,6 +251,42 @@ internal static class Gen5ShaderScalarEvaluator return false; } + if (resolveVertexInputs && + IsVertexFetchCandidate(instruction, bufferMemory, bufferDescriptor)) + { + if (!TryReadGlobalMemory( + ctx, + bufferDescriptor.BaseAddress, + bufferDescriptor.SizeBytes, + out var vertexData)) + { + error = + $"vertex-buffer-read-failed pc=0x{instruction.Pc:X} " + + $"address=0x{bufferDescriptor.BaseAddress:X16} " + + $"bytes={bufferDescriptor.SizeBytes} " + + $"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords}"; + return false; + } + + if (!TryCreateVertexInputBinding( + instruction, + bufferMemory, + bufferDescriptor, + vertexData, + (uint)vertexInputBindings.Count, + scalarRegisters, + out var vertexInputBinding)) + { + error = + $"vertex-input-binding-failed pc=0x{instruction.Pc:X} " + + $"s{bufferMemory.ScalarResource}"; + return false; + } + + vertexInputBindings.Add(vertexInputBinding); + continue; + } + var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress); if (globalMemoryByAddress.TryGetValue(key, out var existingBinding)) { @@ -339,10 +378,51 @@ internal static class Gen5ShaderScalarEvaluator resolved, globalMemoryBindings, state.ComputeSystemRegisters, - runtimeScalarRegisters); + runtimeScalarRegisters, + vertexInputBindings); return true; } + private static bool TryCreateVertexInputBinding( + Gen5ShaderInstruction instruction, + Gen5BufferMemoryControl control, + BufferDescriptor descriptor, + byte[] data, + uint location, + uint[] scalarRegisters, + out Gen5VertexInputBinding binding) + { + binding = default!; + if (!IsVertexFetchCandidate(instruction, control, descriptor) || + instruction.Sources.Count <= 2 || + !TryEvaluateScalarOperand(instruction.Sources[2], scalarRegisters, out var scalarOffset)) + { + return false; + } + + binding = new Gen5VertexInputBinding( + instruction.Pc, + location, + control.DwordCount, + descriptor.BaseAddress, + descriptor.Stride, + unchecked((uint)control.OffsetBytes + scalarOffset), + data); + return true; + } + + private static bool IsVertexFetchCandidate( + Gen5ShaderInstruction instruction, + Gen5BufferMemoryControl control, + BufferDescriptor descriptor) => + control.IndexEnabled && + !control.OffsetEnabled && + control.DwordCount is >= 1 and <= 4 && + descriptor.BaseAddress != 0 && + descriptor.Stride != 0 && + (instruction.Opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) || + instruction.Opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal)); + private static HashSet CollectRuntimeScalarRegisters(Gen5ShaderProgram program) { var registers = new HashSet(); @@ -950,9 +1030,9 @@ internal static class Gen5ShaderScalarEvaluator }; WriteScalarPair(registers, destination.Value, oldExec, ref execMask); - execMask = newExec; + execMask = MaskWaveValue(newExec); WriteScalarPair(registers, 126, execMask, ref execMask); - scalarConditionCode = newExec != 0; + scalarConditionCode = execMask != 0; return true; } @@ -1000,6 +1080,11 @@ internal static class Gen5ShaderScalarEvaluator return; } + if (destination == 126) + { + value = MaskWaveValue(value); + } + registers[destination] = (uint)value; registers[destination + 1] = (uint)(value >> 32); if (destination == 126) @@ -1008,6 +1093,8 @@ internal static class Gen5ShaderScalarEvaluator } } + private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask; + private static bool TryExecuteScalarCompare( Gen5ShaderInstruction instruction, uint[] registers, diff --git a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs index 5afcc69a..f2682907 100644 --- a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs @@ -133,9 +133,7 @@ internal static partial class Gen5SpirvTranslator SpirvOp.ConvertFToU, _uintType, GetFloatSource(instruction, 0)); - var offset = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, + var offset = ShiftLeftLogical( BitwiseAnd(GetRawSource(instruction, 1), UInt(3)), UInt(3)); result = _module.AddInstruction( @@ -352,9 +350,7 @@ internal static partial class Gen5SpirvTranslator result = _module.AddInstruction( SpirvOp.UConvert, _uintType, - _module.AddInstruction( - SpirvOp.ShiftRightLogical, - _ulongType, + ShiftRightLogical64( product, _module.Constant64(_ulongType, 32))); break; @@ -417,21 +413,12 @@ internal static partial class Gen5SpirvTranslator var reverse = instruction.Opcode == "VAshrrevI32"; var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); - right = BitwiseAnd(right, UInt(31)); - result = Bitcast( - _uintType, - _module.AddInstruction( - SpirvOp.ShiftRightArithmetic, - _intType, - Bitcast(_intType, left), - right)); + result = ShiftRightArithmetic(left, right); break; } case "VLshlAddU32": { - var shifted = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, + var shifted = ShiftLeftLogical( GetRawSource(instruction, 0), BitwiseAnd(GetRawSource(instruction, 1), UInt(31))); result = IAdd(shifted, GetRawSource(instruction, 2)); @@ -439,9 +426,7 @@ internal static partial class Gen5SpirvTranslator } case "VLshlOrU32": { - var shifted = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, + var shifted = ShiftLeftLogical( GetRawSource(instruction, 0), BitwiseAnd(GetRawSource(instruction, 1), UInt(31))); result = BitwiseOr( @@ -474,11 +459,7 @@ internal static partial class Gen5SpirvTranslator var added = IAdd( GetRawSource(instruction, 0), GetRawSource(instruction, 1)); - result = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, - added, - BitwiseAnd(GetRawSource(instruction, 2), UInt(31))); + result = ShiftLeftLogical(added, GetRawSource(instruction, 2)); break; } case "VAdd3U32": @@ -1101,11 +1082,7 @@ internal static partial class Gen5SpirvTranslator _module.AddInstruction(SpirvOp.Not, _uintType, right)); break; case "SLshlB32": - result = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, - left, - BitwiseAnd(right, UInt(31))); + result = ShiftLeftLogical(left, right); break; case "SLshrB32": result = ShiftRightLogical( @@ -1113,13 +1090,7 @@ internal static partial class Gen5SpirvTranslator BitwiseAnd(right, UInt(31))); break; case "SAshrI32": - result = Bitcast( - _uintType, - _module.AddInstruction( - SpirvOp.ShiftRightArithmetic, - _intType, - Bitcast(_intType, left), - BitwiseAnd(right, UInt(31)))); + result = ShiftRightArithmetic(left, right); break; case "SBfmB32": result = _module.AddInstruction( @@ -1185,11 +1156,7 @@ internal static partial class Gen5SpirvTranslator { var shift = (uint)(instruction.Opcode[5] - '0'); result = IAdd( - _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, - left, - UInt(shift)), + ShiftLeftLogical(left, UInt(shift)), right); break; } @@ -1362,14 +1329,10 @@ internal static partial class Gen5SpirvTranslator var shift = _module.AddInstruction( SpirvOp.UConvert, _ulongType, - BitwiseAnd(GetRawSource(instruction, 1), UInt(63))); - var shiftedValue = _module.AddInstruction( - instruction.Opcode == "SLshlB64" - ? SpirvOp.ShiftLeftLogical - : SpirvOp.ShiftRightLogical, - _ulongType, - left, - shift); + GetRawSource(instruction, 1)); + var shiftedValue = instruction.Opcode == "SLshlB64" + ? ShiftLeftLogical64(left, shift) + : ShiftRightLogical64(left, shift); StoreS64(destination, shiftedValue); Store(_scc, IsNotZero64(shiftedValue)); return true; @@ -1398,21 +1361,71 @@ internal static partial class Gen5SpirvTranslator _uintType, requestedWidth, remaining); - var extracted = instruction.Opcode == "SBfeI64" - ? Bitcast( + var offset64 = _module.AddInstruction( + SpirvOp.UConvert, + _ulongType, + offset); + var width64 = _module.AddInstruction( + SpirvOp.UConvert, + _ulongType, + width); + var one64 = _module.Constant64(_ulongType, 1); + var shifted = ShiftRightLogical64(left, offset64); + var partialMask = _module.AddInstruction( + SpirvOp.ISub, + _ulongType, + ShiftLeftLogical64(one64, width64), + one64); + var fullWidth = _module.AddInstruction( + SpirvOp.IEqual, + _boolType, + width, + UInt(64)); + var mask = _module.AddInstruction( + SpirvOp.Select, + _ulongType, + fullWidth, + _module.Constant64(_ulongType, ulong.MaxValue), + partialMask); + var extracted = _module.AddInstruction( + SpirvOp.BitwiseAnd, + _ulongType, + shifted, + mask); + if (instruction.Opcode == "SBfeI64") + { + var signShift = _module.AddInstruction( + SpirvOp.ISub, + _uintType, + width, + UInt(1)); + var signBit = ShiftLeftLogical64( + one64, + _module.AddInstruction( + SpirvOp.UConvert, + _ulongType, + signShift)); + var signExtended = _module.AddInstruction( + SpirvOp.ISub, _ulongType, _module.AddInstruction( - SpirvOp.BitFieldSExtract, - _longType, - Bitcast(_longType, left), - offset, - width)) - : _module.AddInstruction( - SpirvOp.BitFieldUExtract, + SpirvOp.BitwiseXor, + _ulongType, + extracted, + signBit), + signBit); + extracted = _module.AddInstruction( + SpirvOp.Select, _ulongType, - left, - offset, - width); + _module.AddInstruction( + SpirvOp.IEqual, + _boolType, + width, + UInt(0)), + _module.Constant64(_ulongType, 0), + signExtended); + } + StoreS64(destination, extracted); Store(_scc, IsNotZero64(extracted)); return true; @@ -1615,11 +1628,7 @@ internal static partial class Gen5SpirvTranslator SpirvOp.UConvert, _ulongType, LoadS(register + 1)); - high = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _ulongType, - high, - _module.Constant64(_ulongType, 32)); + high = ShiftLeftLogical64(high, _module.Constant64(_ulongType, 32)); return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high); } @@ -1628,9 +1637,7 @@ internal static partial class Gen5SpirvTranslator StoreS( register, _module.AddInstruction(SpirvOp.UConvert, _uintType, value)); - var high = _module.AddInstruction( - SpirvOp.ShiftRightLogical, - _ulongType, + var high = ShiftRightLogical64( value, _module.Constant64(_ulongType, 32)); StoreS( @@ -1682,12 +1689,19 @@ internal static partial class Gen5SpirvTranslator { var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); - if (operation is - SpirvOp.ShiftLeftLogical or - SpirvOp.ShiftRightLogical or - SpirvOp.ShiftRightArithmetic) + if (operation == SpirvOp.ShiftLeftLogical) { - right = BitwiseAnd(right, UInt(31)); + return ShiftLeftLogical(left, right); + } + + if (operation == SpirvOp.ShiftRightLogical) + { + return ShiftRightLogical(left, right); + } + + if (operation == SpirvOp.ShiftRightArithmetic) + { + return ShiftRightArithmetic(left, right); } return _module.AddInstruction(operation, _uintType, left, right); @@ -1986,21 +2000,13 @@ internal static partial class Gen5SpirvTranslator _boolType, localLane, UInt(8)); - var lowShift = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, - localLane, - UInt(2)); + var lowShift = ShiftLeftLogical(localLane, UInt(2)); var highLane = _module.AddInstruction( SpirvOp.ISub, _uintType, localLane, UInt(8)); - var highShift = _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _uintType, - highLane, - UInt(2)); + var highShift = ShiftLeftLogical(highLane, UInt(2)); var lowSelector = BitwiseAnd( ShiftRightLogical(selectorLow, lowShift), UInt(15)); diff --git a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.cs b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.cs index 6bc85774..8a0849d0 100644 --- a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.cs @@ -8,6 +8,7 @@ internal static partial class Gen5SpirvTranslator private const uint ScalarRegisterCount = 256; private const uint VectorRegisterCount = 512; private const uint LdsDwordCount = 8192; + private const uint RdnaWaveLaneCount = 32; public static bool TryCompilePixelShader( Gen5ShaderState state, @@ -95,6 +96,7 @@ internal static partial class Gen5SpirvTranslator private readonly List _interfaces = []; private readonly Dictionary _pixelInputs = []; private readonly Dictionary _vertexOutputs = []; + private readonly Dictionary _vertexInputsByPc = []; private readonly List _imageResources = []; private readonly Dictionary _imageBindingByPc = []; private readonly Dictionary _bufferBindingByPc = []; @@ -150,6 +152,11 @@ internal static partial class Gen5SpirvTranslator ImageComponentKind ComponentKind, bool IsStorage); + private readonly record struct SpirvVertexInput( + uint Variable, + uint Type, + uint ComponentCount); + public CompilationContext( Gen5SpirvStage stage, Gen5ShaderState state, @@ -283,7 +290,10 @@ internal static partial class Gen5SpirvTranslator _module.Build(), _evaluation.GlobalMemoryBindings, _evaluation.ImageBindings, - attributeCount); + attributeCount, + _stage == Gen5SpirvStage.Vertex + ? _evaluation.VertexInputs ?? [] + : []); return true; } catch (Exception exception) @@ -298,6 +308,19 @@ internal static partial class Gen5SpirvTranslator _module.AddCapability(SpirvCapability.Shader); _module.AddCapability(SpirvCapability.Int64); _module.AddCapability(SpirvCapability.ImageQuery); + if (_evaluation.ImageBindings.Any( + static binding => + (binding.Opcode.StartsWith( + "ImageSample", + StringComparison.Ordinal) || + binding.Opcode.StartsWith( + "ImageGather4", + StringComparison.Ordinal)) && + binding.Opcode.EndsWith("O", StringComparison.Ordinal))) + { + _module.AddCapability(SpirvCapability.ImageGatherExtended); + } + if (UsesSubgroupOperations()) { _module.AddCapability(SpirvCapability.GroupNonUniform); @@ -389,10 +412,6 @@ internal static partial class Gen5SpirvTranslator } var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount); - _module.AddDecoration( - ldsArrayType, - SpirvDecoration.ArrayStride, - sizeof(uint)); var ldsPointer = _module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType); _workgroupUintPointer = @@ -466,6 +485,11 @@ internal static partial class Gen5SpirvTranslator _module.AddCapability( SpirvCapability.StorageImageWriteWithoutFormat); } + else if (isStorage && RequiresExtendedStorageImageFormat(format)) + { + _module.AddCapability( + SpirvCapability.StorageImageExtendedFormats); + } var imageType = _module.TypeImage( componentType, @@ -503,6 +527,13 @@ internal static partial class Gen5SpirvTranslator } } + private static bool RequiresExtendedStorageImageFormat( + SpirvImageFormat format) => + format is not SpirvImageFormat.Unknown and + not SpirvImageFormat.Rgba32f and + not SpirvImageFormat.Rgba32i and + not SpirvImageFormat.Rgba32ui; + private static (SpirvImageFormat Format, ImageComponentKind Kind) DecodeImageFormat(IReadOnlyList descriptor) { @@ -583,6 +614,8 @@ internal static partial class Gen5SpirvTranslator if (_stage == Gen5SpirvStage.Vertex) { + DeclareVertexInputs(); + var inputPointer = _module.TypePointer(SpirvStorageClass.Input, _uintType); _vertexIndexInput = _module.AddGlobalVariable( @@ -628,7 +661,6 @@ internal static partial class Gen5SpirvTranslator outputPointer, SpirvStorageClass.Output); _module.AddDecoration(variable, SpirvDecoration.Location, parameter); - _module.AddDecoration(variable, SpirvDecoration.NoPerspective); _vertexOutputs.Add(parameter, variable); _interfaces.Add(variable); } @@ -650,7 +682,6 @@ internal static partial class Gen5SpirvTranslator inputVec4Pointer, SpirvStorageClass.Input); _module.AddDecoration(variable, SpirvDecoration.Location, attribute); - _module.AddDecoration(variable, SpirvDecoration.NoPerspective); _pixelInputs.Add(attribute, variable); _interfaces.Add(variable); } @@ -701,6 +732,42 @@ internal static partial class Gen5SpirvTranslator } } + private void DeclareVertexInputs() + { + foreach (var input in _evaluation.VertexInputs ?? []) + { + var type = input.ComponentCount switch + { + 1u => _floatType, + 2u => _vec2Type, + 3u => _vec3Type, + 4u => _vec4Type, + _ => 0u, + }; + if (type == 0) + { + continue; + } + + var pointer = _module.TypePointer(SpirvStorageClass.Input, type); + var variable = _module.AddGlobalVariable( + pointer, + SpirvStorageClass.Input); + _module.AddName(variable, $"attr{input.Location}"); + _module.AddDecoration( + variable, + SpirvDecoration.Location, + input.Location); + _vertexInputsByPc.TryAdd( + input.Pc, + new SpirvVertexInput( + variable, + type, + input.ComponentCount)); + _interfaces.Add(variable); + } + } + private void EmitInitialState() { for (uint index = 0; @@ -1287,6 +1354,19 @@ internal static partial class Gen5SpirvTranslator out string error) { error = string.Empty; + if (_stage == Gen5SpirvStage.Vertex && + _vertexInputsByPc.TryGetValue(instruction.Pc, out var vertexInput)) + { + return TryEmitVertexInputFetch(control, vertexInput, out error); + } + + if (_stage == Gen5SpirvStage.Vertex && + IsFormatBufferLoad(instruction.Opcode)) + { + error = $"missing vertex input for {instruction.Opcode} pc=0x{instruction.Pc:X}"; + return false; + } + if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex)) { error = "missing buffer-memory binding"; @@ -1372,6 +1452,41 @@ internal static partial class Gen5SpirvTranslator return true; } + private static bool IsFormatBufferLoad(string opcode) => + opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) || + opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal); + + private bool TryEmitVertexInputFetch( + Gen5BufferMemoryControl control, + SpirvVertexInput input, + out string error) + { + error = string.Empty; + if (control.DwordCount == 0 || + control.DwordCount > input.ComponentCount) + { + error = + $"invalid vertex input fetch components={control.DwordCount} " + + $"input={input.ComponentCount}"; + return false; + } + + var loaded = Load(input.Type, input.Variable); + for (uint component = 0; component < control.DwordCount; component++) + { + var value = input.ComponentCount == 1 + ? loaded + : _module.AddInstruction( + SpirvOp.CompositeExtract, + _floatType, + loaded, + component); + StoreV(control.VectorData + component, Bitcast(_uintType, value)); + } + + return true; + } + private bool TryEmitImage( Gen5ShaderInstruction instruction, Gen5ImageControl image, @@ -1389,11 +1504,20 @@ internal static partial class Gen5SpirvTranslator var imageObject = Load(resource.ObjectType, resource.Variable); if (instruction.Opcode == "ImageGetResinfo") { + var queryImage = resource.IsStorage + ? imageObject + : _module.AddInstruction( + SpirvOp.Image, + resource.ImageType, + imageObject); var size = _module.AddInstruction( - SpirvOp.ImageQuerySizeLod, + resource.IsStorage + ? SpirvOp.ImageQuerySize + : SpirvOp.ImageQuerySizeLod, _module.TypeVector(_intType, 2), - imageObject, - UInt(0)); + resource.IsStorage + ? [queryImage] + : [queryImage, UInt(0)]); uint outputIndex = 0; for (uint component = 0; component < 4; component++) { @@ -1504,10 +1628,14 @@ internal static partial class Gen5SpirvTranslator ? BuildClampedIntegerCoordinates(image, 0, width, height) : BuildIntegerCoordinates(image, 0); var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0; + var fetchedImage = _module.AddInstruction( + SpirvOp.Image, + resource.ImageType, + imageObject); sampled = _module.AddInstruction( SpirvOp.ImageFetch, resource.VectorType, - imageObject, + fetchedImage, coordinates, 2, UInt(mipLevel)); @@ -2057,18 +2185,13 @@ internal static partial class Gen5SpirvTranslator private void StoreS(uint register, uint value) { Store(ScalarPointer(register), value); - if (_subgroupInvocationIdInput == 0) - { - return; - } - if (register is 106 or 107) { - Store(_vcc, IsCurrentLaneSet(LoadS64(106))); + Store(_vcc, IsWaveMaskActive(LoadS64(106))); } else if (register is 126 or 127) { - Store(_exec, IsCurrentLaneSet(LoadS64(126))); + Store(_exec, IsWaveMaskActive(LoadS64(126))); } } @@ -2105,12 +2228,49 @@ internal static partial class Gen5SpirvTranslator private uint IAdd(uint left, uint right) => _module.AddInstruction(SpirvOp.IAdd, _uintType, left, right); + private uint ShiftLeftLogical(uint left, uint right) => + _module.AddInstruction( + SpirvOp.ShiftLeftLogical, + _uintType, + left, + BitwiseAnd(right, UInt(31))); + private uint ShiftRightLogical(uint left, uint right) => - _module.AddInstruction(SpirvOp.ShiftRightLogical, _uintType, left, right); + _module.AddInstruction( + SpirvOp.ShiftRightLogical, + _uintType, + left, + BitwiseAnd(right, UInt(31))); + + private uint ShiftRightArithmetic(uint left, uint right) => + Bitcast( + _uintType, + _module.AddInstruction( + SpirvOp.ShiftRightArithmetic, + _intType, + Bitcast(_intType, left), + BitwiseAnd(right, UInt(31)))); + + private uint ShiftLeftLogical64(uint left, uint right) => + _module.AddInstruction( + SpirvOp.ShiftLeftLogical, + _ulongType, + left, + BitwiseAnd64(right, _module.Constant64(_ulongType, 63))); + + private uint ShiftRightLogical64(uint left, uint right) => + _module.AddInstruction( + SpirvOp.ShiftRightLogical, + _ulongType, + left, + BitwiseAnd64(right, _module.Constant64(_ulongType, 63))); private uint BitwiseAnd(uint left, uint right) => _module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, right); + private uint BitwiseAnd64(uint left, uint right) => + _module.AddInstruction(SpirvOp.BitwiseAnd, _ulongType, left, right); + private uint BitwiseOr(uint left, uint right) => _module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right); @@ -2121,25 +2281,44 @@ internal static partial class Gen5SpirvTranslator _module.AddInstruction(SpirvOp.LogicalNot, _boolType, value); private uint SubgroupAny(uint condition) => - _module.AddInstruction( - SpirvOp.GroupNonUniformAny, - _boolType, - UInt(3), - condition); + _subgroupInvocationIdInput == 0 + ? condition + : _module.AddInstruction( + SpirvOp.GroupNonUniformAny, + _boolType, + UInt(3), + condition); private uint CurrentLaneBit() { - var lane = _module.AddInstruction( - SpirvOp.UConvert, - _ulongType, - Load(_uintType, _subgroupInvocationIdInput)); - return _module.AddInstruction( - SpirvOp.ShiftLeftLogical, - _ulongType, + if (_subgroupInvocationIdInput == 0) + { + return _module.Constant64(_ulongType, 1); + } + + var lane = Load(_uintType, _subgroupInvocationIdInput); + var maskedLane = BitwiseAnd(lane, UInt(RdnaWaveLaneCount - 1)); + var shifted = ShiftLeftLogical64( _module.Constant64(_ulongType, 1), - lane); + _module.AddInstruction( + SpirvOp.UConvert, + _ulongType, + maskedLane)); + return _module.AddInstruction( + SpirvOp.Select, + _ulongType, + IsCurrentLaneInRdnaWave(), + shifted, + _module.Constant64(_ulongType, 0)); } + private uint IsCurrentLaneInRdnaWave() => + _module.AddInstruction( + SpirvOp.ULessThan, + _boolType, + Load(_uintType, _subgroupInvocationIdInput), + UInt(RdnaWaveLaneCount)); + private uint BooleanToLaneMask(uint condition) => _module.AddInstruction( SpirvOp.Select, @@ -2148,6 +2327,11 @@ internal static partial class Gen5SpirvTranslator CurrentLaneBit(), _module.Constant64(_ulongType, 0)); + private uint IsWaveMaskActive(uint mask) => + _subgroupInvocationIdInput == 0 + ? IsNotZero64(mask) + : IsCurrentLaneSet(mask); + private uint IsCurrentLaneSet(uint mask) => IsNotZero64( _module.AddInstruction( @@ -2163,10 +2347,11 @@ internal static partial class Gen5SpirvTranslator { var activeLabel = _module.AllocateId(); var mergeLabel = _module.AllocateId(); + var active = Load(_boolType, _exec); _module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0); _module.AddStatement( SpirvOp.BranchConditional, - Load(_boolType, _exec), + active, activeLabel, mergeLabel); _module.AddLabel(activeLabel); @@ -2193,7 +2378,8 @@ internal static partial class Gen5SpirvTranslator instruction.Destinations.Any(IsWaveMaskOperand)); private bool UsesSubgroupOperations() => - UsesSubgroupShuffle() || UsesWaveControl(); + _stage == Gen5SpirvStage.Compute && + (UsesSubgroupShuffle() || UsesWaveControl()); private static bool IsWaveMaskOperand(Gen5Operand operand) => operand.Kind == Gen5OperandKind.ScalarRegister && diff --git a/src/SharpEmu.Libs/Agc/SpirvModuleBuilder.cs b/src/SharpEmu.Libs/Agc/SpirvModuleBuilder.cs index f65c35a3..c6394a96 100644 --- a/src/SharpEmu.Libs/Agc/SpirvModuleBuilder.cs +++ b/src/SharpEmu.Libs/Agc/SpirvModuleBuilder.cs @@ -62,6 +62,7 @@ internal enum SpirvOp : ushort ImageDrefGather = 97, ImageRead = 98, ImageWrite = 99, + Image = 100, ImageQuerySizeLod = 103, ImageQuerySize = 104, ImageQueryLod = 105, @@ -173,6 +174,8 @@ internal enum SpirvCapability : uint Float64 = 10, Int64 = 11, Int16 = 22, + ImageGatherExtended = 25, + StorageImageExtendedFormats = 49, ImageQuery = 50, StorageImageReadWithoutFormat = 55, StorageImageWriteWithoutFormat = 56, @@ -310,6 +313,18 @@ internal sealed class SpirvModuleBuilder private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = []; private readonly Dictionary _floatTypes = []; private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = []; + private readonly Dictionary< + ( + uint SampledType, + SpirvImageDim Dimension, + bool Depth, + bool Arrayed, + bool Multisampled, + uint Sampled, + SpirvImageFormat Format + ), + uint> _imageTypes = []; + private readonly Dictionary _sampledImageTypes = []; private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = []; private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = []; private readonly Dictionary _runtimeArrayTypes = []; @@ -478,6 +493,19 @@ internal sealed class SpirvModuleBuilder uint sampled, SpirvImageFormat format) { + var key = ( + sampledType, + dimension, + depth, + arrayed, + multisampled, + sampled, + format); + if (_imageTypes.TryGetValue(key, out var existing)) + { + return existing; + } + var id = AllocateId(); Emit( _typesConstantsGlobals, @@ -490,13 +518,20 @@ internal sealed class SpirvModuleBuilder multisampled ? 1u : 0u, sampled, (uint)format); + _imageTypes.Add(key, id); return id; } public uint TypeSampledImage(uint imageType) { + if (_sampledImageTypes.TryGetValue(imageType, out var existing)) + { + return existing; + } + var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType); + _sampledImageTypes.Add(imageType, id); return id; } diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index 17080dd6..64dfffe9 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -31,16 +31,82 @@ internal sealed record VulkanGuestDrawTexture( bool IsFallback, bool IsStorage, uint MipLevels = 1, - uint MipLevel = 0); + uint MipLevel = 0, + uint Pitch = 0, + uint TileMode = 0, + uint DstSelect = 0xFAC, + VulkanGuestSampler Sampler = default); + +internal readonly record struct VulkanGuestSampler( + uint Word0, + uint Word1, + uint Word2, + uint Word3); internal sealed record VulkanGuestMemoryBuffer( ulong BaseAddress, byte[] Data); +internal sealed record VulkanGuestVertexBuffer( + uint Location, + uint ComponentCount, + ulong BaseAddress, + uint Stride, + uint OffsetBytes, + byte[] Data); + internal sealed record VulkanGuestIndexBuffer( byte[] Data, bool Is32Bit); +internal readonly record struct VulkanGuestRect( + int X, + int Y, + uint Width, + uint Height); + +internal readonly record struct VulkanGuestViewport( + float X, + float Y, + float Width, + float Height, + float MinDepth, + float MaxDepth); + +internal readonly record struct VulkanGuestBlendState( + bool Enable, + uint ColorSrcFactor, + uint ColorDstFactor, + uint ColorFunc, + uint AlphaSrcFactor, + uint AlphaDstFactor, + uint AlphaFunc, + bool SeparateAlphaBlend, + uint WriteMask) +{ + public static VulkanGuestBlendState Default { get; } = new( + Enable: false, + ColorSrcFactor: 1, + ColorDstFactor: 0, + ColorFunc: 0, + AlphaSrcFactor: 1, + AlphaDstFactor: 0, + AlphaFunc: 0, + SeparateAlphaBlend: false, + WriteMask: 0xFu); +} + +internal sealed record VulkanGuestRenderState( + VulkanGuestBlendState Blend, + VulkanGuestRect? Scissor, + VulkanGuestViewport? Viewport) +{ + public static VulkanGuestRenderState Default { get; } = new( + VulkanGuestBlendState.Default, + Scissor: null, + Viewport: null); +} + internal sealed record VulkanGuestRenderTarget( ulong Address, uint Width, @@ -54,11 +120,13 @@ internal sealed record VulkanTranslatedGuestDraw( byte[] PixelSpirv, IReadOnlyList Textures, IReadOnlyList GlobalMemoryBuffers, + IReadOnlyList VertexBuffers, uint AttributeCount, uint VertexCount, uint InstanceCount, uint PrimitiveType, - VulkanGuestIndexBuffer? IndexBuffer); + VulkanGuestIndexBuffer? IndexBuffer, + VulkanGuestRenderState RenderState); internal sealed record VulkanOffscreenGuestDraw( VulkanTranslatedGuestDraw Draw, @@ -76,8 +144,11 @@ internal sealed record VulkanComputeGuestDispatch( internal static unsafe class VulkanVideoPresenter { + private const uint DefaultWindowWidth = 1280; + private const uint DefaultWindowHeight = 720; private const int MaxPendingGuestWork = 16; private const int MaxGuestWorkPerRender = 16; + private const uint GuestPrimitiveRectList = 0x11; private const uint GuestFormatR32Uint = 0x10004; private const uint GuestFormatR32Sint = 0x20004; private const uint GuestFormatR32Sfloat = 0x30004; @@ -288,7 +359,9 @@ internal static unsafe class VulkanVideoPresenter uint vertexCount = 3, uint instanceCount = 1, uint primitiveType = 4, - VulkanGuestIndexBuffer? indexBuffer = null) + VulkanGuestIndexBuffer? indexBuffer = null, + IReadOnlyList? vertexBuffers = null, + VulkanGuestRenderState? renderState = null) { if (pixelSpirv.Length == 0 || width == 0 || height == 0) { @@ -314,11 +387,13 @@ internal static unsafe class VulkanVideoPresenter pixelSpirv, textures.ToArray(), globalMemoryBuffers.ToArray(), + vertexBuffers?.ToArray() ?? [], attributeCount, vertexCount, instanceCount, primitiveType, - indexBuffer), + indexBuffer, + renderState ?? VulkanGuestRenderState.Default), RequiredGuestWorkSequence: _enqueuedGuestWorkSequence, IsSplash: false); if (_thread is not null) @@ -347,7 +422,9 @@ internal static unsafe class VulkanVideoPresenter uint vertexCount = 3, uint instanceCount = 1, uint primitiveType = 4, - VulkanGuestIndexBuffer? indexBuffer = null) + VulkanGuestIndexBuffer? indexBuffer = null, + IReadOnlyList? vertexBuffers = null, + VulkanGuestRenderState? renderState = null) { if (pixelSpirv.Length == 0 || target.Address == 0 || @@ -379,11 +456,13 @@ internal static unsafe class VulkanVideoPresenter pixelSpirv, textures.ToArray(), globalMemoryBuffers.ToArray(), + vertexBuffers?.ToArray() ?? [], attributeCount, vertexCount, instanceCount, primitiveType, - indexBuffer), + indexBuffer, + renderState ?? VulkanGuestRenderState.Default), target, PublishTarget: true)); } @@ -419,11 +498,13 @@ internal static unsafe class VulkanVideoPresenter pixelSpirv, textures.ToArray(), globalMemoryBuffers.ToArray(), + [], attributeCount, 3, 1, 4, - null), + null, + VulkanGuestRenderState.Default), new VulkanGuestRenderTarget( Address: 0, width, @@ -779,6 +860,7 @@ internal static unsafe class VulkanVideoPresenter private readonly HashSet<(ulong Address, uint Width, uint Height, Format Format)> _tracedTextureUploads = new(); private readonly HashSet<(ulong Address, int Size)> _tracedGlobalBuffers = new(); private readonly HashSet _tracedGuestImageContents = new(); + private int _tracedVertexBufferCount; private readonly Dictionary _computePipelines = new(ReferenceEqualityComparer.Instance); private readonly Queue _pendingGuestSubmissions = new(); @@ -792,15 +874,18 @@ internal static unsafe class VulkanVideoPresenter public DescriptorSetLayout DescriptorSetLayout; public DescriptorPool DescriptorPool; public DescriptorSet DescriptorSet; - public Sampler Sampler; public TextureResource[] Textures = []; public GlobalBufferResource[] GlobalMemoryBuffers = []; + public VertexBufferResource[] VertexBuffers = []; public VkBuffer IndexBuffer; public DeviceMemory IndexMemory; public bool Index32Bit; public uint VertexCount = 3; public uint InstanceCount = 1; public PrimitiveTopology Topology = PrimitiveTopology.TriangleList; + public VulkanGuestBlendState Blend = VulkanGuestBlendState.Default; + public VulkanGuestRect? Scissor; + public VulkanGuestViewport? Viewport; } private sealed class TextureResource @@ -813,9 +898,13 @@ internal static unsafe class VulkanVideoPresenter public ImageView View; public uint Width; public uint Height; + public uint RowLength; + public uint DstSelect; public bool NeedsUpload; public bool OwnsStorage; public bool IsStorage; + public VulkanGuestSampler SamplerState; + public Sampler Sampler; public GuestImageResource? GuestImage; } @@ -826,6 +915,17 @@ internal static unsafe class VulkanVideoPresenter public ulong Size; } + private sealed class VertexBufferResource + { + public VkBuffer Buffer; + public DeviceMemory Memory; + public ulong Size; + public uint Location; + public uint ComponentCount; + public uint Stride; + public uint OffsetBytes; + } + private sealed class GuestImageResource { public ulong Address; @@ -837,7 +937,7 @@ internal static unsafe class VulkanVideoPresenter public DeviceMemory Memory; public ImageView View; public ImageView[] MipViews = []; - public Dictionary<(Format Format, uint MipLevel, uint LevelCount), ImageView> FormatViews { get; } = new(); + public Dictionary<(Format Format, uint MipLevel, uint LevelCount, uint DstSelect), ImageView> FormatViews { get; } = new(); public RenderPass RenderPass; public Framebuffer Framebuffer; public bool Initialized; @@ -854,7 +954,7 @@ internal static unsafe class VulkanVideoPresenter public Presenter(uint width, uint height) { var options = WindowOptions.DefaultVulkan; - options.Size = new Vector2D((int)width, (int)height); + options.Size = new Vector2D((int)DefaultWindowWidth, (int)DefaultWindowHeight); options.Title = VideoOutExports.GetWindowTitle(); options.WindowBorder = WindowBorder.Fixed; options.VSync = true; @@ -1682,9 +1782,13 @@ internal static unsafe class VulkanVideoPresenter Textures = new TextureResource[draw.Textures.Count], GlobalMemoryBuffers = new GlobalBufferResource[draw.GlobalMemoryBuffers.Count], - VertexCount = draw.VertexCount, + VertexBuffers = new VertexBufferResource[draw.VertexBuffers.Count], + VertexCount = GetDrawVertexCount(draw.PrimitiveType, draw.VertexCount, draw.IndexBuffer), InstanceCount = Math.Max(draw.InstanceCount, 1), Topology = GetPrimitiveTopology(draw.PrimitiveType), + Blend = draw.RenderState.Blend, + Scissor = draw.RenderState.Scissor, + Viewport = draw.RenderState.Viewport, }; try @@ -1708,6 +1812,12 @@ internal static unsafe class VulkanVideoPresenter CreateGlobalBufferResource(draw.GlobalMemoryBuffers[index]); } + for (var index = 0; index < draw.VertexBuffers.Count; index++) + { + resources.VertexBuffers[index] = + CreateVertexBufferResource(draw.VertexBuffers[index]); + } + if (draw.IndexBuffer is { Data.Length: > 0 } indexBuffer) { resources.IndexBuffer = CreateHostBuffer( @@ -1811,6 +1921,7 @@ internal static unsafe class VulkanVideoPresenter { TraceVulkanShader( $"vk.compute_resources pipeline begin " + + $"cs=0x{dispatch.ShaderAddress:X16} " + $"spirv={dispatch.ComputeSpirv.Length} " + $"textures={resources.Textures.Length} " + $"globals={resources.GlobalMemoryBuffers.Length}"); @@ -1934,26 +2045,6 @@ internal static unsafe class VulkanVideoPresenter "vkCreatePipelineLayout"); resources.PipelineLayout = translatedPipelineLayout; - if (sampledImageCount != 0) - { - var samplerInfo = new SamplerCreateInfo - { - SType = StructureType.SamplerCreateInfo, - MagFilter = Filter.Linear, - MinFilter = Filter.Linear, - MipmapMode = SamplerMipmapMode.Linear, - AddressModeU = SamplerAddressMode.ClampToEdge, - AddressModeV = SamplerAddressMode.ClampToEdge, - AddressModeW = SamplerAddressMode.ClampToEdge, - MaxLod = 16, - }; - Sampler sampler; - Check( - _vk.CreateSampler(_device, &samplerInfo, null, out sampler), - "vkCreateSampler"); - resources.Sampler = sampler; - } - var poolSizes = new DescriptorPoolSize[ (sampledImageCount == 0 ? 0 : 1) + (storageImageCount == 0 ? 0 : 1) + @@ -2053,9 +2144,16 @@ internal static unsafe class VulkanVideoPresenter for (var index = 0; index < textureCount; index++) { var isStorage = resources.Textures[index].IsStorage; + if (!isStorage && + resources.Textures[index].Sampler.Handle == 0) + { + resources.Textures[index].Sampler = + CreateSampler(resources.Textures[index].SamplerState); + } + imageInfoPointer[index] = new DescriptorImageInfo { - Sampler = isStorage ? default : resources.Sampler, + Sampler = isStorage ? default : resources.Textures[index].Sampler, ImageView = resources.Textures[index].View, ImageLayout = isStorage || resources.Textures[index].GuestImage is { } guestImage && @@ -2116,90 +2214,138 @@ internal static unsafe class VulkanVideoPresenter PName = entryPoint, }; - var vertexInput = new PipelineVertexInputStateCreateInfo + var vertexBindingDescriptions = + new VertexInputBindingDescription[resources.VertexBuffers.Length]; + var vertexAttributeDescriptions = + new VertexInputAttributeDescription[resources.VertexBuffers.Length]; + for (var index = 0; index < resources.VertexBuffers.Length; index++) { - SType = StructureType.PipelineVertexInputStateCreateInfo, - }; - var inputAssembly = new PipelineInputAssemblyStateCreateInfo + var vertexBuffer = resources.VertexBuffers[index]; + vertexBindingDescriptions[index] = new VertexInputBindingDescription + { + Binding = (uint)index, + Stride = vertexBuffer.Stride == 0 + ? Math.Max(vertexBuffer.ComponentCount, 1) * sizeof(float) + : vertexBuffer.Stride, + InputRate = VertexInputRate.Vertex, + }; + vertexAttributeDescriptions[index] = new VertexInputAttributeDescription + { + Location = vertexBuffer.Location, + Binding = (uint)index, + Format = ToVkVertexFormat(vertexBuffer.ComponentCount), + Offset = 0, + }; + } + + fixed (VertexInputBindingDescription* vertexBindingPointerBase = vertexBindingDescriptions) + fixed (VertexInputAttributeDescription* vertexAttributePointerBase = vertexAttributeDescriptions) { - SType = StructureType.PipelineInputAssemblyStateCreateInfo, - Topology = resources.Topology, - }; - var viewport = new Viewport(0, 0, extent.Width, extent.Height, 0, 1); - var scissor = new Rect2D(new Offset2D(0, 0), extent); - var viewportState = new PipelineViewportStateCreateInfo - { - SType = StructureType.PipelineViewportStateCreateInfo, - ViewportCount = 1, - PViewports = &viewport, - ScissorCount = 1, - PScissors = &scissor, - }; - var rasterization = new PipelineRasterizationStateCreateInfo - { - SType = StructureType.PipelineRasterizationStateCreateInfo, - PolygonMode = PolygonMode.Fill, - CullMode = CullModeFlags.None, - FrontFace = FrontFace.CounterClockwise, - LineWidth = 1, - }; - var multisample = new PipelineMultisampleStateCreateInfo - { - SType = StructureType.PipelineMultisampleStateCreateInfo, - RasterizationSamples = SampleCountFlags.Count1Bit, - }; - var colorBlendAttachment = new PipelineColorBlendAttachmentState - { - ColorWriteMask = - ColorComponentFlags.RBit | - ColorComponentFlags.GBit | - ColorComponentFlags.BBit | - ColorComponentFlags.ABit, - }; - var colorBlend = new PipelineColorBlendStateCreateInfo - { - SType = StructureType.PipelineColorBlendStateCreateInfo, - AttachmentCount = 1, - PAttachments = &colorBlendAttachment, - }; - var dynamicStateValue = DynamicState.Scissor; - var dynamicState = new PipelineDynamicStateCreateInfo - { - SType = StructureType.PipelineDynamicStateCreateInfo, - DynamicStateCount = 1, - PDynamicStates = &dynamicStateValue, - }; - var pipelineInfo = new GraphicsPipelineCreateInfo - { - SType = StructureType.GraphicsPipelineCreateInfo, - StageCount = 2, - PStages = shaderStages, - PVertexInputState = &vertexInput, - PInputAssemblyState = &inputAssembly, - PViewportState = &viewportState, - PRasterizationState = &rasterization, - PMultisampleState = &multisample, - PColorBlendState = &colorBlend, - PDynamicState = &dynamicState, - Layout = resources.PipelineLayout, - RenderPass = renderPass, - Subpass = 0, - }; - Pipeline pipeline; - Check( - _vk.CreateGraphicsPipelines( - _device, - default, - 1, - &pipelineInfo, - null, - out pipeline), - "vkCreateGraphicsPipelines(translated)"); - resources.Pipeline = pipeline; - SetDebugName( - ObjectType.Pipeline, - pipeline.Handle, - $"SharpEmu graphics ps={fragmentSpirv.Length}b attrs={resources.Textures.Length}"); + var vertexInput = new PipelineVertexInputStateCreateInfo + { + SType = StructureType.PipelineVertexInputStateCreateInfo, + VertexBindingDescriptionCount = (uint)vertexBindingDescriptions.Length, + PVertexBindingDescriptions = vertexBindingDescriptions.Length == 0 + ? null + : vertexBindingPointerBase, + VertexAttributeDescriptionCount = (uint)vertexAttributeDescriptions.Length, + PVertexAttributeDescriptions = vertexAttributeDescriptions.Length == 0 + ? null + : vertexAttributePointerBase, + }; + var inputAssembly = new PipelineInputAssemblyStateCreateInfo + { + SType = StructureType.PipelineInputAssemblyStateCreateInfo, + Topology = resources.Topology, + }; + var viewport = new Viewport(0, 0, extent.Width, extent.Height, 0, 1); + var scissor = new Rect2D(new Offset2D(0, 0), extent); + var viewportState = new PipelineViewportStateCreateInfo + { + SType = StructureType.PipelineViewportStateCreateInfo, + ViewportCount = 1, + PViewports = &viewport, + ScissorCount = 1, + PScissors = &scissor, + }; + var rasterization = new PipelineRasterizationStateCreateInfo + { + SType = StructureType.PipelineRasterizationStateCreateInfo, + PolygonMode = PolygonMode.Fill, + CullMode = CullModeFlags.None, + FrontFace = FrontFace.CounterClockwise, + LineWidth = 1, + }; + var multisample = new PipelineMultisampleStateCreateInfo + { + SType = StructureType.PipelineMultisampleStateCreateInfo, + RasterizationSamples = SampleCountFlags.Count1Bit, + }; + var colorBlendAttachment = new PipelineColorBlendAttachmentState + { + BlendEnable = resources.Blend.Enable, + SrcColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorSrcFactor), + DstColorBlendFactor = ToVkBlendFactor(resources.Blend.ColorDstFactor), + ColorBlendOp = ToVkBlendOp(resources.Blend.ColorFunc), + SrcAlphaBlendFactor = resources.Blend.SeparateAlphaBlend + ? ToVkBlendFactor(resources.Blend.AlphaSrcFactor) + : ToVkBlendFactor(resources.Blend.ColorSrcFactor), + DstAlphaBlendFactor = resources.Blend.SeparateAlphaBlend + ? ToVkBlendFactor(resources.Blend.AlphaDstFactor) + : ToVkBlendFactor(resources.Blend.ColorDstFactor), + AlphaBlendOp = resources.Blend.SeparateAlphaBlend + ? ToVkBlendOp(resources.Blend.AlphaFunc) + : ToVkBlendOp(resources.Blend.ColorFunc), + ColorWriteMask = + ToVkColorWriteMask(resources.Blend.WriteMask), + }; + var colorBlend = new PipelineColorBlendStateCreateInfo + { + SType = StructureType.PipelineColorBlendStateCreateInfo, + AttachmentCount = 1, + PAttachments = &colorBlendAttachment, + }; + var dynamicStateValues = stackalloc DynamicState[2]; + dynamicStateValues[0] = DynamicState.Viewport; + dynamicStateValues[1] = DynamicState.Scissor; + var dynamicState = new PipelineDynamicStateCreateInfo + { + SType = StructureType.PipelineDynamicStateCreateInfo, + DynamicStateCount = 2, + PDynamicStates = dynamicStateValues, + }; + var pipelineInfo = new GraphicsPipelineCreateInfo + { + SType = StructureType.GraphicsPipelineCreateInfo, + StageCount = 2, + PStages = shaderStages, + PVertexInputState = &vertexInput, + PInputAssemblyState = &inputAssembly, + PViewportState = &viewportState, + PRasterizationState = &rasterization, + PMultisampleState = &multisample, + PColorBlendState = &colorBlend, + PDynamicState = &dynamicState, + Layout = resources.PipelineLayout, + RenderPass = renderPass, + Subpass = 0, + }; + Pipeline pipeline; + Check( + _vk.CreateGraphicsPipelines( + _device, + default, + 1, + &pipelineInfo, + null, + out pipeline), + "vkCreateGraphicsPipelines(translated)"); + resources.Pipeline = pipeline; + SetDebugName( + ObjectType.Pipeline, + pipeline.Handle, + $"SharpEmu graphics ps={fragmentSpirv.Length}b attrs={resources.Textures.Length}"); + } } finally { @@ -2282,6 +2428,7 @@ internal static unsafe class VulkanVideoPresenter vkFormat, mipLevel: 0, levelCount: guestImage.MipLevels, + dstSelect: texture.DstSelect, out var view)) { if (_tracedTextureCacheHits.Add( @@ -2300,6 +2447,9 @@ internal static unsafe class VulkanVideoPresenter View = view, Width = guestImage.Width, Height = guestImage.Height, + RowLength = guestImage.Width, + DstSelect = texture.DstSelect, + SamplerState = texture.Sampler, GuestImage = guestImage, }; } @@ -2329,7 +2479,10 @@ internal static unsafe class VulkanVideoPresenter View = view, Width = guestImage.Width, Height = guestImage.Height, + RowLength = guestImage.Width, + DstSelect = texture.DstSelect, IsStorage = true, + SamplerState = texture.Sampler, GuestImage = guestImage, }; @@ -2463,8 +2616,11 @@ internal static unsafe class VulkanVideoPresenter View = view, Width = width, Height = height, + RowLength = width, + DstSelect = texture.DstSelect, OwnsStorage = true, IsStorage = true, + SamplerState = texture.Sampler, GuestImage = guestImage, }; } @@ -2499,19 +2655,24 @@ internal static unsafe class VulkanVideoPresenter { var width = Math.Max(texture.Width, 1); var height = Math.Max(texture.Height, 1); + var rowLength = texture.TileMode == 0 + ? Math.Max(texture.Pitch, width) + : width; var vkFormat = GetTextureFormat(texture.Format, texture.NumberType); - var expectedSize = GetTextureByteCount(texture.Format, width, height); + var expectedSize = GetTextureByteCount(texture.Format, rowLength, height); if (_tracedTextureUploads.Add((texture.Address, width, height, vkFormat))) { Console.Error.WriteLine( $"[LOADER][TRACE] vk.texture addr=0x{texture.Address:X16} " + $"fmt={texture.Format} num={texture.NumberType} vk={vkFormat} " + - $"size={width}x{height} bytes={texture.RgbaPixels.Length} expected={expectedSize}"); + $"size={width}x{height} row={rowLength} tile={texture.TileMode} " + + $"dst=0x{texture.DstSelect:X3} " + + $"bytes={texture.RgbaPixels.Length} expected={expectedSize}"); } var pixels = texture.RgbaPixels.Length == (int)expectedSize ? texture.RgbaPixels - : CreateFallbackTexturePixels(texture.Format, width, height, expectedSize); + : CreateFallbackTexturePixels(texture.Format, rowLength, height, expectedSize); var uploadPixels = texture.Format == 13 ? ExpandRgb32Pixels(pixels) : pixels; @@ -2567,11 +2728,7 @@ internal static unsafe class VulkanVideoPresenter Image = image, ViewType = ImageViewType.Type2D, Format = vkFormat, - Components = new ComponentMapping( - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity), + Components = ToVkComponentMapping(texture.DstSelect), SubresourceRange = ColorSubresourceRange(), }; Check(_vk.CreateImageView(_device, &viewInfo, null, out var view), "vkCreateImageView(texture)"); @@ -2589,11 +2746,71 @@ internal static unsafe class VulkanVideoPresenter View = view, Width = width, Height = height, + RowLength = rowLength, + DstSelect = texture.DstSelect, NeedsUpload = true, OwnsStorage = true, + SamplerState = texture.Sampler, }; } + private Sampler CreateSampler(VulkanGuestSampler sampler) + { + var minLod = DecodeSamplerMipFilter(sampler) == 0 + ? 0f + : DecodeSamplerMinLod(sampler); + var maxLod = DecodeSamplerMipFilter(sampler) == 0 + ? 0f + : DecodeSamplerMaxLod(sampler); + var samplerInfo = new SamplerCreateInfo + { + SType = StructureType.SamplerCreateInfo, + MagFilter = ToVkFilter(DecodeSamplerMagFilter(sampler)), + MinFilter = ToVkFilter(DecodeSamplerMinFilter(sampler)), + MipmapMode = ToVkMipFilter(DecodeSamplerMipFilter(sampler)), + AddressModeU = ToVkSamplerAddressMode(DecodeSamplerClampX(sampler)), + AddressModeV = ToVkSamplerAddressMode(DecodeSamplerClampY(sampler)), + AddressModeW = ToVkSamplerAddressMode(DecodeSamplerClampZ(sampler)), + MipLodBias = DecodeSamplerLodBias(sampler), + CompareEnable = DecodeSamplerDepthCompare(sampler) != 0, + CompareOp = ToVkCompareOp(DecodeSamplerDepthCompare(sampler)), + MinLod = minLod, + MaxLod = Math.Max(minLod, maxLod), + BorderColor = ToVkBorderColor(DecodeSamplerBorderColor(sampler)), + }; + Sampler vkSampler; + Check( + _vk.CreateSampler(_device, &samplerInfo, null, out vkSampler), + "vkCreateSampler(texture)"); + return vkSampler; + } + + private static ComponentMapping ToVkComponentMapping(uint dstSelect) + { + if (dstSelect == 0) + { + dstSelect = 0xFAC; + } + + return new ComponentMapping( + ToVkComponentSwizzle(dstSelect & 0x7), + ToVkComponentSwizzle((dstSelect >> 3) & 0x7), + ToVkComponentSwizzle((dstSelect >> 6) & 0x7), + ToVkComponentSwizzle((dstSelect >> 9) & 0x7)); + } + + private static ComponentSwizzle ToVkComponentSwizzle(uint selector) => + selector switch + { + 0 => ComponentSwizzle.Zero, + 1 => ComponentSwizzle.One, + 4 => ComponentSwizzle.R, + 5 => ComponentSwizzle.G, + 6 => ComponentSwizzle.B, + 7 => ComponentSwizzle.A, + _ => ComponentSwizzle.Identity, + }; + private static byte[] ExpandRgb32Pixels(byte[] pixels) { var texelCount = pixels.Length / 12; @@ -2636,6 +2853,40 @@ internal static unsafe class VulkanVideoPresenter }; } + private VertexBufferResource CreateVertexBufferResource( + VulkanGuestVertexBuffer guestBuffer) + { + var buffer = CreateHostBuffer( + guestBuffer.Data, + BufferUsageFlags.VertexBufferBit, + out var memory); + var size = (ulong)Math.Max(guestBuffer.Data.Length, sizeof(uint)); + SetDebugName( + ObjectType.Buffer, + buffer.Handle, + $"SharpEmu vertex loc{guestBuffer.Location} " + + $"0x{guestBuffer.BaseAddress:X16} {guestBuffer.Data.Length}b"); + if (_tracedVertexBufferCount++ < 64) + { + TraceVulkanShader( + $"vk.vertex_buffer loc={guestBuffer.Location} " + + $"base=0x{guestBuffer.BaseAddress:X16} stride={guestBuffer.Stride} " + + $"offset={guestBuffer.OffsetBytes} comps={guestBuffer.ComponentCount} " + + $"bytes={guestBuffer.Data.Length}"); + } + + return new VertexBufferResource + { + Buffer = buffer, + Memory = memory, + Size = size, + Location = guestBuffer.Location, + ComponentCount = guestBuffer.ComponentCount, + Stride = guestBuffer.Stride, + OffsetBytes = guestBuffer.OffsetBytes, + }; + } + private VkBuffer CreateHostBuffer( ReadOnlySpan data, BufferUsageFlags usage, @@ -2676,9 +2927,231 @@ internal static unsafe class VulkanVideoPresenter 3 => PrimitiveTopology.LineStrip, 5 => PrimitiveTopology.TriangleFan, 6 => PrimitiveTopology.TriangleStrip, + GuestPrimitiveRectList => PrimitiveTopology.TriangleStrip, _ => PrimitiveTopology.TriangleList, }; + private static Format ToVkVertexFormat(uint componentCount) => + componentCount switch + { + 1 => Format.R32Sfloat, + 2 => Format.R32G32Sfloat, + 3 => Format.R32G32B32Sfloat, + 4 => Format.R32G32B32A32Sfloat, + _ => Format.R32Sfloat, + }; + + private static ulong GetVertexBindingOffset(VertexBufferResource vertexBuffer) + { + if (vertexBuffer.OffsetBytes < vertexBuffer.Size) + { + return vertexBuffer.OffsetBytes; + } + + TraceVulkanShader( + $"vk.vertex_offset_oob loc={vertexBuffer.Location} " + + $"offset={vertexBuffer.OffsetBytes} size={vertexBuffer.Size}"); + return 0; + } + + private static uint GetDrawVertexCount( + uint primitiveType, + uint vertexCount, + VulkanGuestIndexBuffer? indexBuffer) + { + if (primitiveType == GuestPrimitiveRectList && indexBuffer is null) + { + return 4; + } + + return vertexCount; + } + + private static BlendFactor ToVkBlendFactor(uint factor) => + factor switch + { + 0 => BlendFactor.Zero, + 1 => BlendFactor.One, + 2 => BlendFactor.SrcColor, + 3 => BlendFactor.OneMinusSrcColor, + 4 => BlendFactor.SrcAlpha, + 5 => BlendFactor.OneMinusSrcAlpha, + 6 => BlendFactor.DstAlpha, + 7 => BlendFactor.OneMinusDstAlpha, + 8 => BlendFactor.DstColor, + 9 => BlendFactor.OneMinusDstColor, + 10 => BlendFactor.SrcAlphaSaturate, + 13 => BlendFactor.ConstantColor, + 14 => BlendFactor.OneMinusConstantColor, + 15 => BlendFactor.Src1Color, + 16 => BlendFactor.OneMinusSrc1Color, + 17 => BlendFactor.Src1Alpha, + 18 => BlendFactor.OneMinusSrc1Alpha, + 19 => BlendFactor.ConstantAlpha, + 20 => BlendFactor.OneMinusConstantAlpha, + _ => BlendFactor.One, + }; + + private static BlendOp ToVkBlendOp(uint function) => + function switch + { + 0 => BlendOp.Add, + 1 => BlendOp.Subtract, + 2 => BlendOp.Min, + 3 => BlendOp.Max, + 4 => BlendOp.ReverseSubtract, + _ => BlendOp.Add, + }; + + private static uint DecodeSamplerClampX(VulkanGuestSampler sampler) => + sampler.Word0 & 0x7u; + + private static uint DecodeSamplerClampY(VulkanGuestSampler sampler) => + (sampler.Word0 >> 3) & 0x7u; + + private static uint DecodeSamplerClampZ(VulkanGuestSampler sampler) => + (sampler.Word0 >> 6) & 0x7u; + + private static uint DecodeSamplerDepthCompare(VulkanGuestSampler sampler) => + (sampler.Word0 >> 12) & 0x7u; + + private static float DecodeSamplerMinLod(VulkanGuestSampler sampler) => + (sampler.Word1 & 0xFFFu) / 256.0f; + + private static float DecodeSamplerMaxLod(VulkanGuestSampler sampler) => + ((sampler.Word1 >> 12) & 0xFFFu) / 256.0f; + + private static float DecodeSamplerLodBias(VulkanGuestSampler sampler) + { + var raw = sampler.Word2 & 0x3FFFu; + var signed = (short)((raw ^ 0x2000u) - 0x2000u); + return signed / 256.0f; + } + + private static uint DecodeSamplerMagFilter(VulkanGuestSampler sampler) => + (sampler.Word2 >> 20) & 0x3u; + + private static uint DecodeSamplerMinFilter(VulkanGuestSampler sampler) => + (sampler.Word2 >> 22) & 0x3u; + + private static uint DecodeSamplerMipFilter(VulkanGuestSampler sampler) => + (sampler.Word2 >> 26) & 0x3u; + + private static uint DecodeSamplerBorderColor(VulkanGuestSampler sampler) => + (sampler.Word3 >> 30) & 0x3u; + + private static SamplerAddressMode ToVkSamplerAddressMode(uint mode) => + mode switch + { + 0 => SamplerAddressMode.Repeat, + 1 => SamplerAddressMode.MirroredRepeat, + 2 => SamplerAddressMode.ClampToEdge, + 3 or 5 or 7 => SamplerAddressMode.MirrorClampToEdge, + 4 or 6 => SamplerAddressMode.ClampToBorder, + _ => SamplerAddressMode.ClampToEdge, + }; + + private static Filter ToVkFilter(uint filter) => + filter is 1 or 3 ? Filter.Linear : Filter.Nearest; + + private static SamplerMipmapMode ToVkMipFilter(uint filter) => + filter == 2 ? SamplerMipmapMode.Linear : SamplerMipmapMode.Nearest; + + private static CompareOp ToVkCompareOp(uint compare) => + compare switch + { + 1 => CompareOp.Less, + 2 => CompareOp.Equal, + 3 => CompareOp.LessOrEqual, + 4 => CompareOp.Greater, + 5 => CompareOp.NotEqual, + 6 => CompareOp.GreaterOrEqual, + 7 => CompareOp.Always, + _ => CompareOp.Never, + }; + + private static BorderColor ToVkBorderColor(uint color) => + color switch + { + 1 => BorderColor.FloatTransparentBlack, + 2 => BorderColor.FloatOpaqueWhite, + _ => BorderColor.FloatOpaqueBlack, + }; + + private static ColorComponentFlags ToVkColorWriteMask(uint mask) + { + var flags = default(ColorComponentFlags); + if ((mask & 1u) != 0) + { + flags |= ColorComponentFlags.RBit; + } + + if ((mask & 2u) != 0) + { + flags |= ColorComponentFlags.GBit; + } + + if ((mask & 4u) != 0) + { + flags |= ColorComponentFlags.BBit; + } + + if ((mask & 8u) != 0) + { + flags |= ColorComponentFlags.ABit; + } + + return flags; + } + + private static VulkanGuestRect ClampScissor(VulkanGuestRect? scissor, Extent2D extent) + { + if (scissor is not { } rect) + { + return new VulkanGuestRect(0, 0, extent.Width, extent.Height); + } + + var left = Math.Clamp(rect.X, 0, checked((int)extent.Width)); + var top = Math.Clamp(rect.Y, 0, checked((int)extent.Height)); + var right = Math.Clamp( + rect.X + checked((int)rect.Width), + left, + checked((int)extent.Width)); + var bottom = Math.Clamp( + rect.Y + checked((int)rect.Height), + top, + checked((int)extent.Height)); + return new VulkanGuestRect( + left, + top, + checked((uint)(right - left)), + checked((uint)(bottom - top))); + } + + private static Viewport ClampViewport(VulkanGuestViewport? viewport, Extent2D extent) + { + if (viewport is not { } rect) + { + return new Viewport(0, 0, extent.Width, extent.Height, 0, 1); + } + + var maxX = (float)extent.Width; + var maxY = (float)extent.Height; + var left = Math.Clamp(rect.X, 0f, maxX); + var right = Math.Clamp(rect.X + rect.Width, left, maxX); + var yOrigin = Math.Clamp(rect.Y, 0f, maxY); + var yEnd = Math.Clamp(rect.Y + rect.Height, 0f, maxY); + var minDepth = Math.Clamp(rect.MinDepth, 0f, 1f); + var maxDepth = Math.Clamp(rect.MaxDepth, minDepth, 1f); + return new Viewport( + left, + yOrigin, + right - left, + yEnd - yOrigin, + minDepth, + maxDepth); + } + private static byte[] CreateFallbackTexturePixels(uint format, uint width, uint height, ulong expectedSize) { if (format is 9 or 10 or 56 or 62 or 64) @@ -3251,7 +3724,7 @@ internal static unsafe class VulkanVideoPresenter { Format = format, Samples = SampleCountFlags.Count1Bit, - LoadOp = AttachmentLoadOp.DontCare, + LoadOp = AttachmentLoadOp.Load, StoreOp = AttachmentStoreOp.Store, StencilLoadOp = AttachmentLoadOp.DontCare, StencilStoreOp = AttachmentStoreOp.DontCare, @@ -3410,11 +3883,12 @@ internal static unsafe class VulkanVideoPresenter Format format, uint mipLevel, uint levelCount, + uint dstSelect, out ImageView view) { try { - view = GetOrCreateGuestImageView(resource, format, mipLevel, levelCount); + view = GetOrCreateGuestImageView(resource, format, mipLevel, levelCount, dstSelect); return true; } catch (Exception exception) @@ -3431,7 +3905,8 @@ internal static unsafe class VulkanVideoPresenter GuestImageResource resource, Format format, uint mipLevel, - uint levelCount) + uint levelCount, + uint dstSelect = 0xFAC) { if (mipLevel >= resource.MipLevels) { @@ -3441,7 +3916,7 @@ internal static unsafe class VulkanVideoPresenter levelCount = Math.Max(levelCount, 1); levelCount = Math.Min(levelCount, resource.MipLevels - mipLevel); - if (format == resource.Format) + if (format == resource.Format && dstSelect == 0xFAC) { if (mipLevel == 0 && levelCount == resource.MipLevels) { @@ -3460,7 +3935,7 @@ internal static unsafe class VulkanVideoPresenter $"Incompatible image view format {format} for image {resource.Format}."); } - var key = (format, mipLevel, levelCount); + var key = (format, mipLevel, levelCount, dstSelect); if (resource.FormatViews.TryGetValue(key, out var existing)) { return existing; @@ -3472,11 +3947,7 @@ internal static unsafe class VulkanVideoPresenter Image = resource.Image, ViewType = ImageViewType.Type2D, Format = format, - Components = new ComponentMapping( - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity, - ComponentSwizzle.Identity), + Components = ToVkComponentMapping(dstSelect), SubresourceRange = ColorSubresourceRange(mipLevel, levelCount), }; ImageView view; @@ -3491,7 +3962,7 @@ internal static unsafe class VulkanVideoPresenter TraceVulkanShader( $"vk.texture_alias_view addr=0x{resource.Address:X16} " + $"image_format={resource.Format} view_format={format} " + - $"mip={mipLevel} levels={levelCount}"); + $"mip={mipLevel} levels={levelCount} dst=0x{dstSelect:X3}"); return view; } @@ -3946,6 +4417,7 @@ internal static unsafe class VulkanVideoPresenter $"nonzero_bytes={nonzeroBytes}/{byteCount} " + $"nonblack_pixels={nonblackPixels}/{(ulong)image.Width * image.Height} " + $"center={center} hash=0x{hash:X16}"); + DumpGuestImageBytes(image, bytes); } finally { @@ -3959,6 +4431,24 @@ internal static unsafe class VulkanVideoPresenter } } + private static void DumpGuestImageBytes( + GuestImageResource image, + ReadOnlySpan bytes) + { + var directory = + Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_DUMP_DIR"); + if (string.IsNullOrWhiteSpace(directory)) + { + return; + } + + Directory.CreateDirectory(directory); + var path = Path.Combine( + directory, + $"0x{image.Address:X16}-{image.Width}x{image.Height}-{image.Format}.rgba"); + File.WriteAllBytes(path, bytes.ToArray()); + } + private static uint GetReadbackBytesPerPixel(Format format) => format switch { @@ -4066,6 +4556,9 @@ internal static unsafe class VulkanVideoPresenter var copyRegion = new BufferImageCopy { + BufferRowLength = texture.RowLength > texture.Width + ? texture.RowLength + : 0, ImageSubresource = new ImageSubresourceLayers { AspectMask = ImageAspectFlags.ColorBit, @@ -4342,16 +4835,47 @@ internal static unsafe class VulkanVideoPresenter null); } + var drawScissor = ClampScissor(resources.Scissor, extent); + if (drawScissor.Width == 0 || drawScissor.Height == 0) + { + _vk.CmdEndRenderPass(_commandBuffer); + return; + } + + var drawViewport = ClampViewport(resources.Viewport, extent); + _vk.CmdSetViewport(_commandBuffer, 0, 1, &drawViewport); + if (resources.VertexBuffers.Length != 0) + { + var buffers = stackalloc VkBuffer[resources.VertexBuffers.Length]; + var offsets = stackalloc ulong[resources.VertexBuffers.Length]; + for (var index = 0; index < resources.VertexBuffers.Length; index++) + { + buffers[index] = resources.VertexBuffers[index].Buffer; + offsets[index] = GetVertexBindingOffset(resources.VertexBuffers[index]); + } + + _vk.CmdBindVertexBuffers( + _commandBuffer, + 0, + (uint)resources.VertexBuffers.Length, + buffers, + offsets); + } + const uint maxPixelsPerDraw = 512 * 512; var rowsPerDraw = Math.Max( 1u, - Math.Min(extent.Height, maxPixelsPerDraw / Math.Max(extent.Width, 1u))); + Math.Min(drawScissor.Height, maxPixelsPerDraw / Math.Max(drawScissor.Width, 1u))); var drawCount = 0u; - for (var y = 0u; y < extent.Height; y += rowsPerDraw) + for (var y = 0u; y < drawScissor.Height; y += rowsPerDraw) { var scissor = new Rect2D( - new Offset2D(0, checked((int)y)), - new Extent2D(extent.Width, Math.Min(rowsPerDraw, extent.Height - y))); + new Offset2D( + drawScissor.X, + checked(drawScissor.Y + (int)y)), + new Extent2D( + drawScissor.Width, + Math.Min(rowsPerDraw, drawScissor.Height - y))); _vk.CmdSetScissor(_commandBuffer, 0, 1, &scissor); if (resources.IndexBuffer.Handle != 0) @@ -4386,7 +4910,11 @@ internal static unsafe class VulkanVideoPresenter { TraceVulkanShader( $"vk.graphics_chunked target={extent.Width}x{extent.Height} " + - $"draws={drawCount} rows={rowsPerDraw} name={resources.DebugName}"); + $"draws={drawCount} rows={rowsPerDraw} " + + $"scissor={drawScissor.X},{drawScissor.Y},{drawScissor.Width}x{drawScissor.Height} " + + $"viewport={drawViewport.X:0.###},{drawViewport.Y:0.###}," + + $"{drawViewport.Width:0.###}x{drawViewport.Height:0.###} " + + $"name={resources.DebugName}"); } _vk.CmdEndRenderPass(_commandBuffer); } @@ -4425,6 +4953,11 @@ internal static unsafe class VulkanVideoPresenter _vk.FreeMemory(_device, texture.StagingMemory, null); } + if (texture.Sampler.Handle != 0) + { + _vk.DestroySampler(_device, texture.Sampler, null); + } + if (texture.NeedsUpload && texture.GuestImage is { Initialized: false } guestImage) { @@ -4450,6 +4983,24 @@ internal static unsafe class VulkanVideoPresenter } } + foreach (var vertexBuffer in resources.VertexBuffers) + { + if (vertexBuffer is null) + { + continue; + } + + if (vertexBuffer.Buffer.Handle != 0) + { + _vk.DestroyBuffer(_device, vertexBuffer.Buffer, null); + } + + if (vertexBuffer.Memory.Handle != 0) + { + _vk.FreeMemory(_device, vertexBuffer.Memory, null); + } + } + if (resources.IndexBuffer.Handle != 0) { _vk.DestroyBuffer(_device, resources.IndexBuffer, null); @@ -4470,11 +5021,6 @@ internal static unsafe class VulkanVideoPresenter _vk.DestroyDescriptorPool(_device, resources.DescriptorPool, null); } - if (resources.Sampler.Handle != 0) - { - _vk.DestroySampler(_device, resources.Sampler, null); - } - if (resources.PipelineLayout.Handle != 0) { _vk.DestroyPipelineLayout(_device, resources.PipelineLayout, null); @@ -4565,10 +5111,10 @@ internal static unsafe class VulkanVideoPresenter { var fallbackWidth = _extent.Width != 0 ? _extent.Width - : Math.Max(_windowWidth, 1u); + : DefaultWindowWidth; var fallbackHeight = _extent.Height != 0 ? _extent.Height - : Math.Max(_windowHeight, 1u); + : DefaultWindowHeight; return new Extent2D( ClampSurfaceExtent( capabilities.CurrentExtent.Width, @@ -4586,12 +5132,12 @@ internal static unsafe class VulkanVideoPresenter return new Extent2D( ClampSurfaceExtent( (uint)Math.Max(size.X, 1), - Math.Max(_windowWidth, 1u), + DefaultWindowWidth, capabilities.MinImageExtent.Width, capabilities.MaxImageExtent.Width), ClampSurfaceExtent( (uint)Math.Max(size.Y, 1), - Math.Max(_windowHeight, 1u), + DefaultWindowHeight, capabilities.MinImageExtent.Height, capabilities.MaxImageExtent.Height)); }