using Ryujinx.Graphics.GAL; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// Semaphore updater. /// class SemaphoreUpdater { /// /// GPU semaphore operation. /// private enum SemaphoreOperation { Release = 0, Acquire = 1, Counter = 2, } /// /// Counter type for GPU counter reset. /// private enum ResetCounterType { SamplesPassed = 1, ZcullStats = 2, TransformFeedbackPrimitivesWritten = 0x10, InputVertices = 0x12, InputPrimitives = 0x13, VertexShaderInvocations = 0x15, TessControlShaderInvocations = 0x16, TessEvaluationShaderInvocations = 0x17, TessEvaluationShaderPrimitives = 0x18, GeometryShaderInvocations = 0x1a, GeometryShaderPrimitives = 0x1b, ClipperInputPrimitives = 0x1c, ClipperOutputPrimitives = 0x1d, FragmentShaderInvocations = 0x1e, PrimitivesGenerated = 0x1f, } /// /// Counter type for GPU counter reporting. /// private enum ReportCounterType { Payload = 0, InputVertices = 1, InputPrimitives = 3, VertexShaderInvocations = 5, GeometryShaderInvocations = 7, GeometryShaderPrimitives = 9, ZcullStats0 = 0xa, TransformFeedbackPrimitivesWritten = 0xb, ZcullStats1 = 0xc, ZcullStats2 = 0xe, ClipperInputPrimitives = 0xf, ZcullStats3 = 0x10, ClipperOutputPrimitives = 0x11, PrimitivesGenerated = 0x12, FragmentShaderInvocations = 0x13, SamplesPassed = 0x15, TransformFeedbackOffset = 0x1a, TessControlShaderInvocations = 0x1b, TessEvaluationShaderInvocations = 0x1d, TessEvaluationShaderPrimitives = 0x1f, } private readonly GpuContext _context; private readonly GpuChannel _channel; private readonly DeviceStateWithShadow _state; /// /// Creates a new instance of the semaphore updater. /// /// GPU context /// GPU channel /// Channel state public SemaphoreUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow state) { _context = context; _channel = channel; _state = state; } /// /// Resets the value of an internal GPU counter back to zero. /// /// Method call argument public void ResetCounter(int argument) { ResetCounterType type = (ResetCounterType)argument; switch (type) { case ResetCounterType.SamplesPassed: _context.Renderer.ResetCounter(CounterType.SamplesPassed); break; case ResetCounterType.PrimitivesGenerated: _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated); break; case ResetCounterType.TransformFeedbackPrimitivesWritten: _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten); break; } } /// /// Writes a GPU counter to guest memory. /// /// Method call argument public void Report(int argument) { SemaphoreOperation op = (SemaphoreOperation)(argument & 3); ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f); switch (op) { case SemaphoreOperation.Release: ReleaseSemaphore(); break; case SemaphoreOperation.Counter: ReportCounter(type); break; } } /// /// Writes (or Releases) a GPU semaphore value to guest memory. /// private void ReleaseSemaphore() { _channel.MemoryManager.Write(_state.State.SemaphoreAddress.Pack(), _state.State.SemaphorePayload); _context.AdvanceSequence(); } /// /// Packed GPU counter data (including GPU timestamp) in memory. /// private struct CounterData { public ulong Counter; public ulong Timestamp; } /// /// Writes a GPU counter to guest memory. /// This also writes the current timestamp value. /// /// Counter to be written to memory private static int _fractProbeCount; private void ReportCounter(ReportCounterType type) { ulong gpuVa = _state.State.SemaphoreAddress.Pack(); ulong ticks = _context.GetTimestamp(); ICounterEvent counter = null; void resultHandler(object evt, ulong result) { CounterData counterData = new() { Counter = result, Timestamp = ticks, }; if (counter?.Invalid != true) { _channel.MemoryManager.Write(gpuVa, counterData); } } switch (type) { case ReportCounterType.Payload: resultHandler(null, (ulong)_state.State.SemaphorePayload); break; case ReportCounterType.SamplesPassed: float scale = _channel.TextureManager.RenderTargetScale; float divisor = scale * scale; // Fractional-crash probe (quality-mode dossier): sample counts are divided // by scale^2 before the game reads them -- under a FRACTIONAL scale the // divisor itself is fractional (1.333^2 = 1.777...), a value regime the // integer daily never exercises. Sampled 1/32 to name the family without // flooding; silent at integer scales. // [31/07] GATE AJOUTE. Elle etait en release SANS aucun interrupteur : la // condition « echelle fractionnaire » est remplie par TOUT utilisateur en DLSS // quality ou performance (DlssIntegration pilote GraphicsConfig.ResScale ; // 4K + quality = 1440/1080 = 1,334). Mesure du 31/07 sur GYLT : 21 471 lignes // en 3 minutes -- soit ~690 000 requetes de compteur -- chez quelqu'un qui // n'avait aucun probleme a diagnostiquer. La sonde rend son service UNE fois // par rapport de bug ; elle ne doit pas ecrire le reste du temps. if (GAL.MvppDev.Enabled && scale != System.MathF.Floor(scale) && (_fractProbeCount++ & 31) == 0) { Ryujinx.Common.Logging.Logger.Info?.Print(Ryujinx.Common.Logging.LogClass.Gpu, $"MVPP fract-probe: samples query #{_fractProbeCount - 1}, divisor {divisor} (scale {scale}), gpuVa 0x{gpuVa:X}."); } counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, divisor, false); break; case ReportCounterType.PrimitivesGenerated: counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler, 1f, false); break; case ReportCounterType.TransformFeedbackPrimitivesWritten: counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler, 1f, false); break; } _channel.MemoryManager.CounterCache.AddOrUpdate(gpuVa, counter); } } }