Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4e3a34412e | ||
|
3f4fb8f73a | ||
|
56c56aa34d | ||
|
d4b960d348 | ||
|
b2a225558d | ||
|
0ef0fc044a | ||
|
04bd87ed5a | ||
|
5158cdb308 | ||
|
1402d8391d |
9
.github/workflows/nightly_pr_comment.yml
vendored
9
.github/workflows/nightly_pr_comment.yml
vendored
@@ -36,15 +36,20 @@ jobs:
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
if(art.name.includes('Debug')){
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}else{
|
||||
} else if(art.name.includes('headless-sdl2')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else {
|
||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}
|
||||
}
|
||||
hidden_headless_artifacts += `\n</details>`;
|
||||
hidden_debug_artifacts += `\n</details>`;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});
|
||||
|
@@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
NodeId = nodeId;
|
||||
@@ -63,6 +63,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update delay processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
@@ -63,7 +63,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private Reverb3dParameter _parameter;
|
||||
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId)
|
||||
public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
@@ -80,6 +80,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]);
|
||||
OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]);
|
||||
}
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -194,7 +199,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.BackLeftDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
*((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -66,7 +66,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
private const int FixedPointPrecision = 14;
|
||||
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Enabled = true;
|
||||
IsEffectEnabled = isEnabled;
|
||||
@@ -85,6 +85,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
}
|
||||
|
||||
IsLongSizePreDelaySupported = isLongSizePreDelaySupported;
|
||||
|
||||
// NOTE: We do the opposite as Nintendo here for now to restore previous behaviour
|
||||
// TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping.
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices);
|
||||
DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -214,7 +219,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
|
||||
if (isSurround)
|
||||
{
|
||||
outputValues[4] += state.BackLeftDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f);
|
||||
}
|
||||
|
||||
for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++)
|
||||
|
@@ -445,5 +445,39 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
ToIntSlow(output, input, sampleCount);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (!isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort backLeft = bufferIndices[2];
|
||||
ushort backRight = bufferIndices[3];
|
||||
ushort frontCenter = bufferIndices[4];
|
||||
ushort lowFrequency = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = frontCenter;
|
||||
bufferIndices[3] = lowFrequency;
|
||||
bufferIndices[4] = backLeft;
|
||||
bufferIndices[5] = backRight;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span<ushort> bufferIndices)
|
||||
{
|
||||
if (isSupported && bufferIndices.Length == 6)
|
||||
{
|
||||
ushort frontCenter = bufferIndices[2];
|
||||
ushort lowFrequency = bufferIndices[3];
|
||||
ushort backLeft = bufferIndices[4];
|
||||
ushort backRight = bufferIndices[5];
|
||||
|
||||
bufferIndices[2] = backLeft;
|
||||
bufferIndices[3] = backRight;
|
||||
bufferIndices[4] = frontCenter;
|
||||
bufferIndices[5] = lowFrequency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
public DecayDelay[] DecayDelays1 { get; }
|
||||
public DecayDelay[] DecayDelays2 { get; }
|
||||
public IDelayLine PreDelayLine { get; }
|
||||
public IDelayLine BackLeftDelayLine { get; }
|
||||
public IDelayLine FrontCenterDelayLine { get; }
|
||||
public float DryGain { get; private set; }
|
||||
public uint[] EarlyDelayTime { get; private set; }
|
||||
public float PreviousPreDelayValue { get; set; }
|
||||
@@ -69,7 +69,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine3d(sampleRate, 400);
|
||||
BackLeftDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
FrontCenterDelayLine = new DelayLine3d(sampleRate, 5);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
@@ -97,7 +97,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
public DelayLine[] FdnDelayLines { get; }
|
||||
public DecayDelay[] DecayDelays { get; }
|
||||
public DelayLine PreDelayLine { get; }
|
||||
public DelayLine BackLeftDelayLine { get; }
|
||||
public DelayLine FrontCenterDelayLine { get; }
|
||||
public uint[] EarlyDelayTime { get; }
|
||||
public float[] EarlyGain { get; }
|
||||
public uint PreDelayLineDelayTime { get; private set; }
|
||||
@@ -149,7 +149,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State
|
||||
}
|
||||
|
||||
PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax);
|
||||
BackLeftDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f);
|
||||
|
||||
UpdateParameter(ref parameter);
|
||||
}
|
||||
|
@@ -363,6 +363,9 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
case 4:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
case 5:
|
||||
_commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}.");
|
||||
}
|
||||
|
@@ -107,10 +107,18 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <remarks>This was added in system update 13.0.0</remarks>
|
||||
public const int Revision10 = 10 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// REV11:
|
||||
/// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer.
|
||||
/// A new version of the command estimator was added to address timing changes caused by the legacy effects changes.
|
||||
/// </summary>
|
||||
/// <remarks>This was added in system update 14.0.0</remarks>
|
||||
public const int Revision11 = 11 << 24;
|
||||
|
||||
/// <summary>
|
||||
/// Last revision supported by the implementation.
|
||||
/// </summary>
|
||||
public const int LastRevision = Revision10;
|
||||
public const int LastRevision = Revision11;
|
||||
|
||||
/// <summary>
|
||||
/// Target revision magic supported by the implementation.
|
||||
@@ -366,12 +374,26 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects.
|
||||
/// </summary>
|
||||
/// <returns>True if the audio renderer support new channel resource mapping for 5.1.</returns>
|
||||
public bool IsNewEffectChannelMappingSupported()
|
||||
{
|
||||
return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the version of the <see cref="ICommandProcessingTimeEstimator"/>.
|
||||
/// </summary>
|
||||
/// <returns>The version of the <see cref="ICommandProcessingTimeEstimator"/>.</returns>
|
||||
public int GetCommandProcessingTimeEstimatorVersion()
|
||||
{
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11))
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10))
|
||||
{
|
||||
return 4;
|
||||
|
@@ -336,11 +336,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
/// <param name="isLongSizePreDelaySupported">If set to true, the long size pre-delay is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory<ReverbState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
@@ -357,11 +358,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory<Reverb3dState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
@@ -379,11 +381,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <param name="isEnabled">Set to true if the effect should be active.</param>
|
||||
/// <param name="workBuffer">The work buffer to use for processing.</param>
|
||||
/// <param name="nodeId">The node id associated to this command.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId)
|
||||
/// <param name="newEffectChannelMappingSupported">If set to true, the new effect channel mapping for 5.1 is supported.</param>
|
||||
public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory<DelayState> state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
if (parameter.IsChannelCountValid())
|
||||
{
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId);
|
||||
DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
|
||||
command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command);
|
||||
|
||||
|
@@ -483,31 +483,31 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId)
|
||||
private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Delay);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported)
|
||||
private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported);
|
||||
_commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId)
|
||||
private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported)
|
||||
{
|
||||
Debug.Assert(effect.Type == EffectType.Reverb3d);
|
||||
|
||||
ulong workBuffer = effect.GetWorkBuffer(-1);
|
||||
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId);
|
||||
_commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported);
|
||||
}
|
||||
|
||||
private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId)
|
||||
@@ -650,13 +650,13 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId);
|
||||
break;
|
||||
case EffectType.Delay:
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId);
|
||||
GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb:
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported);
|
||||
GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.Reverb3d:
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId);
|
||||
GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported());
|
||||
break;
|
||||
case EffectType.BiquadFilter:
|
||||
GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId);
|
||||
|
@@ -198,7 +198,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return (uint)1853.2f;
|
||||
}
|
||||
|
||||
public uint Estimate(DelayCommand command)
|
||||
public virtual uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
@@ -272,7 +272,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(ReverbCommand command)
|
||||
public virtual uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
@@ -346,7 +346,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
}
|
||||
|
||||
public uint Estimate(Reverb3dCommand command)
|
||||
public virtual uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
|
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// Copyright (c) 2019-2022 Ryujinx
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
using Ryujinx.Audio.Renderer.Dsp.Command;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ICommandProcessingTimeEstimator"/> version 5. (added with REV11)
|
||||
/// </summary>
|
||||
public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4
|
||||
{
|
||||
public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { }
|
||||
|
||||
public override uint Estimate(DelayCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 8929;
|
||||
case 2:
|
||||
return 25501;
|
||||
case 4:
|
||||
return 47760;
|
||||
case 6:
|
||||
return 82203;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)1295.20f;
|
||||
case 2:
|
||||
return (uint)1213.60f;
|
||||
case 4:
|
||||
return (uint)942.03f;
|
||||
case 6:
|
||||
return (uint)1001.6f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 11941;
|
||||
case 2:
|
||||
return 37197;
|
||||
case 4:
|
||||
return 69750;
|
||||
case 6:
|
||||
return 12004;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)997.67f;
|
||||
case 2:
|
||||
return (uint)977.63f;
|
||||
case 4:
|
||||
return (uint)792.31f;
|
||||
case 6:
|
||||
return (uint)875.43f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(ReverbCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 81475;
|
||||
case 2:
|
||||
return 84975;
|
||||
case 4:
|
||||
return 91625;
|
||||
case 6:
|
||||
return 95332;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)536.30f;
|
||||
case 2:
|
||||
return (uint)588.80f;
|
||||
case 4:
|
||||
return (uint)643.70f;
|
||||
case 6:
|
||||
return (uint)706.0f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 120170;
|
||||
case 2:
|
||||
return 125260;
|
||||
case 4:
|
||||
return 135750;
|
||||
case 6:
|
||||
return 141130;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)617.64f;
|
||||
case 2:
|
||||
return (uint)659.54f;
|
||||
case 4:
|
||||
return (uint)711.44f;
|
||||
case 6:
|
||||
return (uint)778.07f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint Estimate(Reverb3dCommand command)
|
||||
{
|
||||
Debug.Assert(_sampleCount == 160 || _sampleCount == 240);
|
||||
|
||||
if (_sampleCount == 160)
|
||||
{
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 116750;
|
||||
case 2:
|
||||
return 125910;
|
||||
case 4:
|
||||
return 146340;
|
||||
case 6:
|
||||
return 165810;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 735;
|
||||
case 2:
|
||||
return (uint)766.62f;
|
||||
case 4:
|
||||
return (uint)834.07f;
|
||||
case 6:
|
||||
return (uint)875.44f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (command.Enabled)
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return 170290;
|
||||
case 2:
|
||||
return 183880;
|
||||
case 4:
|
||||
return 214700;
|
||||
case 6:
|
||||
return 243850;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (command.Parameter.ChannelCount)
|
||||
{
|
||||
case 1:
|
||||
return (uint)508.47f;
|
||||
case 2:
|
||||
return (uint)582.45f;
|
||||
case 4:
|
||||
return (uint)626.42f;
|
||||
case 6:
|
||||
return (uint)682.47f;
|
||||
default:
|
||||
throw new NotImplementedException($"{command.Parameter.ChannelCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public struct DepthStencilState
|
||||
{
|
||||
public bool DepthTestEnable { get; }
|
||||
public bool DepthWriteEnable { get; }
|
||||
public bool StencilTestEnable { get; }
|
||||
|
||||
public CompareOp DepthFunc { get; }
|
||||
public CompareOp StencilFrontFunc { get; }
|
||||
public StencilOp StencilFrontSFail { get; }
|
||||
public StencilOp StencilFrontDpPass { get; }
|
||||
public StencilOp StencilFrontDpFail { get; }
|
||||
public CompareOp StencilBackFunc { get; }
|
||||
public StencilOp StencilBackSFail { get; }
|
||||
public StencilOp StencilBackDpPass { get; }
|
||||
public StencilOp StencilBackDpFail { get; }
|
||||
|
||||
public DepthStencilState(
|
||||
bool depthTestEnable,
|
||||
bool depthWriteEnable,
|
||||
bool stencilTestEnable,
|
||||
CompareOp depthFunc,
|
||||
CompareOp stencilFrontFunc,
|
||||
StencilOp stencilFrontSFail,
|
||||
StencilOp stencilFrontDpPass,
|
||||
StencilOp stencilFrontDpFail,
|
||||
CompareOp stencilBackFunc,
|
||||
StencilOp stencilBackSFail,
|
||||
StencilOp stencilBackDpPass,
|
||||
StencilOp stencilBackDpFail)
|
||||
{
|
||||
DepthTestEnable = depthTestEnable;
|
||||
DepthWriteEnable = depthWriteEnable;
|
||||
StencilTestEnable = stencilTestEnable;
|
||||
DepthFunc = depthFunc;
|
||||
StencilFrontFunc = stencilFrontFunc;
|
||||
StencilFrontSFail = stencilFrontSFail;
|
||||
StencilFrontDpPass = stencilFrontDpPass;
|
||||
StencilFrontDpFail = stencilFrontDpFail;
|
||||
StencilBackFunc = stencilBackFunc;
|
||||
StencilBackSFail = stencilBackSFail;
|
||||
StencilBackDpPass = stencilBackDpPass;
|
||||
StencilBackDpFail = stencilBackDpFail;
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,7 +9,6 @@ namespace Ryujinx.Graphics.GAL
|
||||
Texture2DArray,
|
||||
Texture2DMultisample,
|
||||
Texture2DMultisampleArray,
|
||||
Rectangle,
|
||||
Cubemap,
|
||||
CubemapArray,
|
||||
TextureBuffer
|
||||
|
@@ -525,7 +525,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
int scissorW = screenScissorState.Width;
|
||||
int scissorH = screenScissorState.Height;
|
||||
|
||||
if (clearAffectedByScissor)
|
||||
if (clearAffectedByScissor && _state.State.ScissorState[0].Enable)
|
||||
{
|
||||
ref var scissorState = ref _state.State.ScissorState[0];
|
||||
|
||||
|
@@ -20,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public const int RasterizerStateIndex = 1;
|
||||
public const int ScissorStateIndex = 2;
|
||||
public const int VertexBufferStateIndex = 3;
|
||||
public const int PrimitiveRestartStateIndex = 4;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
@@ -75,6 +76,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.VertexBufferState),
|
||||
nameof(ThreedClassState.VertexBufferEndAddress)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState,
|
||||
nameof(ThreedClassState.PrimitiveRestartDrawArrays),
|
||||
nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateTessellationState,
|
||||
nameof(ThreedClassState.TessOuterLevel),
|
||||
nameof(ThreedClassState.TessInnerLevel),
|
||||
@@ -140,8 +145,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
nameof(ThreedClassState.PointSpriteEnable),
|
||||
nameof(ThreedClassState.PointCoordReplace)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdatePrimitiveRestartState, nameof(ThreedClassState.PrimitiveRestartState)),
|
||||
|
||||
new StateUpdateCallbackEntry(UpdateIndexBufferState,
|
||||
nameof(ThreedClassState.IndexBufferState),
|
||||
nameof(ThreedClassState.IndexBufferCount)),
|
||||
@@ -197,6 +200,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
if (_drawState.DrawIndexed != _prevDrawIndexed)
|
||||
{
|
||||
_updateTracker.ForceDirty(VertexBufferStateIndex);
|
||||
|
||||
// If PrimitiveRestartDrawArrays is false and this is a non-indexed draw, we need to ensure primitive restart is disabled.
|
||||
// If PrimitiveRestartDrawArrays is false and this is a indexed draw, we need to ensure primitive restart enable matches GPU state.
|
||||
// If PrimitiveRestartDrawArrays is true, then primitive restart enable should always match GPU state.
|
||||
// That is because "PrimitiveRestartDrawArrays" is not configurable on the backend, it is always
|
||||
// true on OpenGL and always false on Vulkan.
|
||||
if (!_state.State.PrimitiveRestartDrawArrays && _state.State.PrimitiveRestartState.Enable)
|
||||
{
|
||||
_updateTracker.ForceDirty(PrimitiveRestartStateIndex);
|
||||
}
|
||||
|
||||
_prevDrawIndexed = _drawState.DrawIndexed;
|
||||
}
|
||||
|
||||
@@ -816,8 +830,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
private void UpdatePrimitiveRestartState()
|
||||
{
|
||||
PrimitiveRestartState primitiveRestart = _state.State.PrimitiveRestartState;
|
||||
bool enable = primitiveRestart.Enable && (_drawState.DrawIndexed || _state.State.PrimitiveRestartDrawArrays);
|
||||
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(primitiveRestart.Enable, primitiveRestart.Index);
|
||||
_context.Renderer.Pipeline.SetPrimitiveRestart(enable, primitiveRestart.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@@ -730,7 +730,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
public int PatchVertices;
|
||||
public fixed uint ReservedDD0[4];
|
||||
public uint TextureBarrier;
|
||||
public fixed uint ReservedDE4[7];
|
||||
public uint WatchdogTimer;
|
||||
public Boolean32 PrimitiveRestartDrawArrays;
|
||||
public fixed uint ReservedDEC[5];
|
||||
public Array16<ScissorState> ScissorState;
|
||||
public fixed uint ReservedF00[21];
|
||||
public StencilBackMasks StencilBackMasks;
|
||||
|
@@ -255,6 +255,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
|
||||
/// </summary>
|
||||
/// <param name="va">GPU virtual address to write the data into</param>
|
||||
/// <param name="data">The data to be written</param>
|
||||
public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
|
||||
{
|
||||
if (IsContiguous(va, data.Length))
|
||||
{
|
||||
Physical.Write(Translate(va), data);
|
||||
}
|
||||
else
|
||||
{
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = Translate(va);
|
||||
|
||||
size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(0, size));
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = Translate(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, (int)PageSize);
|
||||
|
||||
if (pa != PteUnmapped && Physical.IsMapped(pa))
|
||||
{
|
||||
Physical.Write(pa, data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a given range of pages to the specified CPU virtual address.
|
||||
/// </summary>
|
||||
|
@@ -340,6 +340,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the page at a given address is mapped on CPU memory.
|
||||
/// </summary>
|
||||
/// <param name="address">CPU virtual address of the page to check</param>
|
||||
/// <returns>True if mapped, false otherwise</returns>
|
||||
public bool IsMapped(ulong address)
|
||||
{
|
||||
return _cpuMemory.IsMapped(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Release our reference to the CPU memory manager.
|
||||
/// </summary>
|
||||
|
@@ -15,11 +15,13 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
|
||||
public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight);
|
||||
public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight);
|
||||
|
||||
public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive;
|
||||
|
||||
public int Width => Frame->width;
|
||||
public int Height => Frame->height;
|
||||
public int Stride => Frame->linesize[0];
|
||||
public int UvWidth => (Frame->width + 1) >> 1;
|
||||
public int UvHeight => (Frame->height + 1) >> 1;
|
||||
public int UvWidth => (Width + 1) >> 1;
|
||||
public int UvHeight => (Height + 1) >> 1;
|
||||
public int UvStride => Frame->linesize[1];
|
||||
|
||||
public Surface(int width, int height)
|
||||
|
@@ -486,8 +486,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp
|
||||
Idct8(tempIn, tempOut);
|
||||
for (j = 0; j < 8; ++j)
|
||||
{
|
||||
dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i],
|
||||
BitUtils.RoundPowerOfTwo(tempOut[j], 5));
|
||||
dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
|
||||
public unsafe Plane UPlane => new Plane((IntPtr)UBuffer.ToPointer(), UBuffer.Length);
|
||||
public unsafe Plane VPlane => new Plane((IntPtr)VBuffer.ToPointer(), VBuffer.Length);
|
||||
|
||||
public FrameField Field => FrameField.Progressive;
|
||||
|
||||
public int Width { get; }
|
||||
public int Height { get; }
|
||||
public int AlignedWidth { get; }
|
||||
|
@@ -31,7 +31,24 @@ namespace Ryujinx.Graphics.Nvdec
|
||||
|
||||
if (decoder.Decode(ref info, outputSurface, bitstream))
|
||||
{
|
||||
SurfaceWriter.Write(rm.Gmm, outputSurface, lumaOffset, chromaOffset);
|
||||
if (outputSurface.Field == FrameField.Progressive)
|
||||
{
|
||||
SurfaceWriter.Write(
|
||||
rm.Gmm,
|
||||
outputSurface,
|
||||
lumaOffset + pictureInfo.LumaFrameOffset,
|
||||
chromaOffset + pictureInfo.ChromaFrameOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
SurfaceWriter.WriteInterlaced(
|
||||
rm.Gmm,
|
||||
outputSurface,
|
||||
lumaOffset + pictureInfo.LumaTopFieldOffset,
|
||||
chromaOffset + pictureInfo.ChromaTopFieldOffset,
|
||||
lumaOffset + pictureInfo.LumaBottomFieldOffset,
|
||||
chromaOffset + pictureInfo.ChromaBottomFieldOffset);
|
||||
}
|
||||
}
|
||||
|
||||
rm.Cache.Put(outputSurface);
|
||||
|
@@ -38,6 +38,55 @@ namespace Ryujinx.Graphics.Nvdec.Image
|
||||
surface.UvHeight);
|
||||
}
|
||||
|
||||
public static void WriteInterlaced(
|
||||
MemoryManager gmm,
|
||||
ISurface surface,
|
||||
uint lumaTopOffset,
|
||||
uint chromaTopOffset,
|
||||
uint lumaBottomOffset,
|
||||
uint chromaBottomOffset)
|
||||
{
|
||||
int lumaSize = GetBlockLinearSize(surface.Width, surface.Height / 2, 1);
|
||||
|
||||
using var lumaTop = gmm.GetWritableRegion(ExtendOffset(lumaTopOffset), lumaSize);
|
||||
using var lumaBottom = gmm.GetWritableRegion(ExtendOffset(lumaBottomOffset), lumaSize);
|
||||
|
||||
WriteLuma(
|
||||
lumaTop.Memory.Span,
|
||||
surface.YPlane.AsSpan(),
|
||||
surface.Stride * 2,
|
||||
surface.Width,
|
||||
surface.Height / 2);
|
||||
|
||||
WriteLuma(
|
||||
lumaBottom.Memory.Span,
|
||||
surface.YPlane.AsSpan().Slice(surface.Stride),
|
||||
surface.Stride * 2,
|
||||
surface.Width,
|
||||
surface.Height / 2);
|
||||
|
||||
int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight / 2, 2);
|
||||
|
||||
using var chromaTop = gmm.GetWritableRegion(ExtendOffset(chromaTopOffset), chromaSize);
|
||||
using var chromaBottom = gmm.GetWritableRegion(ExtendOffset(chromaBottomOffset), chromaSize);
|
||||
|
||||
WriteChroma(
|
||||
chromaTop.Memory.Span,
|
||||
surface.UPlane.AsSpan(),
|
||||
surface.VPlane.AsSpan(),
|
||||
surface.UvStride * 2,
|
||||
surface.UvWidth,
|
||||
surface.UvHeight / 2);
|
||||
|
||||
WriteChroma(
|
||||
chromaBottom.Memory.Span,
|
||||
surface.UPlane.AsSpan().Slice(surface.UvStride),
|
||||
surface.VPlane.AsSpan().Slice(surface.UvStride),
|
||||
surface.UvStride * 2,
|
||||
surface.UvWidth,
|
||||
surface.UvHeight / 2);
|
||||
}
|
||||
|
||||
private static void WriteLuma(Span<byte> dst, ReadOnlySpan<byte> src, int srcStride, int width, int height)
|
||||
{
|
||||
LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, srcStride, 1, 2, src);
|
||||
|
@@ -26,10 +26,10 @@ namespace Ryujinx.Graphics.Nvdec.Types.H264
|
||||
public uint Transform8x8ModeFlag;
|
||||
public uint LumaPitch;
|
||||
public uint ChromaPitch;
|
||||
public uint LumaTopOffset;
|
||||
public uint LumaBottomOffset;
|
||||
public uint LumaTopFieldOffset;
|
||||
public uint LumaBottomFieldOffset;
|
||||
public uint LumaFrameOffset;
|
||||
public uint ChromaTopOffset;
|
||||
public uint ChromaTopFieldOffset;
|
||||
public uint ChromaBottomFieldOffset;
|
||||
public uint ChromaFrameOffset;
|
||||
public uint HistBufferSize;
|
||||
|
@@ -444,8 +444,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return TextureTarget.Texture2DArray;
|
||||
case Target.Texture2DMultisample:
|
||||
return TextureTarget.Texture2DMultisample;
|
||||
case Target.Rectangle:
|
||||
return TextureTarget.TextureRectangle;
|
||||
case Target.Texture2DMultisampleArray:
|
||||
return TextureTarget.Texture2DMultisampleArray;
|
||||
case Target.Cubemap:
|
||||
return TextureTarget.TextureCubeMap;
|
||||
case Target.CubemapArray:
|
||||
|
@@ -155,6 +155,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
_pipeline.Initialize(this);
|
||||
_counters.Initialize();
|
||||
|
||||
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
||||
// This call is expected to fail if we're running with a core profile,
|
||||
// as this clamp target was deprecated, but that's fine as a core profile
|
||||
// should already have the desired behaviour were outputs are not clamped.
|
||||
GL.ClampColor(ClampColorTarget.ClampFragmentColor, ClampColorMode.False);
|
||||
}
|
||||
|
||||
private void PrintGpuInformation()
|
||||
|
@@ -70,6 +70,25 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
AppendLine("}" + suffix);
|
||||
}
|
||||
|
||||
public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp)
|
||||
{
|
||||
TextureDescriptor[] descriptors = Config.GetTextureDescriptors();
|
||||
|
||||
for (int i = 0; i < descriptors.Length; i++)
|
||||
{
|
||||
var descriptor = descriptors[i];
|
||||
|
||||
if (descriptor.CbufSlot == texOp.CbufSlot &&
|
||||
descriptor.HandleIndex == texOp.Handle &&
|
||||
descriptor.Format == texOp.Format)
|
||||
{
|
||||
return (descriptor, i);
|
||||
}
|
||||
}
|
||||
|
||||
return (default, -1);
|
||||
}
|
||||
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
|
||||
{
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
|
@@ -756,27 +756,34 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
|
||||
string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);
|
||||
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
IAstNode lod = operation.GetSource(lodSrcIndex);
|
||||
|
||||
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
|
||||
|
||||
if (texOp.Index == 3)
|
||||
{
|
||||
return $"textureQueryLevels({samplerName})";
|
||||
}
|
||||
else
|
||||
{
|
||||
string texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
|
||||
(TextureDescriptor descriptor, int descriptorIndex) = context.FindTextureDescriptor(texOp);
|
||||
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
|
||||
string texCall;
|
||||
|
||||
if (hasLod)
|
||||
{
|
||||
int lodSrcIndex = isBindless || isIndexed ? 1 : 0;
|
||||
IAstNode lod = operation.GetSource(lodSrcIndex);
|
||||
string lodExpr = GetSoureExpr(context, lod, GetSrcVarType(operation.Inst, lodSrcIndex));
|
||||
|
||||
texCall = $"textureSize({samplerName}, {lodExpr}){GetMask(texOp.Index)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
|
||||
}
|
||||
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
int index = context.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
texCall = "Helper_TextureSizeUnscale(" + texCall + ", " + index + ")";
|
||||
texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})";
|
||||
}
|
||||
|
||||
return texCall;
|
||||
|
@@ -48,38 +48,10 @@ namespace Ryujinx.Graphics.Vic
|
||||
|
||||
int one = 1 << (mtx.MatrixRShift + 8);
|
||||
|
||||
|
||||
// NOTE: This is buggy on .NET 5.0.100, we use a workaround for now (see https://github.com/dotnet/runtime/issues/44704)
|
||||
// TODO: Uncomment this when fixed.
|
||||
//Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
|
||||
//Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
|
||||
//Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
|
||||
//Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
|
||||
|
||||
Vector128<int> col1 = new Vector128<int>();
|
||||
Vector128<int> col2 = new Vector128<int>();
|
||||
Vector128<int> col3 = new Vector128<int>();
|
||||
Vector128<int> col4 = new Vector128<int>();
|
||||
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff00, 0);
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff10, 1);
|
||||
col1 = Sse41.Insert(col1, mtx.MatrixCoeff20, 2);
|
||||
col1 = Sse41.Insert(col1, 0, 3);
|
||||
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff01, 0);
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff11, 1);
|
||||
col2 = Sse41.Insert(col2, mtx.MatrixCoeff21, 2);
|
||||
col2 = Sse41.Insert(col2, 0, 3);
|
||||
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff02, 0);
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff12, 1);
|
||||
col3 = Sse41.Insert(col3, mtx.MatrixCoeff22, 2);
|
||||
col3 = Sse41.Insert(col3, one, 3);
|
||||
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff03, 0);
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff13, 1);
|
||||
col4 = Sse41.Insert(col4, mtx.MatrixCoeff23, 2);
|
||||
col4 = Sse41.Insert(col4, 0, 3);
|
||||
Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
|
||||
Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
|
||||
Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
|
||||
Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
|
||||
|
||||
Vector128<int> rShift = Vector128.CreateScalar(mtx.MatrixRShift);
|
||||
Vector128<ushort> clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow);
|
||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
/// If the required buffer is larger than this, it won't be
|
||||
/// added to the pool to avoid long term high memory usage.
|
||||
/// </summary>
|
||||
private const int MaxBufferSize = 2048 * 1280;
|
||||
private const int MaxBufferSize = 2048 * 2048;
|
||||
|
||||
private struct PoolItem
|
||||
{
|
||||
|
@@ -2,16 +2,85 @@
|
||||
|
||||
namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
ref struct RentedBuffer
|
||||
{
|
||||
public static RentedBuffer Empty => new RentedBuffer(Span<byte>.Empty, -1);
|
||||
|
||||
public Span<byte> Data;
|
||||
public int Index;
|
||||
|
||||
public RentedBuffer(Span<byte> data, int index)
|
||||
{
|
||||
Data = data;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public void Return(BufferPool<byte> pool)
|
||||
{
|
||||
if (Index != -1)
|
||||
{
|
||||
pool.Return(Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ref struct InputSurface
|
||||
{
|
||||
public ReadOnlySpan<byte> Buffer0;
|
||||
public ReadOnlySpan<byte> Buffer1;
|
||||
public ReadOnlySpan<byte> Buffer2;
|
||||
|
||||
public int Buffer0Index;
|
||||
public int Buffer1Index;
|
||||
public int Buffer2Index;
|
||||
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
public int UvWidth;
|
||||
public int UvHeight;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Buffer0Index = -1;
|
||||
Buffer1Index = -1;
|
||||
Buffer2Index = -1;
|
||||
}
|
||||
|
||||
public void SetBuffer0(RentedBuffer buffer)
|
||||
{
|
||||
Buffer0 = buffer.Data;
|
||||
Buffer0Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void SetBuffer1(RentedBuffer buffer)
|
||||
{
|
||||
Buffer1 = buffer.Data;
|
||||
Buffer1Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void SetBuffer2(RentedBuffer buffer)
|
||||
{
|
||||
Buffer2 = buffer.Data;
|
||||
Buffer2Index = buffer.Index;
|
||||
}
|
||||
|
||||
public void Return(BufferPool<byte> pool)
|
||||
{
|
||||
if (Buffer0Index != -1)
|
||||
{
|
||||
pool.Return(Buffer0Index);
|
||||
}
|
||||
|
||||
if (Buffer1Index != -1)
|
||||
{
|
||||
pool.Return(Buffer1Index);
|
||||
}
|
||||
|
||||
if (Buffer2Index != -1)
|
||||
{
|
||||
pool.Return(Buffer2Index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Graphics.Vic.Types;
|
||||
using System;
|
||||
@@ -12,24 +12,32 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
static class SurfaceReader
|
||||
{
|
||||
public static Surface Read(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
|
||||
public static Surface Read(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets)
|
||||
{
|
||||
switch (config.SlotPixelFormat)
|
||||
switch (surfaceConfig.SlotPixelFormat)
|
||||
{
|
||||
case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref offsets);
|
||||
case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref surfaceConfig, ref offsets);
|
||||
}
|
||||
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{config.SlotPixelFormat}\".");
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{surfaceConfig.SlotPixelFormat}\".");
|
||||
|
||||
int lw = config.SlotLumaWidth + 1;
|
||||
int lh = config.SlotLumaHeight + 1;
|
||||
int lw = surfaceConfig.SlotLumaWidth + 1;
|
||||
int lh = surfaceConfig.SlotLumaHeight + 1;
|
||||
|
||||
return new Surface(rm.SurfacePool, lw, lh);
|
||||
}
|
||||
|
||||
private unsafe static Surface ReadNv12(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
|
||||
private unsafe static Surface ReadNv12(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets)
|
||||
{
|
||||
InputSurface input = ReadSurface(rm.Gmm, ref config, ref offsets, 1, 2);
|
||||
InputSurface input = ReadSurface(rm, ref config, ref surfaceConfig, ref offsets, 1, 2);
|
||||
|
||||
int width = input.Width;
|
||||
int height = input.Height;
|
||||
@@ -160,6 +168,8 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
}
|
||||
|
||||
input.Return(rm.BufferPool);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -170,84 +180,227 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
}
|
||||
|
||||
private static InputSurface ReadSurface(
|
||||
MemoryManager gmm,
|
||||
ref SlotSurfaceConfig config,
|
||||
ref PlaneOffsets offsets,
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref SlotSurfaceConfig surfaceConfig,
|
||||
ref Array8<PlaneOffsets> offsets,
|
||||
int bytesPerPixel,
|
||||
int planes)
|
||||
{
|
||||
InputSurface surface = new InputSurface();
|
||||
|
||||
int gobBlocksInY = 1 << config.SlotBlkHeight;
|
||||
surface.Initialize();
|
||||
|
||||
bool linear = config.SlotBlkKind == 0;
|
||||
int gobBlocksInY = 1 << surfaceConfig.SlotBlkHeight;
|
||||
|
||||
int lw = config.SlotLumaWidth + 1;
|
||||
int lh = config.SlotLumaHeight + 1;
|
||||
bool linear = surfaceConfig.SlotBlkKind == 0;
|
||||
|
||||
int cw = config.SlotChromaWidth + 1;
|
||||
int ch = config.SlotChromaHeight + 1;
|
||||
int lw = surfaceConfig.SlotLumaWidth + 1;
|
||||
int lh = surfaceConfig.SlotLumaHeight + 1;
|
||||
|
||||
int cw = surfaceConfig.SlotChromaWidth + 1;
|
||||
int ch = surfaceConfig.SlotChromaHeight + 1;
|
||||
|
||||
// Interlaced inputs have double the height when deinterlaced.
|
||||
int heightShift = config.FrameFormat.IsField() ? 1 : 0;
|
||||
|
||||
surface.Width = lw;
|
||||
surface.Height = lh;
|
||||
surface.Height = lh << heightShift;
|
||||
surface.UvWidth = cw;
|
||||
surface.UvHeight = ch;
|
||||
surface.UvHeight = ch << heightShift;
|
||||
|
||||
if (planes > 0)
|
||||
{
|
||||
surface.Buffer0 = ReadBuffer(gmm, offsets.LumaOffset, linear, lw, lh, bytesPerPixel, gobBlocksInY);
|
||||
surface.SetBuffer0(ReadBuffer(rm, ref config, ref offsets, linear, 0, lw, lh, bytesPerPixel, gobBlocksInY));
|
||||
}
|
||||
|
||||
if (planes > 1)
|
||||
{
|
||||
surface.Buffer1 = ReadBuffer(gmm, offsets.ChromaUOffset, linear, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY);
|
||||
surface.SetBuffer1(ReadBuffer(rm, ref config, ref offsets, linear, 1, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY));
|
||||
}
|
||||
|
||||
if (planes > 2)
|
||||
{
|
||||
surface.Buffer2 = ReadBuffer(gmm, offsets.ChromaVOffset, linear, cw, ch, 1, gobBlocksInY);
|
||||
surface.SetBuffer2(ReadBuffer(rm, ref config, ref offsets, linear, 2, cw, ch, 1, gobBlocksInY));
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> ReadBuffer(
|
||||
MemoryManager gmm,
|
||||
uint offset,
|
||||
private static RentedBuffer ReadBuffer(
|
||||
ResourceManager rm,
|
||||
ref SlotConfig config,
|
||||
ref Array8<PlaneOffsets> offsets,
|
||||
bool linear,
|
||||
int plane,
|
||||
int width,
|
||||
int height,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
FrameFormat frameFormat = config.FrameFormat;
|
||||
bool isLuma = plane == 0;
|
||||
bool isField = frameFormat.IsField();
|
||||
bool isTopField = frameFormat.IsTopField(isLuma);
|
||||
int stride = GetPitch(width, bytesPerPixel);
|
||||
uint offset = GetOffset(ref offsets[0], plane);
|
||||
|
||||
int dstStart = 0;
|
||||
int dstStride = stride;
|
||||
|
||||
if (isField)
|
||||
{
|
||||
dstStart = isTopField ? 0 : stride;
|
||||
dstStride = stride * 2;
|
||||
}
|
||||
|
||||
RentedBuffer buffer;
|
||||
|
||||
if (linear)
|
||||
{
|
||||
buffer = ReadBufferLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = ReadBufferBlockLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
if (isField || frameFormat.IsInterlaced())
|
||||
{
|
||||
RentedBuffer prevBuffer = RentedBuffer.Empty;
|
||||
RentedBuffer nextBuffer = RentedBuffer.Empty;
|
||||
|
||||
if (config.PrevFieldEnable)
|
||||
{
|
||||
prevBuffer = ReadBufferNoDeinterlace(rm, ref offsets[1], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
if (config.NextFieldEnable)
|
||||
{
|
||||
nextBuffer = ReadBufferNoDeinterlace(rm, ref offsets[2], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
int w = width * bytesPerPixel;
|
||||
|
||||
switch (config.DeinterlaceMode)
|
||||
{
|
||||
case DeinterlaceMode.Weave:
|
||||
Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.BobField:
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.Bob:
|
||||
bool isCurrentTop = isLuma ? config.IsEven : config.ChromaEven;
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isCurrentTop ^ frameFormat.IsInterlacedBottomFirst());
|
||||
break;
|
||||
case DeinterlaceMode.NewBob:
|
||||
case DeinterlaceMode.Disi1:
|
||||
Scaler.DeinterlaceMotionAdaptive(buffer.Data, prevBuffer.Data, nextBuffer.Data, w, stride, isTopField);
|
||||
break;
|
||||
case DeinterlaceMode.WeaveLumaBobFieldChroma:
|
||||
if (isLuma)
|
||||
{
|
||||
Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
|
||||
}
|
||||
else
|
||||
{
|
||||
Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.Print(LogClass.Vic, $"Unsupported deinterlace mode \"{config.DeinterlaceMode}\".");
|
||||
break;
|
||||
}
|
||||
|
||||
prevBuffer.Return(rm.BufferPool);
|
||||
nextBuffer.Return(rm.BufferPool);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static uint GetOffset(ref PlaneOffsets offsets, int plane)
|
||||
{
|
||||
return plane switch
|
||||
{
|
||||
0 => offsets.LumaOffset,
|
||||
1 => offsets.ChromaUOffset,
|
||||
2 => offsets.ChromaVOffset,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(plane))
|
||||
};
|
||||
}
|
||||
|
||||
private static RentedBuffer ReadBufferNoDeinterlace(
|
||||
ResourceManager rm,
|
||||
ref PlaneOffsets offsets,
|
||||
bool linear,
|
||||
int plane,
|
||||
int width,
|
||||
int height,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
int stride = GetPitch(width, bytesPerPixel);
|
||||
uint offset = GetOffset(ref offsets, plane);
|
||||
|
||||
if (linear)
|
||||
{
|
||||
return gmm.GetSpan(ExtendOffset(offset), stride * height);
|
||||
return ReadBufferLinear(rm, offset, width, height, 0, stride, bytesPerPixel);
|
||||
}
|
||||
|
||||
return ReadBuffer(gmm, offset, width, height, stride, bytesPerPixel, gobBlocksInY);
|
||||
return ReadBufferBlockLinear(rm, offset, width, height, 0, stride, bytesPerPixel, gobBlocksInY);
|
||||
}
|
||||
|
||||
private static ReadOnlySpan<byte> ReadBuffer(
|
||||
MemoryManager gmm,
|
||||
private static RentedBuffer ReadBufferLinear(
|
||||
ResourceManager rm,
|
||||
uint offset,
|
||||
int width,
|
||||
int height,
|
||||
int dstStart,
|
||||
int dstStride,
|
||||
int bytesPerPixel)
|
||||
{
|
||||
int srcStride = GetPitch(width, bytesPerPixel);
|
||||
int inSize = srcStride * height;
|
||||
|
||||
ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
|
||||
int outSize = dstStride * height;
|
||||
int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
|
||||
Span<byte> dst = buffer;
|
||||
dst = dst.Slice(0, outSize);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
src.Slice(y * srcStride, srcStride).CopyTo(dst.Slice(dstStart + y * dstStride, srcStride));
|
||||
}
|
||||
|
||||
return new RentedBuffer(dst, bufferIndex);
|
||||
}
|
||||
|
||||
private static RentedBuffer ReadBufferBlockLinear(
|
||||
ResourceManager rm,
|
||||
uint offset,
|
||||
int width,
|
||||
int height,
|
||||
int dstStart,
|
||||
int dstStride,
|
||||
int bytesPerPixel,
|
||||
int gobBlocksInY)
|
||||
{
|
||||
int inSize = GetBlockLinearSize(width, height, bytesPerPixel, gobBlocksInY);
|
||||
|
||||
ReadOnlySpan<byte> src = gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
|
||||
|
||||
Span<byte> dst = new byte[dstStride * height];
|
||||
int outSize = dstStride * height;
|
||||
int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
|
||||
Span<byte> dst = buffer;
|
||||
dst = dst.Slice(0, outSize);
|
||||
|
||||
LayoutConverter.ConvertBlockLinearToLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
LayoutConverter.ConvertBlockLinearToLinear(dst.Slice(dstStart), width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
|
||||
return dst;
|
||||
return new RentedBuffer(dst, bufferIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
switch (config.OutPixelFormat)
|
||||
{
|
||||
case PixelFormat.A8B8G8R8:
|
||||
case PixelFormat.X8B8G8R8:
|
||||
case PixelFormat.X8B8G8R8:
|
||||
WriteA8B8G8R8(rm, input, ref config, ref offsets);
|
||||
break;
|
||||
case PixelFormat.A8R8G8B8:
|
||||
@@ -433,7 +433,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
{
|
||||
if (linear)
|
||||
{
|
||||
rm.Gmm.Write(ExtendOffset(offset), src);
|
||||
rm.Gmm.WriteMapped(ExtendOffset(offset), src);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ namespace Ryujinx.Graphics.Vic.Image
|
||||
|
||||
LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
|
||||
|
||||
rm.Gmm.Write(ExtendOffset(offset), dst);
|
||||
rm.Gmm.WriteMapped(ExtendOffset(offset), dst);
|
||||
|
||||
rm.BufferPool.Return(dstIndex);
|
||||
}
|
||||
|
124
Ryujinx.Graphics.Vic/Scaler.cs
Normal file
124
Ryujinx.Graphics.Vic/Scaler.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
|
||||
namespace Ryujinx.Graphics.Vic
|
||||
{
|
||||
static class Scaler
|
||||
{
|
||||
public static void DeinterlaceWeave(Span<byte> data, ReadOnlySpan<byte> prevData, int width, int fieldSize, bool isTopField)
|
||||
{
|
||||
// Prev I Curr I Curr P
|
||||
// TTTTTTTT BBBBBBBB TTTTTTTT
|
||||
// -------- -------- BBBBBBBB
|
||||
|
||||
if (isTopField)
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset + fieldSize, width));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset, width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeinterlaceBob(Span<byte> data, int width, int fieldSize, bool isTopField)
|
||||
{
|
||||
// Curr I Curr P
|
||||
// TTTTTTTT TTTTTTTT
|
||||
// -------- TTTTTTTT
|
||||
|
||||
if (isTopField)
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
data.Slice(offset, width).CopyTo(data.Slice(offset + fieldSize, width));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
data.Slice(offset + fieldSize, width).CopyTo(data.Slice(offset, width));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static void DeinterlaceMotionAdaptive(
|
||||
Span<byte> data,
|
||||
ReadOnlySpan<byte> prevData,
|
||||
ReadOnlySpan<byte> nextData,
|
||||
int width,
|
||||
int fieldSize,
|
||||
bool isTopField)
|
||||
{
|
||||
// Very simple motion adaptive algorithm.
|
||||
// If the pixel changed between previous and next frame, use Bob, otherwise use Weave.
|
||||
//
|
||||
// Example pseudo code:
|
||||
// C_even = (P_even == N_even) ? P_even : C_odd
|
||||
// Where: C is current frame, P is previous frame and N is next frame, and even/odd are the fields.
|
||||
//
|
||||
// Note: This does not fully match the hardware algorithm.
|
||||
// The motion adaptive deinterlacing implemented on hardware is considerably more complex,
|
||||
// and hard to implement accurately without proper documentation as for example, the
|
||||
// method used for motion estimation is unknown.
|
||||
|
||||
int start = isTopField ? fieldSize : 0;
|
||||
int otherFieldOffset = isTopField ? -fieldSize : fieldSize;
|
||||
|
||||
fixed (byte* pData = data, pPrevData = prevData, pNextData = nextData)
|
||||
{
|
||||
for (int offset = start; offset < data.Length; offset += fieldSize * 2)
|
||||
{
|
||||
int refOffset = (offset - start) >> 1;
|
||||
int x = 0;
|
||||
|
||||
if (Avx2.IsSupported)
|
||||
{
|
||||
for (; x < (width & ~0x1f); x += 32)
|
||||
{
|
||||
Vector256<byte> prevPixels = Avx.LoadVector256(pPrevData + refOffset + x);
|
||||
Vector256<byte> nextPixels = Avx.LoadVector256(pNextData + refOffset + x);
|
||||
Vector256<byte> bob = Avx.LoadVector256(pData + offset + otherFieldOffset + x);
|
||||
Vector256<byte> diff = Avx2.CompareEqual(prevPixels, nextPixels);
|
||||
Avx.Store(pData + offset + x, Avx2.BlendVariable(bob, prevPixels, diff));
|
||||
}
|
||||
}
|
||||
else if (Sse41.IsSupported)
|
||||
{
|
||||
for (; x < (width & ~0xf); x += 16)
|
||||
{
|
||||
Vector128<byte> prevPixels = Sse2.LoadVector128(pPrevData + refOffset + x);
|
||||
Vector128<byte> nextPixels = Sse2.LoadVector128(pNextData + refOffset + x);
|
||||
Vector128<byte> bob = Sse2.LoadVector128(pData + offset + otherFieldOffset + x);
|
||||
Vector128<byte> diff = Sse2.CompareEqual(prevPixels, nextPixels);
|
||||
Sse2.Store(pData + offset + x, Sse41.BlendVariable(bob, prevPixels, diff));
|
||||
}
|
||||
}
|
||||
|
||||
for (; x < width; x++)
|
||||
{
|
||||
byte prevPixel = prevData[refOffset + x];
|
||||
byte nextPixel = nextData[refOffset + x];
|
||||
|
||||
if (nextPixel != prevPixel)
|
||||
{
|
||||
data[offset + x] = data[offset + otherFieldOffset + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
data[offset + x] = prevPixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
Normal file
12
Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
enum DeinterlaceMode
|
||||
{
|
||||
Weave,
|
||||
BobField,
|
||||
Bob,
|
||||
NewBob,
|
||||
Disi1,
|
||||
WeaveLumaBobFieldChroma
|
||||
}
|
||||
}
|
79
Ryujinx.Graphics.Vic/Types/FrameFormat.cs
Normal file
79
Ryujinx.Graphics.Vic/Types/FrameFormat.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
namespace Ryujinx.Graphics.Vic.Types
|
||||
{
|
||||
enum FrameFormat
|
||||
{
|
||||
Progressive,
|
||||
InterlacedTopFieldFirst,
|
||||
InterlacedBottomFieldFirst,
|
||||
TopField,
|
||||
BottomField,
|
||||
SubPicProgressive,
|
||||
SubPicInterlacedTopFieldFirst,
|
||||
SubPicInterlacedBottomFieldFirst,
|
||||
SubPicTopField,
|
||||
SubPicBottomField,
|
||||
TopFieldChromaBottom,
|
||||
BottomFieldChromaTop,
|
||||
SubPicTopFieldChromaBottom,
|
||||
SubPicBottomFieldChromaTop
|
||||
}
|
||||
|
||||
static class FrameFormatExtensions
|
||||
{
|
||||
public static bool IsField(this FrameFormat frameFormat)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.TopField:
|
||||
case FrameFormat.BottomField:
|
||||
case FrameFormat.SubPicTopField:
|
||||
case FrameFormat.SubPicBottomField:
|
||||
case FrameFormat.TopFieldChromaBottom:
|
||||
case FrameFormat.BottomFieldChromaTop:
|
||||
case FrameFormat.SubPicTopFieldChromaBottom:
|
||||
case FrameFormat.SubPicBottomFieldChromaTop:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsInterlaced(this FrameFormat frameFormat)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.InterlacedTopFieldFirst:
|
||||
case FrameFormat.InterlacedBottomFieldFirst:
|
||||
case FrameFormat.SubPicInterlacedTopFieldFirst:
|
||||
case FrameFormat.SubPicInterlacedBottomFieldFirst:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsInterlacedBottomFirst(this FrameFormat frameFormat)
|
||||
{
|
||||
return frameFormat == FrameFormat.InterlacedBottomFieldFirst ||
|
||||
frameFormat == FrameFormat.SubPicInterlacedBottomFieldFirst;
|
||||
}
|
||||
|
||||
public static bool IsTopField(this FrameFormat frameFormat, bool isLuma)
|
||||
{
|
||||
switch (frameFormat)
|
||||
{
|
||||
case FrameFormat.TopField:
|
||||
case FrameFormat.SubPicTopField:
|
||||
return true;
|
||||
case FrameFormat.TopFieldChromaBottom:
|
||||
case FrameFormat.SubPicTopFieldChromaBottom:
|
||||
return isLuma;
|
||||
case FrameFormat.BottomFieldChromaTop:
|
||||
case FrameFormat.SubPicBottomFieldChromaTop:
|
||||
return !isLuma;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@
|
||||
public bool PrevMotionFieldEnable => _word0.Extract(13);
|
||||
public bool PpMotionFieldEnable => _word0.Extract(14);
|
||||
public bool CombMotionFieldEnable => _word0.Extract(15);
|
||||
public int FrameFormat => _word0.Extract(16, 4);
|
||||
public FrameFormat FrameFormat => (FrameFormat)_word0.Extract(16, 4);
|
||||
public int FilterLengthY => _word0.Extract(20, 2);
|
||||
public int FilterLengthX => _word0.Extract(22, 2);
|
||||
public int Panoramic => _word0.Extract(24, 12);
|
||||
@@ -36,7 +36,7 @@
|
||||
public int FilterDetail => _word1.Extract(74, 10);
|
||||
public int ChromaNoise => _word1.Extract(84, 10);
|
||||
public int ChromaDetail => _word1.Extract(94, 10);
|
||||
public int DeinterlaceMode => _word1.Extract(104, 4);
|
||||
public DeinterlaceMode DeinterlaceMode => (DeinterlaceMode)_word1.Extract(104, 4);
|
||||
public int MotionAccumWeight => _word1.Extract(108, 3);
|
||||
public int NoiseIir => _word1.Extract(111, 11);
|
||||
public int LightLevel => _word1.Extract(122, 4);
|
||||
|
@@ -43,9 +43,9 @@ namespace Ryujinx.Graphics.Vic
|
||||
continue;
|
||||
}
|
||||
|
||||
var offsets = _state.State.SetSurfacexSlotx[i][0];
|
||||
ref var offsets = ref _state.State.SetSurfacexSlotx[i];
|
||||
|
||||
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotSurfaceConfig, ref offsets);
|
||||
using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets);
|
||||
|
||||
Blender.BlendOne(output, src, ref slot);
|
||||
}
|
||||
|
8
Ryujinx.Graphics.Video/FrameField.cs
Normal file
8
Ryujinx.Graphics.Video/FrameField.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Graphics.Video
|
||||
{
|
||||
public enum FrameField
|
||||
{
|
||||
Progressive,
|
||||
Interlaced
|
||||
}
|
||||
}
|
@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Video
|
||||
Plane UPlane { get; }
|
||||
Plane VPlane { get; }
|
||||
|
||||
FrameField Field { get; }
|
||||
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
int Stride { get; }
|
||||
|
@@ -19,7 +19,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||
<PackageReference Include="LibHac" Version="0.16.0" />
|
||||
<PackageReference Include="LibHac" Version="0.16.1" />
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
|
Reference in New Issue
Block a user