From aaed66bf839a49f279770576926f39668022b426 Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 4 Oct 2026 07:12:58 +0200 Subject: [PATCH] [Agc] Emit addressed draw events for occlusion query counters --- src/SharpEmu.Libs/Agc/AgcExports.Events.cs | 18 ++++- .../Agc/AgcEventWriteTests.cs | 77 +++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 tests/SharpEmu.Libs.Tests/Agc/AgcEventWriteTests.cs diff --git a/src/SharpEmu.Libs/Agc/AgcExports.Events.cs b/src/SharpEmu.Libs/Agc/AgcExports.Events.cs index d21a8b6..2637559 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.Events.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.Events.cs @@ -64,14 +64,24 @@ public static partial class AgcExports var commandBufferAddress = ctx[CpuRegister.Rdi]; var eventType = (uint)(ctx[CpuRegister.Rsi] & 0xFF); var eventAddress = ctx[CpuRegister.Rdx]; - if (commandBufferAddress == 0 || eventType > 0x3F || eventAddress != 0) + if (commandBufferAddress == 0 || eventType > 0x3F || (eventAddress != 0 && (eventType & ~1u) != 0x38)) { return ReturnPointer(ctx, 0); } - if (!TryAllocateCommandDwords(ctx, commandBufferAddress, 2, out var commandAddress) || - !TryWriteUInt32(ctx, commandAddress, Pm4(2, ItEventWrite, 0)) || - !TryWriteUInt32(ctx, commandAddress + 4, eventType)) + // Counter selection and counter dumps carry a payload in the address fields. + var hasAddress = (eventType & ~1u) == 0x38; + var packetDwords = hasAddress ? 4u : 2u; + if (!TryAllocateCommandDwords(ctx, commandBufferAddress, packetDwords, out var commandAddress) || + !TryWriteUInt32(ctx, commandAddress, Pm4(packetDwords, ItEventWrite, 0)) || + !TryWriteUInt32(ctx, commandAddress + 4, hasAddress ? eventType | 0x100u : eventType)) + { + return ReturnPointer(ctx, 0); + } + + if (hasAddress && + (!TryWriteUInt32(ctx, commandAddress + 8, (uint)eventAddress & ~7u) || + !TryWriteUInt32(ctx, commandAddress + 12, (uint)(eventAddress >> 32)))) { return ReturnPointer(ctx, 0); } diff --git a/tests/SharpEmu.Libs.Tests/Agc/AgcEventWriteTests.cs b/tests/SharpEmu.Libs.Tests/Agc/AgcEventWriteTests.cs new file mode 100644 index 0000000..5e7a3cb --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Agc/AgcEventWriteTests.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using SharpEmu.HLE; +using SharpEmu.Libs.Agc; +using SharpEmu.Libs.Gpu.GpuCommands; +using SharpEmu.Libs.Tests.Gpu.GpuCommands; +using Xunit; + +namespace SharpEmu.Libs.Tests.Agc; + +public sealed class AgcEventWriteTests +{ + [Fact] + public void DrawCounterDumpsPublishBothHalvesOfQueryResults() + { + var runner = new StreamRunner(); + var begin = Build(0x39, StreamRunner.LabelAddress); + var end = Build(0x39, StreamRunner.LabelAddress + 8); + + Assert.Equal(0x139u, begin[1]); + Assert.Equal(SubmissionProgress.Complete, runner.Run(begin, end)); + for (var block = 0u; block < 16; block++) + { + var address = StreamRunner.LabelAddress + block * 16; + var first = runner.Host.ReadQword(address); + var last = runner.Host.ReadQword(address + 8); + Assert.NotEqual(0UL, first & last & (1UL << 63)); + Assert.True(last > first); + } + } + + [Theory] + [InlineData(0x38u, 0x1234567800UL, 4)] + [InlineData(0x39u, 0x1234567800UL, 4)] + [InlineData(0x3Au, 0UL, 2)] + [InlineData(0x10u, 0UL, 2)] + public void DrawEventsPreservePayloadAndAdvanceCursor(uint eventType, ulong payload, int words) + { + var packet = Build(eventType, payload); + Assert.Equal(words, packet.Length); + Assert.Equal(eventType | (words == 4 ? 0x100u : 0), packet[1]); + if (words == 4) + { + Assert.Equal((uint)payload, packet[2]); + Assert.Equal((uint)(payload >> 32), packet[3]); + } + } + + private static uint[] Build(uint eventType, ulong payload) + { + const ulong memoryAddress = 0x2_1000_0000; + const ulong commandBuffer = memoryAddress + 0x80; + const ulong packetAddress = memoryAddress + 0x200; + var memory = new FakeCpuMemory(memoryAddress, 0x1000); + var context = new CpuContext(memory, Generation.Gen5); + Span bytes = stackalloc byte[16]; + BinaryPrimitives.WriteUInt64LittleEndian(bytes, packetAddress); + BinaryPrimitives.WriteUInt64LittleEndian(bytes[8..], packetAddress + 0x100); + Assert.True(memory.TryWrite(commandBuffer + 0x10, bytes)); + context[CpuRegister.Rdi] = commandBuffer; + context[CpuRegister.Rsi] = eventType; + context[CpuRegister.Rdx] = payload; + + AgcExports.DcbEventWrite(context); + Assert.Equal(packetAddress, context[CpuRegister.Rax]); + Assert.True(memory.TryRead(commandBuffer + 0x10, bytes[..8])); + var length = checked((int)(BinaryPrimitives.ReadUInt64LittleEndian(bytes) - packetAddress)); + Assert.InRange(length, 8, 16); + Assert.True(memory.TryRead(packetAddress, bytes[..length])); + var packet = new uint[length / 4]; + for (var i = 0; i < packet.Length; i++) + packet[i] = BinaryPrimitives.ReadUInt32LittleEndian(bytes[(i * 4)..]); + return packet; + } +}