Move solution and projects to src
This commit is contained in:
15
src/Ryujinx.Graphics.Gpu/ClassId.cs
Normal file
15
src/Ryujinx.Graphics.Gpu/ClassId.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU engine class ID.
|
||||
/// </summary>
|
||||
public enum ClassId
|
||||
{
|
||||
Twod = 0x902d,
|
||||
Threed = 0xb197,
|
||||
Compute = 0xb1c0,
|
||||
InlineToMemory = 0xa140,
|
||||
Dma = 0xb0b5,
|
||||
GPFifo = 0xb06f
|
||||
}
|
||||
}
|
||||
104
src/Ryujinx.Graphics.Gpu/Constants.cs
Normal file
104
src/Ryujinx.Graphics.Gpu/Constants.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
/// <summary>
|
||||
/// Common Maxwell GPU constants.
|
||||
/// </summary>
|
||||
static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// Maximum number of compute uniform buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not reflect the hardware count, the API will emulate some constant buffers using
|
||||
/// global memory to make up for the low amount of compute constant buffers supported by hardware (only 8).
|
||||
/// </remarks>
|
||||
public const int TotalCpUniformBuffers = 17; // 8 hardware constant buffers + 9 emulated (14 available to the user).
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of compute storage buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalCpStorageBuffers = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of graphics uniform buffers.
|
||||
/// </summary>
|
||||
public const int TotalGpUniformBuffers = 18;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of graphics storage buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of storage buffers is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalGpStorageBuffers = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of transform feedback buffers.
|
||||
/// </summary>
|
||||
public const int TotalTransformFeedbackBuffers = 4;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of textures on a single shader stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of textures is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalTextures = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of images on a single shader stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum number of images is API limited, the hardware supports an unlimited amount.
|
||||
/// </remarks>
|
||||
public const int TotalImages = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of render target color buffers.
|
||||
/// </summary>
|
||||
public const int TotalRenderTargets = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Number of shader stages.
|
||||
/// </summary>
|
||||
public const int ShaderStages = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of vertex attributes.
|
||||
/// </summary>
|
||||
public const int TotalVertexAttribs = 16; // FIXME: Should be 32, but OpenGL only supports 16.
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of vertex buffers.
|
||||
/// </summary>
|
||||
public const int TotalVertexBuffers = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of viewports.
|
||||
/// </summary>
|
||||
public const int TotalViewports = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum size of gl_ClipDistance array in shaders.
|
||||
/// </summary>
|
||||
public const int TotalClipDistances = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Byte alignment for texture stride.
|
||||
/// </summary>
|
||||
public const int StrideAlignment = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Byte alignment for block linear textures
|
||||
/// </summary>
|
||||
public const int GobAlignment = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Expected byte alignment for storage buffers
|
||||
/// </summary>
|
||||
public const int StorageAlignment = 16;
|
||||
}
|
||||
}
|
||||
219
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
Normal file
219
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a compute engine class.
|
||||
/// </summary>
|
||||
class ComputeClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ThreedClass _3dEngine;
|
||||
private readonly DeviceState<ComputeClassState> _state;
|
||||
|
||||
private readonly InlineToMemoryClass _i2mClass;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the compute engine class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="threedEngine">3D engine</param>
|
||||
public ComputeClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_3dEngine = threedEngine;
|
||||
_state = new DeviceState<ComputeClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(ComputeClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||
{ nameof(ComputeClassState.LoadInlineData), new RwCallback(LoadInlineData, null) },
|
||||
{ nameof(ComputeClassState.SendSignalingPcasB), new RwCallback(SendSignalingPcasB, null) }
|
||||
});
|
||||
|
||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Launches the Inline-to-Memory DMA copy operation.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
{
|
||||
_i2mClass.LaunchDma(ref Unsafe.As<ComputeClassState, InlineToMemoryClassState>(ref _state.State), argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a block of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to push</param>
|
||||
public void LoadInlineData(ReadOnlySpan<int> data)
|
||||
{
|
||||
_i2mClass.LoadInlineData(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a word of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadInlineData(int argument)
|
||||
{
|
||||
_i2mClass.LoadInlineData(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the compute dispatch operation.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void SendSignalingPcasB(int argument)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
// Since we're going to change the state, make sure any pending instanced draws are done.
|
||||
_3dEngine.PerformDeferredDraws();
|
||||
|
||||
// Make sure all pending uniform buffer data is written to memory.
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
uint qmdAddress = _state.State.SendPcasA;
|
||||
|
||||
var qmd = _channel.MemoryManager.Read<ComputeQmd>((ulong)qmdAddress << 8);
|
||||
|
||||
ulong shaderGpuVa = ((ulong)_state.State.SetProgramRegionAAddressUpper << 32) | _state.State.SetProgramRegionB;
|
||||
|
||||
shaderGpuVa += (uint)qmd.ProgramOffset;
|
||||
|
||||
int localMemorySize = qmd.ShaderLocalMemoryLowSize + qmd.ShaderLocalMemoryHighSize;
|
||||
|
||||
int sharedMemorySize = Math.Min(qmd.SharedMemorySize, _context.Capabilities.MaximumComputeSharedMemorySize);
|
||||
|
||||
for (int index = 0; index < Constants.TotalCpUniformBuffers; index++)
|
||||
{
|
||||
if (!qmd.ConstantBufferValid(index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong gpuVa = (uint)qmd.ConstantBufferAddrLower(index) | (ulong)qmd.ConstantBufferAddrUpper(index) << 32;
|
||||
ulong size = (ulong)qmd.ConstantBufferSize(index);
|
||||
|
||||
_channel.BufferManager.SetComputeUniformBuffer(index, gpuVa, size);
|
||||
}
|
||||
|
||||
ulong samplerPoolGpuVa = ((ulong)_state.State.SetTexSamplerPoolAOffsetUpper << 32) | _state.State.SetTexSamplerPoolB;
|
||||
ulong texturePoolGpuVa = ((ulong)_state.State.SetTexHeaderPoolAOffsetUpper << 32) | _state.State.SetTexHeaderPoolB;
|
||||
|
||||
GpuChannelPoolState poolState = new GpuChannelPoolState(
|
||||
texturePoolGpuVa,
|
||||
_state.State.SetTexHeaderPoolCMaximumIndex,
|
||||
_state.State.SetBindlessTextureConstantBufferSlotSelect);
|
||||
|
||||
GpuChannelComputeState computeState = new GpuChannelComputeState(
|
||||
qmd.CtaThreadDimension0,
|
||||
qmd.CtaThreadDimension1,
|
||||
qmd.CtaThreadDimension2,
|
||||
localMemorySize,
|
||||
sharedMemorySize,
|
||||
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
|
||||
CachedShaderProgram cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
_channel.TextureManager.SetComputeSamplerPool(samplerPoolGpuVa, _state.State.SetTexSamplerPoolCMaximumIndex, qmd.SamplerIndex);
|
||||
_channel.TextureManager.SetComputeTexturePool(texturePoolGpuVa, _state.State.SetTexHeaderPoolCMaximumIndex);
|
||||
_channel.TextureManager.SetComputeTextureBufferIndex(_state.State.SetBindlessTextureConstantBufferSlotSelect);
|
||||
|
||||
ShaderProgramInfo info = cs.Shaders[0].Info;
|
||||
|
||||
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
|
||||
|
||||
for (int index = 0; index < info.SBuffers.Count; index++)
|
||||
{
|
||||
BufferDescriptor sb = info.SBuffers[index];
|
||||
|
||||
ulong sbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
|
||||
|
||||
int sbDescOffset = 0x310 + sb.Slot * 0x10;
|
||||
|
||||
sbDescAddress += (ulong)sbDescOffset;
|
||||
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size, sb.Flags);
|
||||
}
|
||||
|
||||
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)
|
||||
{
|
||||
// Refetch the shader, as assumptions about storage buffer alignment have changed.
|
||||
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||
|
||||
info = cs.Shaders[0].Info;
|
||||
}
|
||||
|
||||
for (int index = 0; index < info.CBuffers.Count; index++)
|
||||
{
|
||||
BufferDescriptor cb = info.CBuffers[index];
|
||||
|
||||
// NVN uses the "hardware" constant buffer for anything that is less than 8,
|
||||
// and those are already bound above.
|
||||
// Anything greater than or equal to 8 uses the emulated constant buffers.
|
||||
// They are emulated using global memory loads.
|
||||
if (cb.Slot < 8)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong cbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
|
||||
|
||||
int cbDescOffset = 0x260 + (cb.Slot - 8) * 0x10;
|
||||
|
||||
cbDescAddress += (ulong)cbDescOffset;
|
||||
|
||||
SbDescriptor cbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(cbDescAddress);
|
||||
|
||||
_channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
|
||||
|
||||
_channel.TextureManager.SetComputeBindings(cs.Bindings);
|
||||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.BufferManager.CommitComputeBindings();
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||
|
||||
_3dEngine.ForceShaderUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
435
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs
Normal file
435
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs
Normal file
@@ -0,0 +1,435 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
{
|
||||
/// <summary>
|
||||
/// Notify type.
|
||||
/// </summary>
|
||||
enum NotifyType
|
||||
{
|
||||
WriteOnly = 0,
|
||||
WriteThenAwaken = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CWD control SM selection.
|
||||
/// </summary>
|
||||
enum SetCwdControlSmSelection
|
||||
{
|
||||
LoadBalanced = 0,
|
||||
RoundRobin = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache lines to invalidate.
|
||||
/// </summary>
|
||||
enum InvalidateCacheLines
|
||||
{
|
||||
All = 0,
|
||||
One = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC SCG type.
|
||||
/// </summary>
|
||||
enum SetGwcScgTypeScgType
|
||||
{
|
||||
GraphicsCompute0 = 0,
|
||||
Compute1 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render enable override mode.
|
||||
/// </summary>
|
||||
enum SetRenderEnableOverrideMode
|
||||
{
|
||||
UseRenderEnable = 0,
|
||||
AlwaysRender = 1,
|
||||
NeverRender = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore report operation.
|
||||
/// </summary>
|
||||
enum SetReportSemaphoreDOperation
|
||||
{
|
||||
Release = 0,
|
||||
Trap = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore report structure size.
|
||||
/// </summary>
|
||||
enum SetReportSemaphoreDStructureSize
|
||||
{
|
||||
FourWords = 0,
|
||||
OneWord = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore report reduction operation.
|
||||
/// </summary>
|
||||
enum SetReportSemaphoreDReductionOp
|
||||
{
|
||||
RedAdd = 0,
|
||||
RedMin = 1,
|
||||
RedMax = 2,
|
||||
RedInc = 3,
|
||||
RedDec = 4,
|
||||
RedAnd = 5,
|
||||
RedOr = 6,
|
||||
RedXor = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore report reduction format.
|
||||
/// </summary>
|
||||
enum SetReportSemaphoreDReductionFormat
|
||||
{
|
||||
Unsigned32 = 0,
|
||||
Signed32 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute class state.
|
||||
/// </summary>
|
||||
unsafe struct ComputeClassState
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint SetObject;
|
||||
public int SetObjectClassId => (int)((SetObject >> 0) & 0xFFFF);
|
||||
public int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F);
|
||||
public fixed uint Reserved04[63];
|
||||
public uint NoOperation;
|
||||
public uint SetNotifyA;
|
||||
public int SetNotifyAAddressUpper => (int)((SetNotifyA >> 0) & 0xFF);
|
||||
public uint SetNotifyB;
|
||||
public uint Notify;
|
||||
public NotifyType NotifyType => (NotifyType)(Notify);
|
||||
public uint WaitForIdle;
|
||||
public fixed uint Reserved114[7];
|
||||
public uint SetGlobalRenderEnableA;
|
||||
public int SetGlobalRenderEnableAOffsetUpper => (int)((SetGlobalRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetGlobalRenderEnableB;
|
||||
public uint SetGlobalRenderEnableC;
|
||||
public int SetGlobalRenderEnableCMode => (int)((SetGlobalRenderEnableC >> 0) & 0x7);
|
||||
public uint SendGoIdle;
|
||||
public uint PmTrigger;
|
||||
public uint PmTriggerWfi;
|
||||
public fixed uint Reserved148[2];
|
||||
public uint SetInstrumentationMethodHeader;
|
||||
public uint SetInstrumentationMethodData;
|
||||
public fixed uint Reserved158[10];
|
||||
public uint LineLengthIn;
|
||||
public uint LineCount;
|
||||
public uint OffsetOutUpper;
|
||||
public int OffsetOutUpperValue => (int)((OffsetOutUpper >> 0) & 0xFF);
|
||||
public uint OffsetOut;
|
||||
public uint PitchOut;
|
||||
public uint SetDstBlockSize;
|
||||
public SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)((SetDstBlockSize >> 0) & 0xF);
|
||||
public SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF);
|
||||
public SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF);
|
||||
public uint SetDstWidth;
|
||||
public uint SetDstHeight;
|
||||
public uint SetDstDepth;
|
||||
public uint SetDstLayer;
|
||||
public uint SetDstOriginBytesX;
|
||||
public int SetDstOriginBytesXV => (int)((SetDstOriginBytesX >> 0) & 0xFFFFF);
|
||||
public uint SetDstOriginSamplesY;
|
||||
public int SetDstOriginSamplesYV => (int)((SetDstOriginSamplesY >> 0) & 0xFFFF);
|
||||
public uint LaunchDma;
|
||||
public LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)((LaunchDma >> 0) & 0x1);
|
||||
public LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3);
|
||||
public LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3);
|
||||
public LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1);
|
||||
public bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0;
|
||||
public LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7);
|
||||
public LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3);
|
||||
public bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0;
|
||||
public uint LoadInlineData;
|
||||
public fixed uint Reserved1B8[9];
|
||||
public uint SetI2mSemaphoreA;
|
||||
public int SetI2mSemaphoreAOffsetUpper => (int)((SetI2mSemaphoreA >> 0) & 0xFF);
|
||||
public uint SetI2mSemaphoreB;
|
||||
public uint SetI2mSemaphoreC;
|
||||
public fixed uint Reserved1E8[2];
|
||||
public uint SetI2mSpareNoop00;
|
||||
public uint SetI2mSpareNoop01;
|
||||
public uint SetI2mSpareNoop02;
|
||||
public uint SetI2mSpareNoop03;
|
||||
public uint SetValidSpanOverflowAreaA;
|
||||
public int SetValidSpanOverflowAreaAAddressUpper => (int)((SetValidSpanOverflowAreaA >> 0) & 0xFF);
|
||||
public uint SetValidSpanOverflowAreaB;
|
||||
public uint SetValidSpanOverflowAreaC;
|
||||
public uint SetCoalesceWaitingPeriodUnit;
|
||||
public uint PerfmonTransfer;
|
||||
public uint SetShaderSharedMemoryWindow;
|
||||
public uint SetSelectMaxwellTextureHeaders;
|
||||
public bool SetSelectMaxwellTextureHeadersV => (SetSelectMaxwellTextureHeaders & 0x1) != 0;
|
||||
public uint InvalidateShaderCaches;
|
||||
public bool InvalidateShaderCachesInstruction => (InvalidateShaderCaches & 0x1) != 0;
|
||||
public bool InvalidateShaderCachesData => (InvalidateShaderCaches & 0x10) != 0;
|
||||
public bool InvalidateShaderCachesConstant => (InvalidateShaderCaches & 0x1000) != 0;
|
||||
public bool InvalidateShaderCachesLocks => (InvalidateShaderCaches & 0x2) != 0;
|
||||
public bool InvalidateShaderCachesFlushData => (InvalidateShaderCaches & 0x4) != 0;
|
||||
public uint SetReservedSwMethod00;
|
||||
public uint SetReservedSwMethod01;
|
||||
public uint SetReservedSwMethod02;
|
||||
public uint SetReservedSwMethod03;
|
||||
public uint SetReservedSwMethod04;
|
||||
public uint SetReservedSwMethod05;
|
||||
public uint SetReservedSwMethod06;
|
||||
public uint SetReservedSwMethod07;
|
||||
public uint SetCwdControl;
|
||||
public SetCwdControlSmSelection SetCwdControlSmSelection => (SetCwdControlSmSelection)((SetCwdControl >> 0) & 0x1);
|
||||
public uint InvalidateTextureHeaderCacheNoWfi;
|
||||
public InvalidateCacheLines InvalidateTextureHeaderCacheNoWfiLines => (InvalidateCacheLines)((InvalidateTextureHeaderCacheNoWfi >> 0) & 0x1);
|
||||
public int InvalidateTextureHeaderCacheNoWfiTag => (int)((InvalidateTextureHeaderCacheNoWfi >> 4) & 0x3FFFFF);
|
||||
public uint SetCwdRefCounter;
|
||||
public int SetCwdRefCounterSelect => (int)((SetCwdRefCounter >> 0) & 0x3F);
|
||||
public int SetCwdRefCounterValue => (int)((SetCwdRefCounter >> 8) & 0xFFFF);
|
||||
public uint SetReservedSwMethod08;
|
||||
public uint SetReservedSwMethod09;
|
||||
public uint SetReservedSwMethod10;
|
||||
public uint SetReservedSwMethod11;
|
||||
public uint SetReservedSwMethod12;
|
||||
public uint SetReservedSwMethod13;
|
||||
public uint SetReservedSwMethod14;
|
||||
public uint SetReservedSwMethod15;
|
||||
public uint SetGwcScgType;
|
||||
public SetGwcScgTypeScgType SetGwcScgTypeScgType => (SetGwcScgTypeScgType)((SetGwcScgType >> 0) & 0x1);
|
||||
public uint SetScgControl;
|
||||
public int SetScgControlCompute1MaxSmCount => (int)((SetScgControl >> 0) & 0x1FF);
|
||||
public uint InvalidateConstantBufferCacheA;
|
||||
public int InvalidateConstantBufferCacheAAddressUpper => (int)((InvalidateConstantBufferCacheA >> 0) & 0xFF);
|
||||
public uint InvalidateConstantBufferCacheB;
|
||||
public uint InvalidateConstantBufferCacheC;
|
||||
public int InvalidateConstantBufferCacheCByteCount => (int)((InvalidateConstantBufferCacheC >> 0) & 0x1FFFF);
|
||||
public bool InvalidateConstantBufferCacheCThruL2 => (InvalidateConstantBufferCacheC & 0x80000000) != 0;
|
||||
public uint SetComputeClassVersion;
|
||||
public int SetComputeClassVersionCurrent => (int)((SetComputeClassVersion >> 0) & 0xFFFF);
|
||||
public int SetComputeClassVersionOldestSupported => (int)((SetComputeClassVersion >> 16) & 0xFFFF);
|
||||
public uint CheckComputeClassVersion;
|
||||
public int CheckComputeClassVersionCurrent => (int)((CheckComputeClassVersion >> 0) & 0xFFFF);
|
||||
public int CheckComputeClassVersionOldestSupported => (int)((CheckComputeClassVersion >> 16) & 0xFFFF);
|
||||
public uint SetQmdVersion;
|
||||
public int SetQmdVersionCurrent => (int)((SetQmdVersion >> 0) & 0xFFFF);
|
||||
public int SetQmdVersionOldestSupported => (int)((SetQmdVersion >> 16) & 0xFFFF);
|
||||
public uint SetWfiConfig;
|
||||
public bool SetWfiConfigEnableScgTypeWfi => (SetWfiConfig & 0x1) != 0;
|
||||
public uint CheckQmdVersion;
|
||||
public int CheckQmdVersionCurrent => (int)((CheckQmdVersion >> 0) & 0xFFFF);
|
||||
public int CheckQmdVersionOldestSupported => (int)((CheckQmdVersion >> 16) & 0xFFFF);
|
||||
public uint WaitForIdleScgType;
|
||||
public uint InvalidateSkedCaches;
|
||||
public bool InvalidateSkedCachesV => (InvalidateSkedCaches & 0x1) != 0;
|
||||
public uint SetScgRenderEnableControl;
|
||||
public bool SetScgRenderEnableControlCompute1UsesRenderEnable => (SetScgRenderEnableControl & 0x1) != 0;
|
||||
public fixed uint Reserved2A0[4];
|
||||
public uint SetCwdSlotCount;
|
||||
public int SetCwdSlotCountV => (int)((SetCwdSlotCount >> 0) & 0xFF);
|
||||
public uint SendPcasA;
|
||||
public uint SendPcasB;
|
||||
public int SendPcasBFrom => (int)((SendPcasB >> 0) & 0xFFFFFF);
|
||||
public int SendPcasBDelta => (int)((SendPcasB >> 24) & 0xFF);
|
||||
public uint SendSignalingPcasB;
|
||||
public bool SendSignalingPcasBInvalidate => (SendSignalingPcasB & 0x1) != 0;
|
||||
public bool SendSignalingPcasBSchedule => (SendSignalingPcasB & 0x2) != 0;
|
||||
public fixed uint Reserved2C0[9];
|
||||
public uint SetShaderLocalMemoryNonThrottledA;
|
||||
public int SetShaderLocalMemoryNonThrottledASizeUpper => (int)((SetShaderLocalMemoryNonThrottledA >> 0) & 0xFF);
|
||||
public uint SetShaderLocalMemoryNonThrottledB;
|
||||
public uint SetShaderLocalMemoryNonThrottledC;
|
||||
public int SetShaderLocalMemoryNonThrottledCMaxSmCount => (int)((SetShaderLocalMemoryNonThrottledC >> 0) & 0x1FF);
|
||||
public uint SetShaderLocalMemoryThrottledA;
|
||||
public int SetShaderLocalMemoryThrottledASizeUpper => (int)((SetShaderLocalMemoryThrottledA >> 0) & 0xFF);
|
||||
public uint SetShaderLocalMemoryThrottledB;
|
||||
public uint SetShaderLocalMemoryThrottledC;
|
||||
public int SetShaderLocalMemoryThrottledCMaxSmCount => (int)((SetShaderLocalMemoryThrottledC >> 0) & 0x1FF);
|
||||
public fixed uint Reserved2FC[5];
|
||||
public uint SetSpaVersion;
|
||||
public int SetSpaVersionMinor => (int)((SetSpaVersion >> 0) & 0xFF);
|
||||
public int SetSpaVersionMajor => (int)((SetSpaVersion >> 8) & 0xFF);
|
||||
public fixed uint Reserved314[123];
|
||||
public uint SetFalcon00;
|
||||
public uint SetFalcon01;
|
||||
public uint SetFalcon02;
|
||||
public uint SetFalcon03;
|
||||
public uint SetFalcon04;
|
||||
public uint SetFalcon05;
|
||||
public uint SetFalcon06;
|
||||
public uint SetFalcon07;
|
||||
public uint SetFalcon08;
|
||||
public uint SetFalcon09;
|
||||
public uint SetFalcon10;
|
||||
public uint SetFalcon11;
|
||||
public uint SetFalcon12;
|
||||
public uint SetFalcon13;
|
||||
public uint SetFalcon14;
|
||||
public uint SetFalcon15;
|
||||
public uint SetFalcon16;
|
||||
public uint SetFalcon17;
|
||||
public uint SetFalcon18;
|
||||
public uint SetFalcon19;
|
||||
public uint SetFalcon20;
|
||||
public uint SetFalcon21;
|
||||
public uint SetFalcon22;
|
||||
public uint SetFalcon23;
|
||||
public uint SetFalcon24;
|
||||
public uint SetFalcon25;
|
||||
public uint SetFalcon26;
|
||||
public uint SetFalcon27;
|
||||
public uint SetFalcon28;
|
||||
public uint SetFalcon29;
|
||||
public uint SetFalcon30;
|
||||
public uint SetFalcon31;
|
||||
public fixed uint Reserved580[127];
|
||||
public uint SetShaderLocalMemoryWindow;
|
||||
public fixed uint Reserved780[4];
|
||||
public uint SetShaderLocalMemoryA;
|
||||
public int SetShaderLocalMemoryAAddressUpper => (int)((SetShaderLocalMemoryA >> 0) & 0xFF);
|
||||
public uint SetShaderLocalMemoryB;
|
||||
public fixed uint Reserved798[383];
|
||||
public uint SetShaderCacheControl;
|
||||
public bool SetShaderCacheControlIcachePrefetchEnable => (SetShaderCacheControl & 0x1) != 0;
|
||||
public fixed uint ReservedD98[19];
|
||||
public uint SetSmTimeoutInterval;
|
||||
public int SetSmTimeoutIntervalCounterBit => (int)((SetSmTimeoutInterval >> 0) & 0x3F);
|
||||
public fixed uint ReservedDE8[87];
|
||||
public uint SetSpareNoop12;
|
||||
public uint SetSpareNoop13;
|
||||
public uint SetSpareNoop14;
|
||||
public uint SetSpareNoop15;
|
||||
public fixed uint ReservedF54[59];
|
||||
public uint SetSpareNoop00;
|
||||
public uint SetSpareNoop01;
|
||||
public uint SetSpareNoop02;
|
||||
public uint SetSpareNoop03;
|
||||
public uint SetSpareNoop04;
|
||||
public uint SetSpareNoop05;
|
||||
public uint SetSpareNoop06;
|
||||
public uint SetSpareNoop07;
|
||||
public uint SetSpareNoop08;
|
||||
public uint SetSpareNoop09;
|
||||
public uint SetSpareNoop10;
|
||||
public uint SetSpareNoop11;
|
||||
public fixed uint Reserved1070[103];
|
||||
public uint InvalidateSamplerCacheAll;
|
||||
public bool InvalidateSamplerCacheAllV => (InvalidateSamplerCacheAll & 0x1) != 0;
|
||||
public uint InvalidateTextureHeaderCacheAll;
|
||||
public bool InvalidateTextureHeaderCacheAllV => (InvalidateTextureHeaderCacheAll & 0x1) != 0;
|
||||
public fixed uint Reserved1214[29];
|
||||
public uint InvalidateTextureDataCacheNoWfi;
|
||||
public InvalidateCacheLines InvalidateTextureDataCacheNoWfiLines => (InvalidateCacheLines)((InvalidateTextureDataCacheNoWfi >> 0) & 0x1);
|
||||
public int InvalidateTextureDataCacheNoWfiTag => (int)((InvalidateTextureDataCacheNoWfi >> 4) & 0x3FFFFF);
|
||||
public fixed uint Reserved128C[7];
|
||||
public uint ActivatePerfSettingsForComputeContext;
|
||||
public bool ActivatePerfSettingsForComputeContextAll => (ActivatePerfSettingsForComputeContext & 0x1) != 0;
|
||||
public fixed uint Reserved12AC[33];
|
||||
public uint InvalidateSamplerCache;
|
||||
public InvalidateCacheLines InvalidateSamplerCacheLines => (InvalidateCacheLines)((InvalidateSamplerCache >> 0) & 0x1);
|
||||
public int InvalidateSamplerCacheTag => (int)((InvalidateSamplerCache >> 4) & 0x3FFFFF);
|
||||
public uint InvalidateTextureHeaderCache;
|
||||
public InvalidateCacheLines InvalidateTextureHeaderCacheLines => (InvalidateCacheLines)((InvalidateTextureHeaderCache >> 0) & 0x1);
|
||||
public int InvalidateTextureHeaderCacheTag => (int)((InvalidateTextureHeaderCache >> 4) & 0x3FFFFF);
|
||||
public uint InvalidateTextureDataCache;
|
||||
public InvalidateCacheLines InvalidateTextureDataCacheLines => (InvalidateCacheLines)((InvalidateTextureDataCache >> 0) & 0x1);
|
||||
public int InvalidateTextureDataCacheTag => (int)((InvalidateTextureDataCache >> 4) & 0x3FFFFF);
|
||||
public fixed uint Reserved133C[58];
|
||||
public uint InvalidateSamplerCacheNoWfi;
|
||||
public InvalidateCacheLines InvalidateSamplerCacheNoWfiLines => (InvalidateCacheLines)((InvalidateSamplerCacheNoWfi >> 0) & 0x1);
|
||||
public int InvalidateSamplerCacheNoWfiTag => (int)((InvalidateSamplerCacheNoWfi >> 4) & 0x3FFFFF);
|
||||
public fixed uint Reserved1428[64];
|
||||
public uint SetShaderExceptions;
|
||||
public bool SetShaderExceptionsEnable => (SetShaderExceptions & 0x1) != 0;
|
||||
public fixed uint Reserved152C[9];
|
||||
public uint SetRenderEnableA;
|
||||
public int SetRenderEnableAOffsetUpper => (int)((SetRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetRenderEnableB;
|
||||
public uint SetRenderEnableC;
|
||||
public int SetRenderEnableCMode => (int)((SetRenderEnableC >> 0) & 0x7);
|
||||
public uint SetTexSamplerPoolA;
|
||||
public int SetTexSamplerPoolAOffsetUpper => (int)((SetTexSamplerPoolA >> 0) & 0xFF);
|
||||
public uint SetTexSamplerPoolB;
|
||||
public uint SetTexSamplerPoolC;
|
||||
public int SetTexSamplerPoolCMaximumIndex => (int)((SetTexSamplerPoolC >> 0) & 0xFFFFF);
|
||||
public fixed uint Reserved1568[3];
|
||||
public uint SetTexHeaderPoolA;
|
||||
public int SetTexHeaderPoolAOffsetUpper => (int)((SetTexHeaderPoolA >> 0) & 0xFF);
|
||||
public uint SetTexHeaderPoolB;
|
||||
public uint SetTexHeaderPoolC;
|
||||
public int SetTexHeaderPoolCMaximumIndex => (int)((SetTexHeaderPoolC >> 0) & 0x3FFFFF);
|
||||
public fixed uint Reserved1580[34];
|
||||
public uint SetProgramRegionA;
|
||||
public int SetProgramRegionAAddressUpper => (int)((SetProgramRegionA >> 0) & 0xFF);
|
||||
public uint SetProgramRegionB;
|
||||
public fixed uint Reserved1610[34];
|
||||
public uint InvalidateShaderCachesNoWfi;
|
||||
public bool InvalidateShaderCachesNoWfiInstruction => (InvalidateShaderCachesNoWfi & 0x1) != 0;
|
||||
public bool InvalidateShaderCachesNoWfiGlobalData => (InvalidateShaderCachesNoWfi & 0x10) != 0;
|
||||
public bool InvalidateShaderCachesNoWfiConstant => (InvalidateShaderCachesNoWfi & 0x1000) != 0;
|
||||
public fixed uint Reserved169C[170];
|
||||
public uint SetRenderEnableOverride;
|
||||
public SetRenderEnableOverrideMode SetRenderEnableOverrideMode => (SetRenderEnableOverrideMode)((SetRenderEnableOverride >> 0) & 0x3);
|
||||
public fixed uint Reserved1948[57];
|
||||
public uint PipeNop;
|
||||
public uint SetSpare00;
|
||||
public uint SetSpare01;
|
||||
public uint SetSpare02;
|
||||
public uint SetSpare03;
|
||||
public fixed uint Reserved1A40[48];
|
||||
public uint SetReportSemaphoreA;
|
||||
public int SetReportSemaphoreAOffsetUpper => (int)((SetReportSemaphoreA >> 0) & 0xFF);
|
||||
public uint SetReportSemaphoreB;
|
||||
public uint SetReportSemaphoreC;
|
||||
public uint SetReportSemaphoreD;
|
||||
public SetReportSemaphoreDOperation SetReportSemaphoreDOperation => (SetReportSemaphoreDOperation)((SetReportSemaphoreD >> 0) & 0x3);
|
||||
public bool SetReportSemaphoreDAwakenEnable => (SetReportSemaphoreD & 0x100000) != 0;
|
||||
public SetReportSemaphoreDStructureSize SetReportSemaphoreDStructureSize => (SetReportSemaphoreDStructureSize)((SetReportSemaphoreD >> 28) & 0x1);
|
||||
public bool SetReportSemaphoreDFlushDisable => (SetReportSemaphoreD & 0x4) != 0;
|
||||
public bool SetReportSemaphoreDReductionEnable => (SetReportSemaphoreD & 0x8) != 0;
|
||||
public SetReportSemaphoreDReductionOp SetReportSemaphoreDReductionOp => (SetReportSemaphoreDReductionOp)((SetReportSemaphoreD >> 9) & 0x7);
|
||||
public SetReportSemaphoreDReductionFormat SetReportSemaphoreDReductionFormat => (SetReportSemaphoreDReductionFormat)((SetReportSemaphoreD >> 17) & 0x3);
|
||||
public fixed uint Reserved1B10[702];
|
||||
public uint SetBindlessTexture;
|
||||
public int SetBindlessTextureConstantBufferSlotSelect => (int)((SetBindlessTexture >> 0) & 0x7);
|
||||
public uint SetTrapHandler;
|
||||
public fixed uint Reserved2610[843];
|
||||
public Array8<uint> SetShaderPerformanceCounterValueUpper;
|
||||
public Array8<uint> SetShaderPerformanceCounterValue;
|
||||
public Array8<uint> SetShaderPerformanceCounterEvent;
|
||||
public int SetShaderPerformanceCounterEventEvent(int i) => (int)((SetShaderPerformanceCounterEvent[i] >> 0) & 0xFF);
|
||||
public Array8<uint> SetShaderPerformanceCounterControlA;
|
||||
public int SetShaderPerformanceCounterControlAEvent0(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 0) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect0(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 2) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlAEvent1(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 5) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect1(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 7) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlAEvent2(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 10) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect2(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 12) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlAEvent3(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 15) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect3(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 17) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlAEvent4(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 20) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect4(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 22) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlAEvent5(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 25) & 0x3);
|
||||
public int SetShaderPerformanceCounterControlABitSelect5(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 27) & 0x7);
|
||||
public int SetShaderPerformanceCounterControlASpare(int i) => (int)((SetShaderPerformanceCounterControlA[i] >> 30) & 0x3);
|
||||
public Array8<uint> SetShaderPerformanceCounterControlB;
|
||||
public bool SetShaderPerformanceCounterControlBEdge(int i) => (SetShaderPerformanceCounterControlB[i] & 0x1) != 0;
|
||||
public int SetShaderPerformanceCounterControlBMode(int i) => (int)((SetShaderPerformanceCounterControlB[i] >> 1) & 0x3);
|
||||
public bool SetShaderPerformanceCounterControlBWindowed(int i) => (SetShaderPerformanceCounterControlB[i] & 0x8) != 0;
|
||||
public int SetShaderPerformanceCounterControlBFunc(int i) => (int)((SetShaderPerformanceCounterControlB[i] >> 4) & 0xFFFF);
|
||||
public uint SetShaderPerformanceCounterTrapControl;
|
||||
public int SetShaderPerformanceCounterTrapControlMask => (int)((SetShaderPerformanceCounterTrapControl >> 0) & 0xFF);
|
||||
public uint StartShaderPerformanceCounter;
|
||||
public int StartShaderPerformanceCounterCounterMask => (int)((StartShaderPerformanceCounter >> 0) & 0xFF);
|
||||
public uint StopShaderPerformanceCounter;
|
||||
public int StopShaderPerformanceCounterCounterMask => (int)((StopShaderPerformanceCounter >> 0) & 0xFF);
|
||||
public fixed uint Reserved33E8[6];
|
||||
public MmeShadowScratch SetMmeShadowScratch;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
275
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs
Normal file
275
src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs
Normal file
@@ -0,0 +1,275 @@
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of the dependent Queue Meta Data.
|
||||
/// </summary>
|
||||
enum DependentQmdType
|
||||
{
|
||||
Queue,
|
||||
Grid
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the release memory barrier.
|
||||
/// </summary>
|
||||
enum ReleaseMembarType
|
||||
{
|
||||
FeNone,
|
||||
FeSysmembar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of the CWD memory barrier.
|
||||
/// </summary>
|
||||
enum CwdMembarType
|
||||
{
|
||||
L1None,
|
||||
L1Sysmembar,
|
||||
L1Membar
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NaN behavior of 32-bits float operations on the shader.
|
||||
/// </summary>
|
||||
enum Fp32NanBehavior
|
||||
{
|
||||
Legacy,
|
||||
Fp64Compatible
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NaN behavior of 32-bits float to integer conversion on the shader.
|
||||
/// </summary>
|
||||
enum Fp32F2iNanBehavior
|
||||
{
|
||||
PassZero,
|
||||
PassIndefinite
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Limit of calls.
|
||||
/// </summary>
|
||||
enum ApiVisibleCallLimit
|
||||
{
|
||||
_32,
|
||||
NoCheck
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shared memory bank mapping mode.
|
||||
/// </summary>
|
||||
enum SharedMemoryBankMapping
|
||||
{
|
||||
FourBytesPerBank,
|
||||
EightBytesPerBank
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denormal behavior of 32-bits float narrowing instructions.
|
||||
/// </summary>
|
||||
enum Fp32NarrowInstruction
|
||||
{
|
||||
KeepDenorms,
|
||||
FlushDenorms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the L1 cache.
|
||||
/// </summary>
|
||||
enum L1Configuration
|
||||
{
|
||||
DirectlyAddressableMemorySize16kb,
|
||||
DirectlyAddressableMemorySize32kb,
|
||||
DirectlyAddressableMemorySize48kb
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduction operation.
|
||||
/// </summary>
|
||||
enum ReductionOp
|
||||
{
|
||||
RedAdd,
|
||||
RedMin,
|
||||
RedMax,
|
||||
RedInc,
|
||||
RedDec,
|
||||
RedAnd,
|
||||
RedOr,
|
||||
RedXor
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reduction format.
|
||||
/// </summary>
|
||||
enum ReductionFormat
|
||||
{
|
||||
Unsigned32,
|
||||
Signed32
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Size of a structure in words.
|
||||
/// </summary>
|
||||
enum StructureSize
|
||||
{
|
||||
FourWords,
|
||||
OneWord
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute Queue Meta Data.
|
||||
/// </summary>
|
||||
unsafe struct ComputeQmd
|
||||
{
|
||||
private fixed int _words[64];
|
||||
|
||||
public int OuterPut => BitRange(30, 0);
|
||||
public bool OuterOverflow => Bit(31);
|
||||
public int OuterGet => BitRange(62, 32);
|
||||
public bool OuterStickyOverflow => Bit(63);
|
||||
public int InnerGet => BitRange(94, 64);
|
||||
public bool InnerOverflow => Bit(95);
|
||||
public int InnerPut => BitRange(126, 96);
|
||||
public bool InnerStickyOverflow => Bit(127);
|
||||
public int QmdReservedAA => BitRange(159, 128);
|
||||
public int DependentQmdPointer => BitRange(191, 160);
|
||||
public int QmdGroupId => BitRange(197, 192);
|
||||
public bool SmGlobalCachingEnable => Bit(198);
|
||||
public bool RunCtaInOneSmPartition => Bit(199);
|
||||
public bool IsQueue => Bit(200);
|
||||
public bool AddToHeadOfQmdGroupLinkedList => Bit(201);
|
||||
public bool SemaphoreReleaseEnable0 => Bit(202);
|
||||
public bool SemaphoreReleaseEnable1 => Bit(203);
|
||||
public bool RequireSchedulingPcas => Bit(204);
|
||||
public bool DependentQmdScheduleEnable => Bit(205);
|
||||
public DependentQmdType DependentQmdType => (DependentQmdType)BitRange(206, 206);
|
||||
public bool DependentQmdFieldCopy => Bit(207);
|
||||
public int QmdReservedB => BitRange(223, 208);
|
||||
public int CircularQueueSize => BitRange(248, 224);
|
||||
public bool QmdReservedC => Bit(249);
|
||||
public bool InvalidateTextureHeaderCache => Bit(250);
|
||||
public bool InvalidateTextureSamplerCache => Bit(251);
|
||||
public bool InvalidateTextureDataCache => Bit(252);
|
||||
public bool InvalidateShaderDataCache => Bit(253);
|
||||
public bool InvalidateInstructionCache => Bit(254);
|
||||
public bool InvalidateShaderConstantCache => Bit(255);
|
||||
public int ProgramOffset => BitRange(287, 256);
|
||||
public int CircularQueueAddrLower => BitRange(319, 288);
|
||||
public int CircularQueueAddrUpper => BitRange(327, 320);
|
||||
public int QmdReservedD => BitRange(335, 328);
|
||||
public int CircularQueueEntrySize => BitRange(351, 336);
|
||||
public int CwdReferenceCountId => BitRange(357, 352);
|
||||
public int CwdReferenceCountDeltaMinusOne => BitRange(365, 358);
|
||||
public ReleaseMembarType ReleaseMembarType => (ReleaseMembarType)BitRange(366, 366);
|
||||
public bool CwdReferenceCountIncrEnable => Bit(367);
|
||||
public CwdMembarType CwdMembarType => (CwdMembarType)BitRange(369, 368);
|
||||
public bool SequentiallyRunCtas => Bit(370);
|
||||
public bool CwdReferenceCountDecrEnable => Bit(371);
|
||||
public bool Throttled => Bit(372);
|
||||
public Fp32NanBehavior Fp32NanBehavior => (Fp32NanBehavior)BitRange(376, 376);
|
||||
public Fp32F2iNanBehavior Fp32F2iNanBehavior => (Fp32F2iNanBehavior)BitRange(377, 377);
|
||||
public ApiVisibleCallLimit ApiVisibleCallLimit => (ApiVisibleCallLimit)BitRange(378, 378);
|
||||
public SharedMemoryBankMapping SharedMemoryBankMapping => (SharedMemoryBankMapping)BitRange(379, 379);
|
||||
public SamplerIndex SamplerIndex => (SamplerIndex)BitRange(382, 382);
|
||||
public Fp32NarrowInstruction Fp32NarrowInstruction => (Fp32NarrowInstruction)BitRange(383, 383);
|
||||
public int CtaRasterWidth => BitRange(415, 384);
|
||||
public int CtaRasterHeight => BitRange(431, 416);
|
||||
public int CtaRasterDepth => BitRange(447, 432);
|
||||
public int CtaRasterWidthResume => BitRange(479, 448);
|
||||
public int CtaRasterHeightResume => BitRange(495, 480);
|
||||
public int CtaRasterDepthResume => BitRange(511, 496);
|
||||
public int QueueEntriesPerCtaMinusOne => BitRange(518, 512);
|
||||
public int CoalesceWaitingPeriod => BitRange(529, 522);
|
||||
public int SharedMemorySize => BitRange(561, 544);
|
||||
public int QmdReservedG => BitRange(575, 562);
|
||||
public int QmdVersion => BitRange(579, 576);
|
||||
public int QmdMajorVersion => BitRange(583, 580);
|
||||
public int QmdReservedH => BitRange(591, 584);
|
||||
public int CtaThreadDimension0 => BitRange(607, 592);
|
||||
public int CtaThreadDimension1 => BitRange(623, 608);
|
||||
public int CtaThreadDimension2 => BitRange(639, 624);
|
||||
public bool ConstantBufferValid(int i) => Bit(640 + i * 1);
|
||||
public int QmdReservedI => BitRange(668, 648);
|
||||
public L1Configuration L1Configuration => (L1Configuration)BitRange(671, 669);
|
||||
public int SmDisableMaskLower => BitRange(703, 672);
|
||||
public int SmDisableMaskUpper => BitRange(735, 704);
|
||||
public int Release0AddressLower => BitRange(767, 736);
|
||||
public int Release0AddressUpper => BitRange(775, 768);
|
||||
public int QmdReservedJ => BitRange(783, 776);
|
||||
public ReductionOp Release0ReductionOp => (ReductionOp)BitRange(790, 788);
|
||||
public bool QmdReservedK => Bit(791);
|
||||
public ReductionFormat Release0ReductionFormat => (ReductionFormat)BitRange(793, 792);
|
||||
public bool Release0ReductionEnable => Bit(794);
|
||||
public StructureSize Release0StructureSize => (StructureSize)BitRange(799, 799);
|
||||
public int Release0Payload => BitRange(831, 800);
|
||||
public int Release1AddressLower => BitRange(863, 832);
|
||||
public int Release1AddressUpper => BitRange(871, 864);
|
||||
public int QmdReservedL => BitRange(879, 872);
|
||||
public ReductionOp Release1ReductionOp => (ReductionOp)BitRange(886, 884);
|
||||
public bool QmdReservedM => Bit(887);
|
||||
public ReductionFormat Release1ReductionFormat => (ReductionFormat)BitRange(889, 888);
|
||||
public bool Release1ReductionEnable => Bit(890);
|
||||
public StructureSize Release1StructureSize => (StructureSize)BitRange(895, 895);
|
||||
public int Release1Payload => BitRange(927, 896);
|
||||
public int ConstantBufferAddrLower(int i) => BitRange(959 + i * 64, 928 + i * 64);
|
||||
public int ConstantBufferAddrUpper(int i) => BitRange(967 + i * 64, 960 + i * 64);
|
||||
public int ConstantBufferReservedAddr(int i) => BitRange(973 + i * 64, 968 + i * 64);
|
||||
public bool ConstantBufferInvalidate(int i) => Bit(974 + i * 64);
|
||||
public int ConstantBufferSize(int i) => BitRange(991 + i * 64, 975 + i * 64);
|
||||
public int ShaderLocalMemoryLowSize => BitRange(1463, 1440);
|
||||
public int QmdReservedN => BitRange(1466, 1464);
|
||||
public int BarrierCount => BitRange(1471, 1467);
|
||||
public int ShaderLocalMemoryHighSize => BitRange(1495, 1472);
|
||||
public int RegisterCount => BitRange(1503, 1496);
|
||||
public int ShaderLocalMemoryCrsSize => BitRange(1527, 1504);
|
||||
public int SassVersion => BitRange(1535, 1528);
|
||||
public int HwOnlyInnerGet => BitRange(1566, 1536);
|
||||
public bool HwOnlyRequireSchedulingPcas => Bit(1567);
|
||||
public int HwOnlyInnerPut => BitRange(1598, 1568);
|
||||
public bool HwOnlyScgType => Bit(1599);
|
||||
public int HwOnlySpanListHeadIndex => BitRange(1629, 1600);
|
||||
public bool QmdReservedQ => Bit(1630);
|
||||
public bool HwOnlySpanListHeadIndexValid => Bit(1631);
|
||||
public int HwOnlySkedNextQmdPointer => BitRange(1663, 1632);
|
||||
public int QmdSpareE => BitRange(1695, 1664);
|
||||
public int QmdSpareF => BitRange(1727, 1696);
|
||||
public int QmdSpareG => BitRange(1759, 1728);
|
||||
public int QmdSpareH => BitRange(1791, 1760);
|
||||
public int QmdSpareI => BitRange(1823, 1792);
|
||||
public int QmdSpareJ => BitRange(1855, 1824);
|
||||
public int QmdSpareK => BitRange(1887, 1856);
|
||||
public int QmdSpareL => BitRange(1919, 1888);
|
||||
public int QmdSpareM => BitRange(1951, 1920);
|
||||
public int QmdSpareN => BitRange(1983, 1952);
|
||||
public int DebugIdUpper => BitRange(2015, 1984);
|
||||
public int DebugIdLower => BitRange(2047, 2016);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool Bit(int bit)
|
||||
{
|
||||
if ((uint)bit >= 64 * 32)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bit));
|
||||
}
|
||||
|
||||
return (_words[bit >> 5] & (1 << (bit & 31))) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int BitRange(int upper, int lower)
|
||||
{
|
||||
if ((uint)lower >= 64 * 32)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(lower));
|
||||
}
|
||||
|
||||
int mask = (int)(uint.MaxValue >> (32 - (upper - lower + 1)));
|
||||
|
||||
return (_words[lower >> 5] >> (lower & 31)) & mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs
Normal file
12
src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Conditional rendering enable.
|
||||
/// </summary>
|
||||
enum ConditionalRenderEnabled
|
||||
{
|
||||
False,
|
||||
True,
|
||||
Host
|
||||
}
|
||||
}
|
||||
96
src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs
Normal file
96
src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// State interface with a shadow memory control register.
|
||||
/// </summary>
|
||||
interface IShadowState
|
||||
{
|
||||
/// <summary>
|
||||
/// MME shadow ram control mode.
|
||||
/// </summary>
|
||||
SetMmeShadowRamControlMode SetMmeShadowRamControlMode { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device's state, with a additional shadow state.
|
||||
/// </summary>
|
||||
/// <typeparam name="TState">Type of the state</typeparam>
|
||||
class DeviceStateWithShadow<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> : IDeviceState where TState : unmanaged, IShadowState
|
||||
{
|
||||
private readonly DeviceState<TState> _state;
|
||||
private readonly DeviceState<TState> _shadowState;
|
||||
|
||||
/// <summary>
|
||||
/// Current device state.
|
||||
/// </summary>
|
||||
public ref TState State => ref _state.State;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the device state, with shadow state.
|
||||
/// </summary>
|
||||
/// <param name="callbacks">Optional that will be called if a register specified by name is read or written</param>
|
||||
/// <param name="debugLogCallback">Optional callback to be used for debug log messages</param>
|
||||
public DeviceStateWithShadow(IReadOnlyDictionary<string, RwCallback> callbacks = null, Action<string> debugLogCallback = null)
|
||||
{
|
||||
_state = new DeviceState<TState>(callbacks, debugLogCallback);
|
||||
_shadowState = new DeviceState<TState>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value from a register.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register offset in bytes</param>
|
||||
/// <returns>Value stored on the register</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Read(int offset)
|
||||
{
|
||||
return _state.Read(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to a register.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register offset in bytes</param>
|
||||
/// <param name="value">Value to be written</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(int offset, int value)
|
||||
{
|
||||
WriteWithRedundancyCheck(offset, value, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to a register, returning a value indicating if <paramref name="value"/>
|
||||
/// is different from the current value on the register.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register offset in bytes</param>
|
||||
/// <param name="value">Value to be written</param>
|
||||
/// <param name="changed">True if the value was changed, false otherwise</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void WriteWithRedundancyCheck(int offset, int value, out bool changed)
|
||||
{
|
||||
var shadowRamControl = _state.State.SetMmeShadowRamControlMode;
|
||||
if (shadowRamControl == SetMmeShadowRamControlMode.MethodPassthrough || offset < 0x200)
|
||||
{
|
||||
_state.WriteWithRedundancyCheck(offset, value, out changed);
|
||||
}
|
||||
else if (shadowRamControl == SetMmeShadowRamControlMode.MethodTrack ||
|
||||
shadowRamControl == SetMmeShadowRamControlMode.MethodTrackWithFilter)
|
||||
{
|
||||
_shadowState.Write(offset, value);
|
||||
_state.WriteWithRedundancyCheck(offset, value, out changed);
|
||||
}
|
||||
else /* if (shadowRamControl == SetMmeShadowRamControlMode.MethodReplay) */
|
||||
{
|
||||
Debug.Assert(shadowRamControl == SetMmeShadowRamControlMode.MethodReplay);
|
||||
_state.WriteWithRedundancyCheck(offset, _shadowState.Read(offset), out changed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
635
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
Normal file
635
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
Normal file
@@ -0,0 +1,635 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a DMA copy engine class.
|
||||
/// </summary>
|
||||
class DmaClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ThreedClass _3dEngine;
|
||||
private readonly DeviceState<DmaClassState> _state;
|
||||
|
||||
/// <summary>
|
||||
/// Copy flags passed on DMA launch.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
private enum CopyFlags
|
||||
{
|
||||
SrcLinear = 1 << 7,
|
||||
DstLinear = 1 << 8,
|
||||
MultiLineEnable = 1 << 9,
|
||||
RemapEnable = 1 << 10
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture parameters for copy.
|
||||
/// </summary>
|
||||
private struct TextureParams
|
||||
{
|
||||
/// <summary>
|
||||
/// Copy region X coordinate.
|
||||
/// </summary>
|
||||
public readonly int RegionX;
|
||||
|
||||
/// <summary>
|
||||
/// Copy region Y coordinate.
|
||||
/// </summary>
|
||||
public readonly int RegionY;
|
||||
|
||||
/// <summary>
|
||||
/// Offset from the base pointer of the data in memory.
|
||||
/// </summary>
|
||||
public readonly int BaseOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Bytes per pixel.
|
||||
/// </summary>
|
||||
public readonly int Bpp;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the texture is linear. If false, the texture is block linear.
|
||||
/// </summary>
|
||||
public readonly bool Linear;
|
||||
|
||||
/// <summary>
|
||||
/// Pixel offset from XYZ coordinates calculator.
|
||||
/// </summary>
|
||||
public readonly OffsetCalculator Calculator;
|
||||
|
||||
/// <summary>
|
||||
/// Creates texture parameters.
|
||||
/// </summary>
|
||||
/// <param name="regionX">Copy region X coordinate</param>
|
||||
/// <param name="regionY">Copy region Y coordinate</param>
|
||||
/// <param name="baseOffset">Offset from the base pointer of the data in memory</param>
|
||||
/// <param name="bpp">Bytes per pixel</param>
|
||||
/// <param name="linear">Whether the texture is linear. If false, the texture is block linear</param>
|
||||
/// <param name="calculator">Pixel offset from XYZ coordinates calculator</param>
|
||||
public TextureParams(int regionX, int regionY, int baseOffset, int bpp, bool linear, OffsetCalculator calculator)
|
||||
{
|
||||
RegionX = regionX;
|
||||
RegionY = regionY;
|
||||
BaseOffset = baseOffset;
|
||||
Bpp = bpp;
|
||||
Linear = linear;
|
||||
Calculator = calculator;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 3, Pack = 1)]
|
||||
private struct UInt24
|
||||
{
|
||||
public byte Byte0;
|
||||
public byte Byte1;
|
||||
public byte Byte2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the DMA copy engine class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="threedEngine">3D engine</param>
|
||||
public DmaClass(GpuContext context, GpuChannel channel, ThreedClass threedEngine)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_3dEngine = threedEngine;
|
||||
_state = new DeviceState<DmaClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(DmaClassState.LaunchDma), new RwCallback(LaunchDma, null) }
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a buffer-to-texture region covers the entirety of a texture.
|
||||
/// </summary>
|
||||
/// <param name="tex">Texture to compare</param>
|
||||
/// <param name="linear">True if the texture is linear, false if block linear</param>
|
||||
/// <param name="bpp">Texture bytes per pixel</param>
|
||||
/// <param name="stride">Texture stride</param>
|
||||
/// <param name="xCount">Number of pixels to be copied</param>
|
||||
/// <param name="yCount">Number of lines to be copied</param>
|
||||
/// <returns></returns>
|
||||
private static bool IsTextureCopyComplete(DmaTexture tex, bool linear, int bpp, int stride, int xCount, int yCount)
|
||||
{
|
||||
if (linear)
|
||||
{
|
||||
// If the stride is negative, the texture has to be flipped, so
|
||||
// the fast copy is not trivial, use the slow path.
|
||||
if (stride <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int alignWidth = Constants.StrideAlignment / bpp;
|
||||
return stride / bpp == BitUtils.AlignUp(xCount, alignWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
int alignWidth = Constants.GobAlignment / bpp;
|
||||
return tex.RegionX == 0 &&
|
||||
tex.RegionY == 0 &&
|
||||
tex.Width == BitUtils.AlignUp(xCount, alignWidth) &&
|
||||
tex.Height == yCount;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a semaphore for a given LaunchDma method call.
|
||||
/// </summary>
|
||||
/// <param name="argument">The LaunchDma call argument</param>
|
||||
private void ReleaseSemaphore(int argument)
|
||||
{
|
||||
LaunchDmaSemaphoreType type = (LaunchDmaSemaphoreType)((argument >> 3) & 0x3);
|
||||
if (type != LaunchDmaSemaphoreType.None)
|
||||
{
|
||||
ulong address = ((ulong)_state.State.SetSemaphoreA << 32) | _state.State.SetSemaphoreB;
|
||||
if (type == LaunchDmaSemaphoreType.ReleaseOneWordSemaphore)
|
||||
{
|
||||
_channel.MemoryManager.Write(address, _state.State.SetSemaphorePayload);
|
||||
}
|
||||
else /* if (type == LaunchDmaSemaphoreType.ReleaseFourWordSemaphore) */
|
||||
{
|
||||
_channel.MemoryManager.Write(address + 8, _context.GetTimestamp());
|
||||
_channel.MemoryManager.Write(address, (ulong)_state.State.SetSemaphorePayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy.
|
||||
/// </summary>
|
||||
/// <param name="argument">The LaunchDma call argument</param>
|
||||
private void DmaCopy(int argument)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
CopyFlags copyFlags = (CopyFlags)argument;
|
||||
|
||||
bool srcLinear = copyFlags.HasFlag(CopyFlags.SrcLinear);
|
||||
bool dstLinear = copyFlags.HasFlag(CopyFlags.DstLinear);
|
||||
bool copy2D = copyFlags.HasFlag(CopyFlags.MultiLineEnable);
|
||||
bool remap = copyFlags.HasFlag(CopyFlags.RemapEnable);
|
||||
|
||||
uint size = _state.State.LineLengthIn;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong srcGpuVa = ((ulong)_state.State.OffsetInUpperUpper << 32) | _state.State.OffsetInLower;
|
||||
ulong dstGpuVa = ((ulong)_state.State.OffsetOutUpperUpper << 32) | _state.State.OffsetOutLower;
|
||||
|
||||
int xCount = (int)_state.State.LineLengthIn;
|
||||
int yCount = (int)_state.State.LineCount;
|
||||
|
||||
_3dEngine.CreatePendingSyncs();
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
if (copy2D)
|
||||
{
|
||||
// Buffer to texture copy.
|
||||
int componentSize = (int)_state.State.SetRemapComponentsComponentSize + 1;
|
||||
int srcComponents = (int)_state.State.SetRemapComponentsNumSrcComponents + 1;
|
||||
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
|
||||
int srcBpp = remap ? srcComponents * componentSize : 1;
|
||||
int dstBpp = remap ? dstComponents * componentSize : 1;
|
||||
|
||||
var dst = Unsafe.As<uint, DmaTexture>(ref _state.State.SetDstBlockSize);
|
||||
var src = Unsafe.As<uint, DmaTexture>(ref _state.State.SetSrcBlockSize);
|
||||
|
||||
int srcRegionX = 0, srcRegionY = 0, dstRegionX = 0, dstRegionY = 0;
|
||||
|
||||
if (!srcLinear)
|
||||
{
|
||||
srcRegionX = src.RegionX;
|
||||
srcRegionY = src.RegionY;
|
||||
}
|
||||
|
||||
if (!dstLinear)
|
||||
{
|
||||
dstRegionX = dst.RegionX;
|
||||
dstRegionY = dst.RegionY;
|
||||
}
|
||||
|
||||
int srcStride = (int)_state.State.PitchIn;
|
||||
int dstStride = (int)_state.State.PitchOut;
|
||||
|
||||
var srcCalculator = new OffsetCalculator(
|
||||
src.Width,
|
||||
src.Height,
|
||||
srcStride,
|
||||
srcLinear,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
srcBpp);
|
||||
|
||||
var dstCalculator = new OffsetCalculator(
|
||||
dst.Width,
|
||||
dst.Height,
|
||||
dstStride,
|
||||
dstLinear,
|
||||
dst.MemoryLayout.UnpackGobBlocksInY(),
|
||||
dst.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
dstBpp);
|
||||
|
||||
(int srcBaseOffset, int srcSize) = srcCalculator.GetRectangleRange(srcRegionX, srcRegionY, xCount, yCount);
|
||||
(int dstBaseOffset, int dstSize) = dstCalculator.GetRectangleRange(dstRegionX, dstRegionY, xCount, yCount);
|
||||
|
||||
if (srcLinear && srcStride < 0)
|
||||
{
|
||||
srcBaseOffset += srcStride * (yCount - 1);
|
||||
}
|
||||
|
||||
if (dstLinear && dstStride < 0)
|
||||
{
|
||||
dstBaseOffset += dstStride * (yCount - 1);
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
|
||||
|
||||
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
|
||||
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
|
||||
|
||||
if (completeSource && completeDest)
|
||||
{
|
||||
var target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
dstGpuVa,
|
||||
dstBpp,
|
||||
dstStride,
|
||||
dst.Height,
|
||||
xCount,
|
||||
yCount,
|
||||
dstLinear,
|
||||
dst.MemoryLayout.UnpackGobBlocksInY(),
|
||||
dst.MemoryLayout.UnpackGobBlocksInZ());
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
byte[] data;
|
||||
if (srcLinear)
|
||||
{
|
||||
data = LayoutConverter.ConvertLinearStridedToLinear(
|
||||
target.Info.Width,
|
||||
target.Info.Height,
|
||||
1,
|
||||
1,
|
||||
xCount * srcBpp,
|
||||
srcStride,
|
||||
target.Info.FormatInfo.BytesPerPixel,
|
||||
srcSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = LayoutConverter.ConvertBlockLinearToLinear(
|
||||
src.Width,
|
||||
src.Height,
|
||||
src.Depth,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
srcBpp,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
1,
|
||||
new SizeInfo((int)target.Size),
|
||||
srcSpan);
|
||||
}
|
||||
|
||||
target.SynchronizeMemory();
|
||||
target.SetData(data);
|
||||
target.SignalModified();
|
||||
return;
|
||||
}
|
||||
else if (srcCalculator.LayoutMatches(dstCalculator))
|
||||
{
|
||||
// No layout conversion has to be performed, just copy the data entirely.
|
||||
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, srcSpan);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// OPT: This allocates a (potentially) huge temporary array and then copies an existing
|
||||
// region of memory into it, data that might get overwritten entirely anyways. Ideally this should
|
||||
// all be rewritten to use pooled arrays, but that gets complicated with packed data and strides
|
||||
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
|
||||
|
||||
TextureParams srcParams = new TextureParams(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
|
||||
TextureParams dstParams = new TextureParams(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
|
||||
|
||||
// If remapping is enabled, we always copy the components directly, in order.
|
||||
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
|
||||
bool isIdentityRemap = !remap ||
|
||||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
|
||||
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
|
||||
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
|
||||
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
|
||||
|
||||
if (isIdentityRemap)
|
||||
{
|
||||
// The order of the components doesn't change, so we can just copy directly
|
||||
// (with layout conversion if necessary).
|
||||
|
||||
switch (srcBpp)
|
||||
{
|
||||
case 1: Copy<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 2: Copy<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 4: Copy<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 8: Copy<ulong>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 12: Copy<Bpp12Pixel>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 16: Copy<Vector128<byte>>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
default: throw new NotSupportedException($"Unable to copy ${srcBpp} bpp pixel format.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The order or value of the components might change.
|
||||
|
||||
switch (componentSize)
|
||||
{
|
||||
case 1: CopyShuffle<byte>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 2: CopyShuffle<ushort>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 3: CopyShuffle<UInt24>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
case 4: CopyShuffle<uint>(dstSpan, srcSpan, dstParams, srcParams); break;
|
||||
default: throw new NotSupportedException($"Unable to copy ${componentSize} component size.");
|
||||
}
|
||||
}
|
||||
|
||||
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (remap &&
|
||||
_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsDstY == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsDstW == SetRemapComponentsDst.ConstA &&
|
||||
_state.State.SetRemapComponentsNumSrcComponents == SetRemapComponentsNumComponents.One &&
|
||||
_state.State.SetRemapComponentsNumDstComponents == SetRemapComponentsNumComponents.One &&
|
||||
_state.State.SetRemapComponentsComponentSize == SetRemapComponentsComponentSize.Four)
|
||||
{
|
||||
// Fast path for clears when remap is enabled.
|
||||
memoryManager.Physical.BufferCache.ClearBuffer(memoryManager, dstGpuVa, size * 4, _state.State.SetRemapConstA);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Implement remap functionality.
|
||||
// Buffer to buffer copy.
|
||||
|
||||
bool srcIsPitchKind = memoryManager.GetKind(srcGpuVa).IsPitch();
|
||||
bool dstIsPitchKind = memoryManager.GetKind(dstGpuVa).IsPitch();
|
||||
|
||||
if (!srcIsPitchKind && dstIsPitchKind)
|
||||
{
|
||||
CopyGobBlockLinearToLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else if (srcIsPitchKind && !dstIsPitchKind)
|
||||
{
|
||||
CopyGobLinearToBlockLinear(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
memoryManager.Physical.BufferCache.CopyBuffer(memoryManager, srcGpuVa, dstGpuVa, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies data from one texture to another, while performing layout conversion if necessary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Pixel type</typeparam>
|
||||
/// <param name="dstSpan">Destination texture memory region</param>
|
||||
/// <param name="srcSpan">Source texture memory region</param>
|
||||
/// <param name="dst">Destination texture parameters</param>
|
||||
/// <param name="src">Source texture parameters</param>
|
||||
private unsafe void Copy<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
|
||||
{
|
||||
int xCount = (int)_state.State.LineLengthIn;
|
||||
int yCount = (int)_state.State.LineCount;
|
||||
|
||||
if (src.Linear && dst.Linear && src.Bpp == dst.Bpp)
|
||||
{
|
||||
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
|
||||
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
|
||||
for (int y = 0; y < yCount; y++)
|
||||
{
|
||||
src.Calculator.SetY(src.RegionY + y);
|
||||
dst.Calculator.SetY(dst.RegionY + y);
|
||||
int srcOffset = src.Calculator.GetOffset(src.RegionX);
|
||||
int dstOffset = dst.Calculator.GetOffset(dst.RegionX);
|
||||
srcSpan.Slice(srcOffset - src.BaseOffset, xCount * src.Bpp)
|
||||
.CopyTo(dstSpan.Slice(dstOffset - dst.BaseOffset, xCount * dst.Bpp));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
|
||||
{
|
||||
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
|
||||
byte* srcBase = srcPtr - src.BaseOffset;
|
||||
|
||||
for (int y = 0; y < yCount; y++)
|
||||
{
|
||||
src.Calculator.SetY(src.RegionY + y);
|
||||
dst.Calculator.SetY(dst.RegionY + y);
|
||||
|
||||
for (int x = 0; x < xCount; x++)
|
||||
{
|
||||
int srcOffset = src.Calculator.GetOffset(src.RegionX + x);
|
||||
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
|
||||
|
||||
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets texture pixel data to a constant value, while performing layout conversion if necessary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Pixel type</typeparam>
|
||||
/// <param name="dstSpan">Destination texture memory region</param>
|
||||
/// <param name="dst">Destination texture parameters</param>
|
||||
/// <param name="fillValue">Constant pixel value to be set</param>
|
||||
private unsafe void Fill<T>(Span<byte> dstSpan, TextureParams dst, T fillValue) where T : unmanaged
|
||||
{
|
||||
int xCount = (int)_state.State.LineLengthIn;
|
||||
int yCount = (int)_state.State.LineCount;
|
||||
|
||||
fixed (byte* dstPtr = dstSpan)
|
||||
{
|
||||
byte* dstBase = dstPtr - dst.BaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
|
||||
|
||||
for (int y = 0; y < yCount; y++)
|
||||
{
|
||||
dst.Calculator.SetY(dst.RegionY + y);
|
||||
|
||||
for (int x = 0; x < xCount; x++)
|
||||
{
|
||||
int dstOffset = dst.Calculator.GetOffset(dst.RegionX + x);
|
||||
|
||||
*(T*)(dstBase + dstOffset) = fillValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies data from one texture to another, while performing layout conversion and component shuffling if necessary.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Pixel type</typeparam>
|
||||
/// <param name="dstSpan">Destination texture memory region</param>
|
||||
/// <param name="srcSpan">Source texture memory region</param>
|
||||
/// <param name="dst">Destination texture parameters</param>
|
||||
/// <param name="src">Source texture parameters</param>
|
||||
private void CopyShuffle<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan, TextureParams dst, TextureParams src) where T : unmanaged
|
||||
{
|
||||
int dstComponents = (int)_state.State.SetRemapComponentsNumDstComponents + 1;
|
||||
|
||||
for (int i = 0; i < dstComponents; i++)
|
||||
{
|
||||
SetRemapComponentsDst componentsDst = i switch
|
||||
{
|
||||
0 => _state.State.SetRemapComponentsDstX,
|
||||
1 => _state.State.SetRemapComponentsDstY,
|
||||
2 => _state.State.SetRemapComponentsDstZ,
|
||||
_ => _state.State.SetRemapComponentsDstW
|
||||
};
|
||||
|
||||
switch (componentsDst)
|
||||
{
|
||||
case SetRemapComponentsDst.SrcX:
|
||||
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan, dst, src);
|
||||
break;
|
||||
case SetRemapComponentsDst.SrcY:
|
||||
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>()), dst, src);
|
||||
break;
|
||||
case SetRemapComponentsDst.SrcZ:
|
||||
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 2), dst, src);
|
||||
break;
|
||||
case SetRemapComponentsDst.SrcW:
|
||||
Copy<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), srcSpan.Slice(Unsafe.SizeOf<T>() * 3), dst, src);
|
||||
break;
|
||||
case SetRemapComponentsDst.ConstA:
|
||||
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstA));
|
||||
break;
|
||||
case SetRemapComponentsDst.ConstB:
|
||||
Fill<T>(dstSpan.Slice(Unsafe.SizeOf<T>() * i), dst, Unsafe.As<uint, T>(ref _state.State.SetRemapConstB));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with block linear GOBs to a block linear destination with linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobBlockLinearToLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(ConvertGobLinearToBlockLinearAddress(srcGpuVa + offset), true);
|
||||
memoryManager.Write(dstGpuVa + offset, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies block linear data with linear GOBs to a block linear destination with block linear GOBs.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">GPU memory manager</param>
|
||||
/// <param name="srcGpuVa">Source GPU virtual address</param>
|
||||
/// <param name="dstGpuVa">Destination GPU virtual address</param>
|
||||
/// <param name="size">Size in bytes of the copy</param>
|
||||
private static void CopyGobLinearToBlockLinear(MemoryManager memoryManager, ulong srcGpuVa, ulong dstGpuVa, ulong size)
|
||||
{
|
||||
if (((srcGpuVa | dstGpuVa | size) & 0xf) == 0)
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset += 16)
|
||||
{
|
||||
Vector128<byte> data = memoryManager.Read<Vector128<byte>>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ulong offset = 0; offset < size; offset++)
|
||||
{
|
||||
byte data = memoryManager.Read<byte>(srcGpuVa + offset, true);
|
||||
memoryManager.Write(ConvertGobLinearToBlockLinearAddress(dstGpuVa + offset), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the GOB block linear address from a linear address.
|
||||
/// </summary>
|
||||
/// <param name="address">Linear address</param>
|
||||
/// <returns>Block linear address</returns>
|
||||
private static ulong ConvertGobLinearToBlockLinearAddress(ulong address)
|
||||
{
|
||||
// y2 y1 y0 x5 x4 x3 x2 x1 x0 -> x5 y2 y1 x4 y0 x3 x2 x1 x0
|
||||
return (address & ~0x1f0UL) |
|
||||
((address & 0x40) >> 2) |
|
||||
((address & 0x10) << 1) |
|
||||
((address & 0x180) >> 1) |
|
||||
((address & 0x20) << 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a buffer to buffer, or buffer to texture copy, then optionally releases a semaphore.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
{
|
||||
DmaCopy(argument);
|
||||
ReleaseSemaphore(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
271
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs
Normal file
271
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
/// <summary>
|
||||
/// Physical mode target.
|
||||
/// </summary>
|
||||
enum SetPhysModeTarget
|
||||
{
|
||||
LocalFb = 0,
|
||||
CoherentSysmem = 1,
|
||||
NoncoherentSysmem = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA data transfer type.
|
||||
/// </summary>
|
||||
enum LaunchDmaDataTransferType
|
||||
{
|
||||
None = 0,
|
||||
Pipelined = 1,
|
||||
NonPipelined = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore type.
|
||||
/// </summary>
|
||||
enum LaunchDmaSemaphoreType
|
||||
{
|
||||
None = 0,
|
||||
ReleaseOneWordSemaphore = 1,
|
||||
ReleaseFourWordSemaphore = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA interrupt type.
|
||||
/// </summary>
|
||||
enum LaunchDmaInterruptType
|
||||
{
|
||||
None = 0,
|
||||
Blocking = 1,
|
||||
NonBlocking = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA destination memory layout.
|
||||
/// </summary>
|
||||
enum LaunchDmaMemoryLayout
|
||||
{
|
||||
Blocklinear = 0,
|
||||
Pitch = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA type.
|
||||
/// </summary>
|
||||
enum LaunchDmaType
|
||||
{
|
||||
Virtual = 0,
|
||||
Physical = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction operation.
|
||||
/// </summary>
|
||||
enum LaunchDmaSemaphoreReduction
|
||||
{
|
||||
Imin = 0,
|
||||
Imax = 1,
|
||||
Ixor = 2,
|
||||
Iand = 3,
|
||||
Ior = 4,
|
||||
Iadd = 5,
|
||||
Inc = 6,
|
||||
Dec = 7,
|
||||
Fadd = 10,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction signedness.
|
||||
/// </summary>
|
||||
enum LaunchDmaSemaphoreReductionSign
|
||||
{
|
||||
Signed = 0,
|
||||
Unsigned = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA L2 cache bypass.
|
||||
/// </summary>
|
||||
enum LaunchDmaBypassL2
|
||||
{
|
||||
UsePteSetting = 0,
|
||||
ForceVolatile = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA component remapping source component.
|
||||
/// </summary>
|
||||
enum SetRemapComponentsDst
|
||||
{
|
||||
SrcX = 0,
|
||||
SrcY = 1,
|
||||
SrcZ = 2,
|
||||
SrcW = 3,
|
||||
ConstA = 4,
|
||||
ConstB = 5,
|
||||
NoWrite = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA component remapping component size.
|
||||
/// </summary>
|
||||
enum SetRemapComponentsComponentSize
|
||||
{
|
||||
One = 0,
|
||||
Two = 1,
|
||||
Three = 2,
|
||||
Four = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA component remapping number of components.
|
||||
/// </summary>
|
||||
enum SetRemapComponentsNumComponents
|
||||
{
|
||||
One = 0,
|
||||
Two = 1,
|
||||
Three = 2,
|
||||
Four = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Width in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetBlockSizeWidth
|
||||
{
|
||||
QuarterGob = 14,
|
||||
OneGob = 0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetBlockSizeHeight
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetBlockSizeDepth
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height of a single GOB in lines.
|
||||
/// </summary>
|
||||
enum SetBlockSizeGobHeight
|
||||
{
|
||||
GobHeightTesla4 = 0,
|
||||
GobHeightFermi8 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA copy class state.
|
||||
/// </summary>
|
||||
unsafe struct DmaClassState
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public fixed uint Reserved00[64];
|
||||
public uint Nop;
|
||||
public fixed uint Reserved104[15];
|
||||
public uint PmTrigger;
|
||||
public fixed uint Reserved144[63];
|
||||
public uint SetSemaphoreA;
|
||||
public int SetSemaphoreAUpper => (int)((SetSemaphoreA >> 0) & 0xFF);
|
||||
public uint SetSemaphoreB;
|
||||
public uint SetSemaphorePayload;
|
||||
public fixed uint Reserved24C[2];
|
||||
public uint SetRenderEnableA;
|
||||
public int SetRenderEnableAUpper => (int)((SetRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetRenderEnableB;
|
||||
public uint SetRenderEnableC;
|
||||
public int SetRenderEnableCMode => (int)((SetRenderEnableC >> 0) & 0x7);
|
||||
public uint SetSrcPhysMode;
|
||||
public SetPhysModeTarget SetSrcPhysModeTarget => (SetPhysModeTarget)((SetSrcPhysMode >> 0) & 0x3);
|
||||
public uint SetDstPhysMode;
|
||||
public SetPhysModeTarget SetDstPhysModeTarget => (SetPhysModeTarget)((SetDstPhysMode >> 0) & 0x3);
|
||||
public fixed uint Reserved268[38];
|
||||
public uint LaunchDma;
|
||||
public LaunchDmaDataTransferType LaunchDmaDataTransferType => (LaunchDmaDataTransferType)((LaunchDma >> 0) & 0x3);
|
||||
public bool LaunchDmaFlushEnable => (LaunchDma & 0x4) != 0;
|
||||
public LaunchDmaSemaphoreType LaunchDmaSemaphoreType => (LaunchDmaSemaphoreType)((LaunchDma >> 3) & 0x3);
|
||||
public LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 5) & 0x3);
|
||||
public LaunchDmaMemoryLayout LaunchDmaSrcMemoryLayout => (LaunchDmaMemoryLayout)((LaunchDma >> 7) & 0x1);
|
||||
public LaunchDmaMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaMemoryLayout)((LaunchDma >> 8) & 0x1);
|
||||
public bool LaunchDmaMultiLineEnable => (LaunchDma & 0x200) != 0;
|
||||
public bool LaunchDmaRemapEnable => (LaunchDma & 0x400) != 0;
|
||||
public bool LaunchDmaForceRmwdisable => (LaunchDma & 0x800) != 0;
|
||||
public LaunchDmaType LaunchDmaSrcType => (LaunchDmaType)((LaunchDma >> 12) & 0x1);
|
||||
public LaunchDmaType LaunchDmaDstType => (LaunchDmaType)((LaunchDma >> 13) & 0x1);
|
||||
public LaunchDmaSemaphoreReduction LaunchDmaSemaphoreReduction => (LaunchDmaSemaphoreReduction)((LaunchDma >> 14) & 0xF);
|
||||
public LaunchDmaSemaphoreReductionSign LaunchDmaSemaphoreReductionSign => (LaunchDmaSemaphoreReductionSign)((LaunchDma >> 18) & 0x1);
|
||||
public bool LaunchDmaSemaphoreReductionEnable => (LaunchDma & 0x80000) != 0;
|
||||
public LaunchDmaBypassL2 LaunchDmaBypassL2 => (LaunchDmaBypassL2)((LaunchDma >> 20) & 0x1);
|
||||
public fixed uint Reserved304[63];
|
||||
public uint OffsetInUpper;
|
||||
public int OffsetInUpperUpper => (int)((OffsetInUpper >> 0) & 0xFF);
|
||||
public uint OffsetInLower;
|
||||
public uint OffsetOutUpper;
|
||||
public int OffsetOutUpperUpper => (int)((OffsetOutUpper >> 0) & 0xFF);
|
||||
public uint OffsetOutLower;
|
||||
public uint PitchIn;
|
||||
public uint PitchOut;
|
||||
public uint LineLengthIn;
|
||||
public uint LineCount;
|
||||
public fixed uint Reserved420[184];
|
||||
public uint SetRemapConstA;
|
||||
public uint SetRemapConstB;
|
||||
public uint SetRemapComponents;
|
||||
public SetRemapComponentsDst SetRemapComponentsDstX => (SetRemapComponentsDst)((SetRemapComponents >> 0) & 0x7);
|
||||
public SetRemapComponentsDst SetRemapComponentsDstY => (SetRemapComponentsDst)((SetRemapComponents >> 4) & 0x7);
|
||||
public SetRemapComponentsDst SetRemapComponentsDstZ => (SetRemapComponentsDst)((SetRemapComponents >> 8) & 0x7);
|
||||
public SetRemapComponentsDst SetRemapComponentsDstW => (SetRemapComponentsDst)((SetRemapComponents >> 12) & 0x7);
|
||||
public SetRemapComponentsComponentSize SetRemapComponentsComponentSize => (SetRemapComponentsComponentSize)((SetRemapComponents >> 16) & 0x3);
|
||||
public SetRemapComponentsNumComponents SetRemapComponentsNumSrcComponents => (SetRemapComponentsNumComponents)((SetRemapComponents >> 20) & 0x3);
|
||||
public SetRemapComponentsNumComponents SetRemapComponentsNumDstComponents => (SetRemapComponentsNumComponents)((SetRemapComponents >> 24) & 0x3);
|
||||
public uint SetDstBlockSize;
|
||||
public SetBlockSizeWidth SetDstBlockSizeWidth => (SetBlockSizeWidth)((SetDstBlockSize >> 0) & 0xF);
|
||||
public SetBlockSizeHeight SetDstBlockSizeHeight => (SetBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF);
|
||||
public SetBlockSizeDepth SetDstBlockSizeDepth => (SetBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF);
|
||||
public SetBlockSizeGobHeight SetDstBlockSizeGobHeight => (SetBlockSizeGobHeight)((SetDstBlockSize >> 12) & 0xF);
|
||||
public uint SetDstWidth;
|
||||
public uint SetDstHeight;
|
||||
public uint SetDstDepth;
|
||||
public uint SetDstLayer;
|
||||
public uint SetDstOrigin;
|
||||
public int SetDstOriginX => (int)((SetDstOrigin >> 0) & 0xFFFF);
|
||||
public int SetDstOriginY => (int)((SetDstOrigin >> 16) & 0xFFFF);
|
||||
public uint Reserved724;
|
||||
public uint SetSrcBlockSize;
|
||||
public SetBlockSizeWidth SetSrcBlockSizeWidth => (SetBlockSizeWidth)((SetSrcBlockSize >> 0) & 0xF);
|
||||
public SetBlockSizeHeight SetSrcBlockSizeHeight => (SetBlockSizeHeight)((SetSrcBlockSize >> 4) & 0xF);
|
||||
public SetBlockSizeDepth SetSrcBlockSizeDepth => (SetBlockSizeDepth)((SetSrcBlockSize >> 8) & 0xF);
|
||||
public SetBlockSizeGobHeight SetSrcBlockSizeGobHeight => (SetBlockSizeGobHeight)((SetSrcBlockSize >> 12) & 0xF);
|
||||
public uint SetSrcWidth;
|
||||
public uint SetSrcHeight;
|
||||
public uint SetSrcDepth;
|
||||
public uint SetSrcLayer;
|
||||
public uint SetSrcOrigin;
|
||||
public int SetSrcOriginX => (int)((SetSrcOrigin >> 0) & 0xFFFF);
|
||||
public int SetSrcOriginY => (int)((SetSrcOrigin >> 16) & 0xFFFF);
|
||||
public fixed uint Reserved740[629];
|
||||
public uint PmTriggerEnd;
|
||||
public fixed uint Reserved1118[2490];
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
20
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs
Normal file
20
src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer to texture copy parameters.
|
||||
/// </summary>
|
||||
struct DmaTexture
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public MemoryLayout MemoryLayout;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int Depth;
|
||||
public int RegionZ;
|
||||
public ushort RegionX;
|
||||
public ushort RegionY;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
41
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs
Normal file
41
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
enum TertOp
|
||||
{
|
||||
Grp0IncMethod = 0,
|
||||
Grp0SetSubDevMask = 1,
|
||||
Grp0StoreSubDevMask = 2,
|
||||
Grp0UseSubDevMask = 3,
|
||||
Grp2NonIncMethod = 0
|
||||
}
|
||||
|
||||
enum SecOp
|
||||
{
|
||||
Grp0UseTert = 0,
|
||||
IncMethod = 1,
|
||||
Grp2UseTert = 2,
|
||||
NonIncMethod = 3,
|
||||
ImmdDataMethod = 4,
|
||||
OneInc = 5,
|
||||
Reserved6 = 6,
|
||||
EndPbSegment = 7
|
||||
}
|
||||
|
||||
struct CompressedMethod
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint Method;
|
||||
#pragma warning restore CS0649
|
||||
public int MethodAddressOld => (int)((Method >> 2) & 0x7FF);
|
||||
public int MethodAddress => (int)((Method >> 0) & 0xFFF);
|
||||
public int SubdeviceMask => (int)((Method >> 4) & 0xFFF);
|
||||
public int MethodSubchannel => (int)((Method >> 13) & 0x7);
|
||||
public TertOp TertOp => (TertOp)((Method >> 16) & 0x3);
|
||||
public int MethodCountOld => (int)((Method >> 18) & 0x7FF);
|
||||
public int MethodCount => (int)((Method >> 16) & 0x1FFF);
|
||||
public int ImmdData => (int)((Method >> 16) & 0x1FFF);
|
||||
public SecOp SecOp => (SecOp)((Method >> 29) & 0x7);
|
||||
}
|
||||
}
|
||||
55
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs
Normal file
55
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
enum Entry0Fetch
|
||||
{
|
||||
Unconditional = 0,
|
||||
Conditional = 1,
|
||||
}
|
||||
|
||||
enum Entry1Priv
|
||||
{
|
||||
User = 0,
|
||||
Kernel = 1,
|
||||
}
|
||||
|
||||
enum Entry1Level
|
||||
{
|
||||
Main = 0,
|
||||
Subroutine = 1,
|
||||
}
|
||||
|
||||
enum Entry1Sync
|
||||
{
|
||||
Proceed = 0,
|
||||
Wait = 1,
|
||||
}
|
||||
|
||||
enum Entry1Opcode
|
||||
{
|
||||
Nop = 0,
|
||||
Illegal = 1,
|
||||
Crc = 2,
|
||||
PbCrc = 3,
|
||||
}
|
||||
|
||||
struct GPEntry
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint Entry0;
|
||||
#pragma warning restore CS0649
|
||||
public Entry0Fetch Entry0Fetch => (Entry0Fetch)((Entry0 >> 0) & 0x1);
|
||||
public int Entry0Get => (int)((Entry0 >> 2) & 0x3FFFFFFF);
|
||||
public int Entry0Operand => (int)(Entry0);
|
||||
#pragma warning disable CS0649
|
||||
public uint Entry1;
|
||||
#pragma warning restore CS0649
|
||||
public int Entry1GetHi => (int)((Entry1 >> 0) & 0xFF);
|
||||
public Entry1Priv Entry1Priv => (Entry1Priv)((Entry1 >> 8) & 0x1);
|
||||
public Entry1Level Entry1Level => (Entry1Level)((Entry1 >> 9) & 0x1);
|
||||
public int Entry1Length => (int)((Entry1 >> 10) & 0x1FFFFF);
|
||||
public Entry1Sync Entry1Sync => (Entry1Sync)((Entry1 >> 31) & 0x1);
|
||||
public Entry1Opcode Entry1Opcode => (Entry1Opcode)((Entry1 >> 0) & 0xFF);
|
||||
}
|
||||
}
|
||||
248
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
Normal file
248
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.MME;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GPU General Purpose FIFO class.
|
||||
/// </summary>
|
||||
class GPFifoClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GPFifoProcessor _parent;
|
||||
private readonly DeviceState<GPFifoClassState> _state;
|
||||
|
||||
private int _previousSubChannel;
|
||||
private bool _createSyncPending;
|
||||
|
||||
private const int MacrosCount = 0x80;
|
||||
|
||||
// Note: The size of the macro memory is unknown, we just make
|
||||
// a guess here and use 256kb as the size. Increase if needed.
|
||||
private const int MacroCodeSize = 256 * 256;
|
||||
|
||||
private readonly Macro[] _macros;
|
||||
private readonly int[] _macroCode;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU General Purpose FIFO class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="parent">Parent GPU General Purpose FIFO processor</param>
|
||||
public GPFifoClass(GpuContext context, GPFifoProcessor parent)
|
||||
{
|
||||
_context = context;
|
||||
_parent = parent;
|
||||
_state = new DeviceState<GPFifoClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(GPFifoClassState.Semaphored), new RwCallback(Semaphored, null) },
|
||||
{ nameof(GPFifoClassState.Syncpointb), new RwCallback(Syncpointb, null) },
|
||||
{ nameof(GPFifoClassState.WaitForIdle), new RwCallback(WaitForIdle, null) },
|
||||
{ nameof(GPFifoClassState.SetReference), new RwCallback(SetReference, null) },
|
||||
{ nameof(GPFifoClassState.LoadMmeInstructionRam), new RwCallback(LoadMmeInstructionRam, null) },
|
||||
{ nameof(GPFifoClassState.LoadMmeStartAddressRam), new RwCallback(LoadMmeStartAddressRam, null) },
|
||||
{ nameof(GPFifoClassState.SetMmeShadowRamControl), new RwCallback(SetMmeShadowRamControl, null) }
|
||||
});
|
||||
|
||||
_macros = new Macro[MacrosCount];
|
||||
_macroCode = new int[MacroCodeSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create any syncs from WaitForIdle command that are currently pending.
|
||||
/// </summary>
|
||||
public void CreatePendingSyncs()
|
||||
{
|
||||
if (_createSyncPending)
|
||||
{
|
||||
_createSyncPending = false;
|
||||
_context.CreateHostSyncIfNeeded(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a GPU counter to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void Semaphored(int argument)
|
||||
{
|
||||
ulong address = ((ulong)_state.State.SemaphorebOffsetLower << 2) |
|
||||
((ulong)_state.State.SemaphoreaOffsetUpper << 32);
|
||||
|
||||
int value = _state.State.SemaphorecPayload;
|
||||
|
||||
SemaphoredOperation operation = _state.State.SemaphoredOperation;
|
||||
|
||||
if (_state.State.SemaphoredReleaseSize == SemaphoredReleaseSize.SixteenBytes)
|
||||
{
|
||||
_parent.MemoryManager.Write(address + 4, 0);
|
||||
_parent.MemoryManager.Write(address + 8, _context.GetTimestamp());
|
||||
}
|
||||
|
||||
// TODO: Acquire operations (Wait), interrupts for invalid combinations.
|
||||
if (operation == SemaphoredOperation.Release)
|
||||
{
|
||||
_parent.MemoryManager.Write(address, value);
|
||||
}
|
||||
else if (operation == SemaphoredOperation.Reduction)
|
||||
{
|
||||
bool signed = _state.State.SemaphoredFormat == SemaphoredFormat.Signed;
|
||||
|
||||
int mem = _parent.MemoryManager.Read<int>(address);
|
||||
|
||||
switch (_state.State.SemaphoredReduction)
|
||||
{
|
||||
case SemaphoredReduction.Min:
|
||||
value = signed ? Math.Min(mem, value) : (int)Math.Min((uint)mem, (uint)value);
|
||||
break;
|
||||
case SemaphoredReduction.Max:
|
||||
value = signed ? Math.Max(mem, value) : (int)Math.Max((uint)mem, (uint)value);
|
||||
break;
|
||||
case SemaphoredReduction.Xor:
|
||||
value ^= mem;
|
||||
break;
|
||||
case SemaphoredReduction.And:
|
||||
value &= mem;
|
||||
break;
|
||||
case SemaphoredReduction.Or:
|
||||
value |= mem;
|
||||
break;
|
||||
case SemaphoredReduction.Add:
|
||||
value += mem;
|
||||
break;
|
||||
case SemaphoredReduction.Inc:
|
||||
value = (uint)mem < (uint)value ? mem + 1 : 0;
|
||||
break;
|
||||
case SemaphoredReduction.Dec:
|
||||
value = (uint)mem > 0 && (uint)mem <= (uint)value ? mem - 1 : value;
|
||||
break;
|
||||
}
|
||||
|
||||
_parent.MemoryManager.Write(address, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a fence operation on a syncpoint.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void Syncpointb(int argument)
|
||||
{
|
||||
SyncpointbOperation operation = _state.State.SyncpointbOperation;
|
||||
|
||||
uint syncpointId = (uint)_state.State.SyncpointbSyncptIndex;
|
||||
|
||||
if (operation == SyncpointbOperation.Wait)
|
||||
{
|
||||
uint threshold = (uint)_state.State.SyncpointaPayload;
|
||||
|
||||
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
else if (operation == SyncpointbOperation.Incr)
|
||||
{
|
||||
_context.CreateHostSyncIfNeeded(true, true);
|
||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||
}
|
||||
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the GPU to be idle.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void WaitForIdle(int argument)
|
||||
{
|
||||
_parent.PerformDeferredDraws();
|
||||
_context.Renderer.Pipeline.Barrier();
|
||||
|
||||
_createSyncPending = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used as an indirect data barrier on NVN. When used, access to previously written data must be coherent.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void SetReference(int argument)
|
||||
{
|
||||
_context.Renderer.Pipeline.CommandBufferBarrier();
|
||||
|
||||
_context.CreateHostSyncIfNeeded(false, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends macro code/data to the MME.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadMmeInstructionRam(int argument)
|
||||
{
|
||||
_macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a macro index to a position for the MME
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadMmeStartAddressRam(int argument)
|
||||
{
|
||||
_macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the shadow RAM control.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void SetMmeShadowRamControl(int argument)
|
||||
{
|
||||
_parent.SetShadowRamControl(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an argument to a macro.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the macro</param>
|
||||
/// <param name="gpuVa">GPU virtual address where the command word is located</param>
|
||||
/// <param name="argument">Argument to be pushed to the macro</param>
|
||||
public void MmePushArgument(int index, ulong gpuVa, int argument)
|
||||
{
|
||||
_macros[index].PushArgument(gpuVa, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares a macro for execution.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the macro</param>
|
||||
/// <param name="argument">Initial argument passed to the macro</param>
|
||||
public void MmeStart(int index, int argument)
|
||||
{
|
||||
_macros[index].StartExecution(_context, _parent, _macroCode, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a macro.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the macro</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
public void CallMme(int index, IDeviceState state)
|
||||
{
|
||||
_macros[index].Execute(_macroCode, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
233
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs
Normal file
233
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
using Ryujinx.Common.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
/// <summary>
|
||||
/// Semaphore operation.
|
||||
/// </summary>
|
||||
enum SemaphoredOperation
|
||||
{
|
||||
Acquire = 1,
|
||||
Release = 2,
|
||||
AcqGeq = 4,
|
||||
AcqAnd = 8,
|
||||
Reduction = 16
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore acquire switch enable.
|
||||
/// </summary>
|
||||
enum SemaphoredAcquireSwitch
|
||||
{
|
||||
Disabled = 0,
|
||||
Enabled = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore release interrupt wait enable.
|
||||
/// </summary>
|
||||
enum SemaphoredReleaseWfi
|
||||
{
|
||||
En = 0,
|
||||
Dis = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore release structure size.
|
||||
/// </summary>
|
||||
enum SemaphoredReleaseSize
|
||||
{
|
||||
SixteenBytes = 0,
|
||||
FourBytes = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore reduction operation.
|
||||
/// </summary>
|
||||
enum SemaphoredReduction
|
||||
{
|
||||
Min = 0,
|
||||
Max = 1,
|
||||
Xor = 2,
|
||||
And = 3,
|
||||
Or = 4,
|
||||
Add = 5,
|
||||
Inc = 6,
|
||||
Dec = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Semaphore format.
|
||||
/// </summary>
|
||||
enum SemaphoredFormat
|
||||
{
|
||||
Signed = 0,
|
||||
Unsigned = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory Translation Lookaside Buffer Page Directory Buffer invalidation.
|
||||
/// </summary>
|
||||
enum MemOpCTlbInvalidatePdb
|
||||
{
|
||||
One = 0,
|
||||
All = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory Translation Lookaside Buffer GPC invalidation enable.
|
||||
/// </summary>
|
||||
enum MemOpCTlbInvalidateGpc
|
||||
{
|
||||
Enable = 0,
|
||||
Disable = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory Translation Lookaside Buffer invalidation target.
|
||||
/// </summary>
|
||||
enum MemOpCTlbInvalidateTarget
|
||||
{
|
||||
VidMem = 0,
|
||||
SysMemCoherent = 2,
|
||||
SysMemNoncoherent = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory operation.
|
||||
/// </summary>
|
||||
enum MemOpDOperation
|
||||
{
|
||||
Membar = 5,
|
||||
MmuTlbInvalidate = 9,
|
||||
L2PeermemInvalidate = 13,
|
||||
L2SysmemInvalidate = 14,
|
||||
L2CleanComptags = 15,
|
||||
L2FlushDirty = 16
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncpoint operation.
|
||||
/// </summary>
|
||||
enum SyncpointbOperation
|
||||
{
|
||||
Wait = 0,
|
||||
Incr = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncpoint wait switch enable.
|
||||
/// </summary>
|
||||
enum SyncpointbWaitSwitch
|
||||
{
|
||||
Dis = 0,
|
||||
En = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for interrupt scope.
|
||||
/// </summary>
|
||||
enum WfiScope
|
||||
{
|
||||
CurrentScgType = 0,
|
||||
All = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Yield operation.
|
||||
/// </summary>
|
||||
enum YieldOp
|
||||
{
|
||||
Nop = 0,
|
||||
PbdmaTimeslice = 1,
|
||||
RunlistTimeslice = 2,
|
||||
Tsg = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// General Purpose FIFO class state.
|
||||
/// </summary>
|
||||
struct GPFifoClassState
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint SetObject;
|
||||
public int SetObjectNvclass => (int)((SetObject >> 0) & 0xFFFF);
|
||||
public int SetObjectEngine => (int)((SetObject >> 16) & 0x1F);
|
||||
public uint Illegal;
|
||||
public int IllegalHandle => (int)(Illegal);
|
||||
public uint Nop;
|
||||
public int NopHandle => (int)(Nop);
|
||||
public uint Reserved0C;
|
||||
public uint Semaphorea;
|
||||
public int SemaphoreaOffsetUpper => (int)((Semaphorea >> 0) & 0xFF);
|
||||
public uint Semaphoreb;
|
||||
public int SemaphorebOffsetLower => (int)((Semaphoreb >> 2) & 0x3FFFFFFF);
|
||||
public uint Semaphorec;
|
||||
public int SemaphorecPayload => (int)(Semaphorec);
|
||||
public uint Semaphored;
|
||||
public SemaphoredOperation SemaphoredOperation => (SemaphoredOperation)((Semaphored >> 0) & 0x1F);
|
||||
public SemaphoredAcquireSwitch SemaphoredAcquireSwitch => (SemaphoredAcquireSwitch)((Semaphored >> 12) & 0x1);
|
||||
public SemaphoredReleaseWfi SemaphoredReleaseWfi => (SemaphoredReleaseWfi)((Semaphored >> 20) & 0x1);
|
||||
public SemaphoredReleaseSize SemaphoredReleaseSize => (SemaphoredReleaseSize)((Semaphored >> 24) & 0x1);
|
||||
public SemaphoredReduction SemaphoredReduction => (SemaphoredReduction)((Semaphored >> 27) & 0xF);
|
||||
public SemaphoredFormat SemaphoredFormat => (SemaphoredFormat)((Semaphored >> 31) & 0x1);
|
||||
public uint NonStallInterrupt;
|
||||
public int NonStallInterruptHandle => (int)(NonStallInterrupt);
|
||||
public uint FbFlush;
|
||||
public int FbFlushHandle => (int)(FbFlush);
|
||||
public uint Reserved28;
|
||||
public uint Reserved2C;
|
||||
public uint MemOpC;
|
||||
public int MemOpCOperandLow => (int)((MemOpC >> 2) & 0x3FFFFFFF);
|
||||
public MemOpCTlbInvalidatePdb MemOpCTlbInvalidatePdb => (MemOpCTlbInvalidatePdb)((MemOpC >> 0) & 0x1);
|
||||
public MemOpCTlbInvalidateGpc MemOpCTlbInvalidateGpc => (MemOpCTlbInvalidateGpc)((MemOpC >> 1) & 0x1);
|
||||
public MemOpCTlbInvalidateTarget MemOpCTlbInvalidateTarget => (MemOpCTlbInvalidateTarget)((MemOpC >> 10) & 0x3);
|
||||
public int MemOpCTlbInvalidateAddrLo => (int)((MemOpC >> 12) & 0xFFFFF);
|
||||
public uint MemOpD;
|
||||
public int MemOpDOperandHigh => (int)((MemOpD >> 0) & 0xFF);
|
||||
public MemOpDOperation MemOpDOperation => (MemOpDOperation)((MemOpD >> 27) & 0x1F);
|
||||
public int MemOpDTlbInvalidateAddrHi => (int)((MemOpD >> 0) & 0xFF);
|
||||
public uint Reserved38;
|
||||
public uint Reserved3C;
|
||||
public uint Reserved40;
|
||||
public uint Reserved44;
|
||||
public uint Reserved48;
|
||||
public uint Reserved4C;
|
||||
public uint SetReference;
|
||||
public int SetReferenceCount => (int)(SetReference);
|
||||
public uint Reserved54;
|
||||
public uint Reserved58;
|
||||
public uint Reserved5C;
|
||||
public uint Reserved60;
|
||||
public uint Reserved64;
|
||||
public uint Reserved68;
|
||||
public uint Reserved6C;
|
||||
public uint Syncpointa;
|
||||
public int SyncpointaPayload => (int)(Syncpointa);
|
||||
public uint Syncpointb;
|
||||
public SyncpointbOperation SyncpointbOperation => (SyncpointbOperation)((Syncpointb >> 0) & 0x1);
|
||||
public SyncpointbWaitSwitch SyncpointbWaitSwitch => (SyncpointbWaitSwitch)((Syncpointb >> 4) & 0x1);
|
||||
public int SyncpointbSyncptIndex => (int)((Syncpointb >> 8) & 0xFFF);
|
||||
public uint Wfi;
|
||||
public WfiScope WfiScope => (WfiScope)((Wfi >> 0) & 0x1);
|
||||
public uint CrcCheck;
|
||||
public int CrcCheckValue => (int)(CrcCheck);
|
||||
public uint Yield;
|
||||
public YieldOp YieldOp => (YieldOp)((Yield >> 0) & 0x3);
|
||||
// TODO: Eventually move this to per-engine state.
|
||||
public Array31<uint> Reserved84;
|
||||
public uint NoOperation;
|
||||
public uint SetNotifyA;
|
||||
public uint SetNotifyB;
|
||||
public uint Notify;
|
||||
public uint WaitForIdle;
|
||||
public uint LoadMmeInstructionRamPointer;
|
||||
public uint LoadMmeInstructionRam;
|
||||
public uint LoadMmeStartAddressRamPointer;
|
||||
public uint LoadMmeStartAddressRam;
|
||||
public uint SetMmeShadowRamControl;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
262
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs
Normal file
262
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GPU General Purpose FIFO device.
|
||||
/// </summary>
|
||||
public sealed class GPFifoDevice : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if the command buffer has pre-fetch enabled.
|
||||
/// </summary>
|
||||
private enum CommandBufferType
|
||||
{
|
||||
Prefetch,
|
||||
NoPrefetch
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command buffer data.
|
||||
/// </summary>
|
||||
private struct CommandBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Processor used to process the command buffer. Contains channel state.
|
||||
/// </summary>
|
||||
public GPFifoProcessor Processor;
|
||||
|
||||
/// <summary>
|
||||
/// The type of the command buffer.
|
||||
/// </summary>
|
||||
public CommandBufferType Type;
|
||||
|
||||
/// <summary>
|
||||
/// Fetched data.
|
||||
/// </summary>
|
||||
public int[] Words;
|
||||
|
||||
/// <summary>
|
||||
/// The GPFIFO entry address (used in <see cref="CommandBufferType.NoPrefetch"/> mode).
|
||||
/// </summary>
|
||||
public ulong EntryAddress;
|
||||
|
||||
/// <summary>
|
||||
/// The count of entries inside this GPFIFO entry.
|
||||
/// </summary>
|
||||
public uint EntryCount;
|
||||
|
||||
/// <summary>
|
||||
/// Get the entries for the command buffer from memory.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The memory manager used to fetch the data</param>
|
||||
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
|
||||
/// <returns>The fetched data</returns>
|
||||
private ReadOnlySpan<int> GetWords(MemoryManager memoryManager, bool flush)
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefetch the command buffer.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The memory manager used to fetch the data</param>
|
||||
public void Prefetch(MemoryManager memoryManager)
|
||||
{
|
||||
Words = GetWords(memoryManager, true).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the command buffer.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The memory manager used to fetch the data</param>
|
||||
/// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param>
|
||||
/// <returns>The command buffer words</returns>
|
||||
public ReadOnlySpan<int> Fetch(MemoryManager memoryManager, bool flush)
|
||||
{
|
||||
return Words ?? GetWords(memoryManager, flush);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ConcurrentQueue<CommandBuffer> _commandBufferQueue;
|
||||
|
||||
private CommandBuffer _currentCommandBuffer;
|
||||
private GPFifoProcessor _prevChannelProcessor;
|
||||
|
||||
private readonly bool _ibEnable;
|
||||
private readonly GpuContext _context;
|
||||
private readonly AutoResetEvent _event;
|
||||
|
||||
private bool _interrupt;
|
||||
private int _flushSkips;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU General Purpose FIFO device.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the GPFIFO belongs to</param>
|
||||
internal GPFifoDevice(GpuContext context)
|
||||
{
|
||||
_commandBufferQueue = new ConcurrentQueue<CommandBuffer>();
|
||||
_ibEnable = true;
|
||||
_context = context;
|
||||
_event = new AutoResetEvent(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal the FIFO that there are new entries to process.
|
||||
/// </summary>
|
||||
public void SignalNewEntries()
|
||||
{
|
||||
_event.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push a GPFIFO entry in the form of a prefetched command buffer.
|
||||
/// It is intended to be used by nvservices to handle special cases.
|
||||
/// </summary>
|
||||
/// <param name="processor">Processor used to process <paramref name="commandBuffer"/></param>
|
||||
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
|
||||
internal void PushHostCommandBuffer(GPFifoProcessor processor, int[] commandBuffer)
|
||||
{
|
||||
_commandBufferQueue.Enqueue(new CommandBuffer
|
||||
{
|
||||
Processor = processor,
|
||||
Type = CommandBufferType.Prefetch,
|
||||
Words = commandBuffer,
|
||||
EntryAddress = ulong.MaxValue,
|
||||
EntryCount = (uint)commandBuffer.Length
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a CommandBuffer from a GPFIFO entry.
|
||||
/// </summary>
|
||||
/// <param name="processor">Processor used to process the command buffer pointed to by <paramref name="entry"/></param>
|
||||
/// <param name="entry">The GPFIFO entry</param>
|
||||
/// <returns>A new CommandBuffer based on the GPFIFO entry</returns>
|
||||
private static CommandBuffer CreateCommandBuffer(GPFifoProcessor processor, GPEntry entry)
|
||||
{
|
||||
CommandBufferType type = CommandBufferType.Prefetch;
|
||||
|
||||
if (entry.Entry1Sync == Entry1Sync.Wait)
|
||||
{
|
||||
type = CommandBufferType.NoPrefetch;
|
||||
}
|
||||
|
||||
ulong startAddress = ((ulong)entry.Entry0Get << 2) | ((ulong)entry.Entry1GetHi << 32);
|
||||
|
||||
return new CommandBuffer
|
||||
{
|
||||
Processor = processor,
|
||||
Type = type,
|
||||
Words = null,
|
||||
EntryAddress = startAddress,
|
||||
EntryCount = (uint)entry.Entry1Length
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes GPFIFO entries.
|
||||
/// </summary>
|
||||
/// <param name="processor">Processor used to process the command buffers pointed to by <paramref name="entries"/></param>
|
||||
/// <param name="entries">GPFIFO entries</param>
|
||||
internal void PushEntries(GPFifoProcessor processor, ReadOnlySpan<ulong> entries)
|
||||
{
|
||||
bool beforeBarrier = true;
|
||||
|
||||
for (int index = 0; index < entries.Length; index++)
|
||||
{
|
||||
ulong entry = entries[index];
|
||||
|
||||
CommandBuffer commandBuffer = CreateCommandBuffer(processor, Unsafe.As<ulong, GPEntry>(ref entry));
|
||||
|
||||
if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch)
|
||||
{
|
||||
commandBuffer.Prefetch(processor.MemoryManager);
|
||||
}
|
||||
|
||||
if (commandBuffer.Type == CommandBufferType.NoPrefetch)
|
||||
{
|
||||
beforeBarrier = false;
|
||||
}
|
||||
|
||||
_commandBufferQueue.Enqueue(commandBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until commands are pushed to the FIFO.
|
||||
/// </summary>
|
||||
/// <returns>True if commands were received, false if wait timed out</returns>
|
||||
public bool WaitForCommands()
|
||||
{
|
||||
return !_commandBufferQueue.IsEmpty || (_event.WaitOne(8) && !_commandBufferQueue.IsEmpty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes commands pushed to the FIFO.
|
||||
/// </summary>
|
||||
public void DispatchCalls()
|
||||
{
|
||||
// Use this opportunity to also dispose any pending channels that were closed.
|
||||
_context.RunDeferredActions();
|
||||
|
||||
// Process command buffers.
|
||||
while (_ibEnable && !_interrupt && _commandBufferQueue.TryDequeue(out CommandBuffer entry))
|
||||
{
|
||||
bool flushCommandBuffer = true;
|
||||
|
||||
if (_flushSkips != 0)
|
||||
{
|
||||
_flushSkips--;
|
||||
flushCommandBuffer = false;
|
||||
}
|
||||
|
||||
_currentCommandBuffer = entry;
|
||||
ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer);
|
||||
|
||||
// If we are changing the current channel,
|
||||
// we need to force all the host state to be updated.
|
||||
if (_prevChannelProcessor != entry.Processor)
|
||||
{
|
||||
_prevChannelProcessor = entry.Processor;
|
||||
entry.Processor.ForceAllDirty();
|
||||
}
|
||||
|
||||
entry.Processor.Process(entry.EntryAddress, words);
|
||||
}
|
||||
|
||||
_interrupt = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the number of flushes that should be skipped for subsequent command buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can improve performance when command buffer data only needs to be consumed by the GPU.
|
||||
/// </remarks>
|
||||
/// <param name="count">The amount of flushes that should be skipped</param>
|
||||
internal void SetFlushSkips(int count)
|
||||
{
|
||||
_flushSkips = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interrupts command processing. This will break out of the DispatchCalls loop.
|
||||
/// </summary>
|
||||
public void Interrupt()
|
||||
{
|
||||
_interrupt = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of resources used for GPFifo command processing.
|
||||
/// </summary>
|
||||
public void Dispose() => _event.Dispose();
|
||||
}
|
||||
}
|
||||
331
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
Normal file
331
src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Compute;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Dma;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Twod;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GPU General Purpose FIFO command processor.
|
||||
/// </summary>
|
||||
class GPFifoProcessor
|
||||
{
|
||||
private const int MacrosCount = 0x80;
|
||||
private const int MacroIndexMask = MacrosCount - 1;
|
||||
|
||||
private const int LoadInlineDataMethodOffset = 0x6d;
|
||||
private const int UniformBufferUpdateDataMethodOffset = 0x8e4;
|
||||
|
||||
private readonly GpuChannel _channel;
|
||||
|
||||
/// <summary>
|
||||
/// Channel memory manager.
|
||||
/// </summary>
|
||||
public MemoryManager MemoryManager => _channel.MemoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// 3D Engine.
|
||||
/// </summary>
|
||||
public ThreedClass ThreedClass => _3dClass;
|
||||
|
||||
/// <summary>
|
||||
/// Internal GPFIFO state.
|
||||
/// </summary>
|
||||
private struct DmaState
|
||||
{
|
||||
public int Method;
|
||||
public int SubChannel;
|
||||
public int MethodCount;
|
||||
public bool NonIncrementing;
|
||||
public bool IncrementOnce;
|
||||
}
|
||||
|
||||
private DmaState _state;
|
||||
|
||||
private readonly ThreedClass _3dClass;
|
||||
private readonly ComputeClass _computeClass;
|
||||
private readonly InlineToMemoryClass _i2mClass;
|
||||
private readonly TwodClass _2dClass;
|
||||
private readonly DmaClass _dmaClass;
|
||||
|
||||
private readonly GPFifoClass _fifoClass;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU General Purpose FIFO command processor.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">Channel that the GPFIFO processor belongs to</param>
|
||||
public GPFifoProcessor(GpuContext context, GpuChannel channel)
|
||||
{
|
||||
_channel = channel;
|
||||
|
||||
_fifoClass = new GPFifoClass(context, this);
|
||||
_3dClass = new ThreedClass(context, channel, _fifoClass);
|
||||
_computeClass = new ComputeClass(context, channel, _3dClass);
|
||||
_i2mClass = new InlineToMemoryClass(context, channel);
|
||||
_2dClass = new TwodClass(channel);
|
||||
_dmaClass = new DmaClass(context, channel, _3dClass);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a command buffer.
|
||||
/// </summary>
|
||||
/// <param name="baseGpuVa">Base GPU virtual address of the command buffer</param>
|
||||
/// <param name="commandBuffer">Command buffer</param>
|
||||
public void Process(ulong baseGpuVa, ReadOnlySpan<int> commandBuffer)
|
||||
{
|
||||
for (int index = 0; index < commandBuffer.Length; index++)
|
||||
{
|
||||
int command = commandBuffer[index];
|
||||
|
||||
ulong gpuVa = baseGpuVa + (ulong)index * 4;
|
||||
|
||||
if (_state.MethodCount != 0)
|
||||
{
|
||||
if (TryFastI2mBufferUpdate(commandBuffer, ref index))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Send(gpuVa, _state.Method, command, _state.SubChannel, _state.MethodCount <= 1);
|
||||
|
||||
if (!_state.NonIncrementing)
|
||||
{
|
||||
_state.Method++;
|
||||
}
|
||||
|
||||
if (_state.IncrementOnce)
|
||||
{
|
||||
_state.NonIncrementing = true;
|
||||
}
|
||||
|
||||
_state.MethodCount--;
|
||||
}
|
||||
else
|
||||
{
|
||||
CompressedMethod meth = Unsafe.As<int, CompressedMethod>(ref command);
|
||||
|
||||
if (TryFastUniformBufferUpdate(meth, commandBuffer, index))
|
||||
{
|
||||
index += meth.MethodCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (meth.SecOp)
|
||||
{
|
||||
case SecOp.IncMethod:
|
||||
case SecOp.NonIncMethod:
|
||||
case SecOp.OneInc:
|
||||
_state.Method = meth.MethodAddress;
|
||||
_state.SubChannel = meth.MethodSubchannel;
|
||||
_state.MethodCount = meth.MethodCount;
|
||||
_state.IncrementOnce = meth.SecOp == SecOp.OneInc;
|
||||
_state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod;
|
||||
break;
|
||||
case SecOp.ImmdDataMethod:
|
||||
Send(gpuVa, meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_3dClass.FlushUboDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to perform a fast Inline-to-Memory data update.
|
||||
/// If successful, all data will be copied at once, and <see cref="DmaState.MethodCount"/>
|
||||
/// command buffer entries will be consumed.
|
||||
/// </summary>
|
||||
/// <param name="commandBuffer">Command buffer where the data is contained</param>
|
||||
/// <param name="offset">Offset at <paramref name="commandBuffer"/> where the data is located, auto-incremented on success</param>
|
||||
/// <returns>True if the fast copy was successful, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryFastI2mBufferUpdate(ReadOnlySpan<int> commandBuffer, ref int offset)
|
||||
{
|
||||
if (_state.Method == LoadInlineDataMethodOffset && _state.NonIncrementing && _state.SubChannel <= 2)
|
||||
{
|
||||
int availableCount = commandBuffer.Length - offset;
|
||||
int consumeCount = Math.Min(_state.MethodCount, availableCount);
|
||||
|
||||
var data = commandBuffer.Slice(offset, consumeCount);
|
||||
|
||||
if (_state.SubChannel == 0)
|
||||
{
|
||||
_3dClass.LoadInlineData(data);
|
||||
}
|
||||
else if (_state.SubChannel == 1)
|
||||
{
|
||||
_computeClass.LoadInlineData(data);
|
||||
}
|
||||
else /* if (_state.SubChannel == 2) */
|
||||
{
|
||||
_i2mClass.LoadInlineData(data);
|
||||
}
|
||||
|
||||
offset += consumeCount - 1;
|
||||
_state.MethodCount -= consumeCount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to perform a fast constant buffer data update.
|
||||
/// If successful, all data will be copied at once, and <see cref="CompressedMethod.MethodCount"/> + 1
|
||||
/// command buffer entries will be consumed.
|
||||
/// </summary>
|
||||
/// <param name="meth">Compressed method to be checked</param>
|
||||
/// <param name="commandBuffer">Command buffer where <paramref name="meth"/> is contained</param>
|
||||
/// <param name="offset">Offset at <paramref name="commandBuffer"/> where <paramref name="meth"/> is located</param>
|
||||
/// <returns>True if the fast copy was successful, false otherwise</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan<int> commandBuffer, int offset)
|
||||
{
|
||||
int availableCount = commandBuffer.Length - offset;
|
||||
|
||||
if (meth.MethodAddress == UniformBufferUpdateDataMethodOffset &&
|
||||
meth.MethodCount < availableCount &&
|
||||
meth.SecOp == SecOp.NonIncMethod)
|
||||
{
|
||||
_3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a uncompressed method for processing by the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address where the command word is located</param>
|
||||
/// <param name="meth">Method to be processed</param>
|
||||
private void Send(ulong gpuVa, int offset, int argument, int subChannel, bool isLastCall)
|
||||
{
|
||||
if (offset < 0x60)
|
||||
{
|
||||
_fifoClass.Write(offset * 4, argument);
|
||||
}
|
||||
else if (offset < 0xe00)
|
||||
{
|
||||
offset *= 4;
|
||||
|
||||
switch (subChannel)
|
||||
{
|
||||
case 0:
|
||||
_3dClass.Write(offset, argument);
|
||||
break;
|
||||
case 1:
|
||||
_computeClass.Write(offset, argument);
|
||||
break;
|
||||
case 2:
|
||||
_i2mClass.Write(offset, argument);
|
||||
break;
|
||||
case 3:
|
||||
_2dClass.Write(offset, argument);
|
||||
break;
|
||||
case 4:
|
||||
_dmaClass.Write(offset, argument);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IDeviceState state = subChannel switch
|
||||
{
|
||||
0 => _3dClass,
|
||||
3 => _2dClass,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (state != null)
|
||||
{
|
||||
int macroIndex = (offset >> 1) & MacroIndexMask;
|
||||
|
||||
if ((offset & 1) != 0)
|
||||
{
|
||||
_fifoClass.MmePushArgument(macroIndex, gpuVa, argument);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fifoClass.MmeStart(macroIndex, argument);
|
||||
}
|
||||
|
||||
if (isLastCall)
|
||||
{
|
||||
_fifoClass.CallMme(macroIndex, state);
|
||||
|
||||
_3dClass.PerformDeferredDraws();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data directly to the state of the specified class.
|
||||
/// </summary>
|
||||
/// <param name="classId">ID of the class to write the data into</param>
|
||||
/// <param name="offset">State offset in bytes</param>
|
||||
/// <param name="value">Value to be written</param>
|
||||
public void Write(ClassId classId, int offset, int value)
|
||||
{
|
||||
switch (classId)
|
||||
{
|
||||
case ClassId.Threed:
|
||||
_3dClass.Write(offset, value);
|
||||
break;
|
||||
case ClassId.Compute:
|
||||
_computeClass.Write(offset, value);
|
||||
break;
|
||||
case ClassId.InlineToMemory:
|
||||
_i2mClass.Write(offset, value);
|
||||
break;
|
||||
case ClassId.Twod:
|
||||
_2dClass.Write(offset, value);
|
||||
break;
|
||||
case ClassId.Dma:
|
||||
_dmaClass.Write(offset, value);
|
||||
break;
|
||||
case ClassId.GPFifo:
|
||||
_fifoClass.Write(offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the shadow ram control value of all sub-channels.
|
||||
/// </summary>
|
||||
/// <param name="control">New shadow ram control value</param>
|
||||
public void SetShadowRamControl(int control)
|
||||
{
|
||||
_3dClass.SetShadowRamControl(control);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a full host state update by marking all state as modified,
|
||||
/// and also requests all GPU resources in use to be rebound.
|
||||
/// </summary>
|
||||
public void ForceAllDirty()
|
||||
{
|
||||
_3dClass.ForceStateDirty();
|
||||
_channel.BufferManager.Rebind();
|
||||
_channel.TextureManager.Rebind();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform any deferred draws.
|
||||
/// </summary>
|
||||
public void PerformDeferredDraws()
|
||||
{
|
||||
_3dClass.PerformDeferredDraws();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Inline-to-Memory engine class.
|
||||
/// </summary>
|
||||
class InlineToMemoryClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceState<InlineToMemoryClassState> _state;
|
||||
|
||||
private bool _isLinear;
|
||||
|
||||
private int _offset;
|
||||
private int _size;
|
||||
|
||||
private ulong _dstGpuVa;
|
||||
private int _dstX;
|
||||
private int _dstY;
|
||||
private int _dstWidth;
|
||||
private int _dstHeight;
|
||||
private int _dstStride;
|
||||
private int _dstGobBlocksInY;
|
||||
private int _dstGobBlocksInZ;
|
||||
private int _lineLengthIn;
|
||||
private int _lineCount;
|
||||
|
||||
private bool _finished;
|
||||
|
||||
private int[] _buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the Inline-to-Memory engine class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="initializeState">Indicates if the internal state should be initialized. Set to false if part of another engine</param>
|
||||
public InlineToMemoryClass(GpuContext context, GpuChannel channel, bool initializeState)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
|
||||
if (initializeState)
|
||||
{
|
||||
_state = new DeviceState<InlineToMemoryClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(InlineToMemoryClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||
{ nameof(InlineToMemoryClassState.LoadInlineData), new RwCallback(LoadInlineData, null) }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the inline-to-memory engine class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
public InlineToMemoryClass(GpuContext context, GpuChannel channel) : this(context, channel, true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Launches Inline-to-Memory engine DMA copy.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
{
|
||||
LaunchDma(ref _state.State, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches Inline-to-Memory engine DMA copy.
|
||||
/// </summary>
|
||||
/// <param name="state">Current class state</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LaunchDma(ref InlineToMemoryClassState state, int argument)
|
||||
{
|
||||
_isLinear = (argument & 1) != 0;
|
||||
|
||||
_offset = 0;
|
||||
_size = (int)(BitUtils.AlignUp<uint>(state.LineLengthIn, 4) * state.LineCount);
|
||||
|
||||
int count = _size / 4;
|
||||
|
||||
if (_buffer == null || _buffer.Length < count)
|
||||
{
|
||||
_buffer = new int[count];
|
||||
}
|
||||
|
||||
ulong dstGpuVa = ((ulong)state.OffsetOutUpperValue << 32) | state.OffsetOut;
|
||||
|
||||
_dstGpuVa = dstGpuVa;
|
||||
_dstX = state.SetDstOriginBytesXV;
|
||||
_dstY = state.SetDstOriginSamplesYV;
|
||||
_dstWidth = (int)state.SetDstWidth;
|
||||
_dstHeight = (int)state.SetDstHeight;
|
||||
_dstStride = (int)state.PitchOut;
|
||||
_dstGobBlocksInY = 1 << (int)state.SetDstBlockSizeHeight;
|
||||
_dstGobBlocksInZ = 1 << (int)state.SetDstBlockSizeDepth;
|
||||
_lineLengthIn = (int)state.LineLengthIn;
|
||||
_lineCount = (int)state.LineCount;
|
||||
|
||||
_finished = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a block of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to push</param>
|
||||
public void LoadInlineData(ReadOnlySpan<int> data)
|
||||
{
|
||||
if (!_finished)
|
||||
{
|
||||
int copySize = Math.Min(data.Length, _buffer.Length - _offset);
|
||||
data.Slice(0, copySize).CopyTo(new Span<int>(_buffer).Slice(_offset, copySize));
|
||||
|
||||
_offset += copySize;
|
||||
|
||||
if (_offset * 4 >= _size)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a word of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadInlineData(int argument)
|
||||
{
|
||||
if (!_finished)
|
||||
{
|
||||
_buffer[_offset++] = argument;
|
||||
|
||||
if (_offset * 4 >= _size)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs actual copy of the inline data after the transfer is finished.
|
||||
/// </summary>
|
||||
private void FinishTransfer()
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
var data = MemoryMarshal.Cast<int, byte>(_buffer).Slice(0, _size);
|
||||
|
||||
if (_isLinear && _lineCount == 1)
|
||||
{
|
||||
memoryManager.WriteTrackedResource(_dstGpuVa, data.Slice(0, _lineLengthIn));
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Verify if the destination X/Y and width/height are taken into account
|
||||
// for linear texture transfers. If not, we can use the fast path for that aswell.
|
||||
// Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
|
||||
if (!_isLinear)
|
||||
{
|
||||
var target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
_dstGpuVa,
|
||||
1,
|
||||
_dstStride,
|
||||
_dstHeight,
|
||||
_lineLengthIn,
|
||||
_lineCount,
|
||||
_isLinear,
|
||||
_dstGobBlocksInY,
|
||||
_dstGobBlocksInZ);
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
target.SynchronizeMemory();
|
||||
target.SetData(data, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
|
||||
target.SignalModified();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var dstCalculator = new OffsetCalculator(
|
||||
_dstWidth,
|
||||
_dstHeight,
|
||||
_dstStride,
|
||||
_isLinear,
|
||||
_dstGobBlocksInY,
|
||||
1);
|
||||
|
||||
int srcOffset = 0;
|
||||
|
||||
for (int y = _dstY; y < _dstY + _lineCount; y++)
|
||||
{
|
||||
int x1 = _dstX;
|
||||
int x2 = _dstX + _lineLengthIn;
|
||||
int x1Round = BitUtils.AlignUp(_dstX, 16);
|
||||
int x2Trunc = BitUtils.AlignDown(x2, 16);
|
||||
|
||||
int x = x1;
|
||||
|
||||
if (x1Round <= x2)
|
||||
{
|
||||
for (; x < x1Round; x++, srcOffset++)
|
||||
{
|
||||
int dstOffset = dstCalculator.GetOffset(x, y);
|
||||
|
||||
ulong dstAddress = _dstGpuVa + (uint)dstOffset;
|
||||
|
||||
memoryManager.Write(dstAddress, data[srcOffset]);
|
||||
}
|
||||
}
|
||||
|
||||
for (; x < x2Trunc; x += 16, srcOffset += 16)
|
||||
{
|
||||
int dstOffset = dstCalculator.GetOffset(x, y);
|
||||
|
||||
ulong dstAddress = _dstGpuVa + (uint)dstOffset;
|
||||
|
||||
memoryManager.Write(dstAddress, MemoryMarshal.Cast<byte, Vector128<byte>>(data.Slice(srcOffset, 16))[0]);
|
||||
}
|
||||
|
||||
for (; x < x2; x++, srcOffset++)
|
||||
{
|
||||
int dstOffset = dstCalculator.GetOffset(x, y);
|
||||
|
||||
ulong dstAddress = _dstGpuVa + (uint)dstOffset;
|
||||
|
||||
memoryManager.Write(dstAddress, data[srcOffset]);
|
||||
}
|
||||
|
||||
// All lines must be aligned to 4 bytes, as the data is pushed one word at a time.
|
||||
// If our copy length is not a multiple of 4, then we need to skip the padding bytes here.
|
||||
int misalignment = _lineLengthIn & 3;
|
||||
|
||||
if (misalignment != 0)
|
||||
{
|
||||
srcOffset += 4 - misalignment;
|
||||
}
|
||||
}
|
||||
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
||||
_finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
|
||||
{
|
||||
/// <summary>
|
||||
/// Notify type.
|
||||
/// </summary>
|
||||
enum NotifyType
|
||||
{
|
||||
WriteOnly = 0,
|
||||
WriteThenAwaken = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Width in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstBlockSizeWidth
|
||||
{
|
||||
OneGob = 0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstBlockSizeHeight
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstBlockSizeDepth
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory layout of the destination texture.
|
||||
/// </summary>
|
||||
enum LaunchDmaDstMemoryLayout
|
||||
{
|
||||
Blocklinear = 0,
|
||||
Pitch = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA completion type.
|
||||
/// </summary>
|
||||
enum LaunchDmaCompletionType
|
||||
{
|
||||
FlushDisable = 0,
|
||||
FlushOnly = 1,
|
||||
ReleaseSemaphore = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA interrupt type.
|
||||
/// </summary>
|
||||
enum LaunchDmaInterruptType
|
||||
{
|
||||
None = 0,
|
||||
Interrupt = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore structure size.
|
||||
/// </summary>
|
||||
enum LaunchDmaSemaphoreStructSize
|
||||
{
|
||||
FourWords = 0,
|
||||
OneWord = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction operation.
|
||||
/// </summary>
|
||||
enum LaunchDmaReductionOp
|
||||
{
|
||||
RedAdd = 0,
|
||||
RedMin = 1,
|
||||
RedMax = 2,
|
||||
RedInc = 3,
|
||||
RedDec = 4,
|
||||
RedAnd = 5,
|
||||
RedOr = 6,
|
||||
RedXor = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction format.
|
||||
/// </summary>
|
||||
enum LaunchDmaReductionFormat
|
||||
{
|
||||
Unsigned32 = 0,
|
||||
Signed32 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline-to-Memory class state.
|
||||
/// </summary>
|
||||
unsafe struct InlineToMemoryClassState
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint SetObject;
|
||||
public int SetObjectClassId => (int)((SetObject >> 0) & 0xFFFF);
|
||||
public int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F);
|
||||
public fixed uint Reserved04[63];
|
||||
public uint NoOperation;
|
||||
public uint SetNotifyA;
|
||||
public int SetNotifyAAddressUpper => (int)((SetNotifyA >> 0) & 0xFF);
|
||||
public uint SetNotifyB;
|
||||
public uint Notify;
|
||||
public NotifyType NotifyType => (NotifyType)(Notify);
|
||||
public uint WaitForIdle;
|
||||
public fixed uint Reserved114[7];
|
||||
public uint SetGlobalRenderEnableA;
|
||||
public int SetGlobalRenderEnableAOffsetUpper => (int)((SetGlobalRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetGlobalRenderEnableB;
|
||||
public uint SetGlobalRenderEnableC;
|
||||
public int SetGlobalRenderEnableCMode => (int)((SetGlobalRenderEnableC >> 0) & 0x7);
|
||||
public uint SendGoIdle;
|
||||
public uint PmTrigger;
|
||||
public uint PmTriggerWfi;
|
||||
public fixed uint Reserved148[2];
|
||||
public uint SetInstrumentationMethodHeader;
|
||||
public uint SetInstrumentationMethodData;
|
||||
public fixed uint Reserved158[10];
|
||||
public uint LineLengthIn;
|
||||
public uint LineCount;
|
||||
public uint OffsetOutUpper;
|
||||
public int OffsetOutUpperValue => (int)((OffsetOutUpper >> 0) & 0xFF);
|
||||
public uint OffsetOut;
|
||||
public uint PitchOut;
|
||||
public uint SetDstBlockSize;
|
||||
public SetDstBlockSizeWidth SetDstBlockSizeWidth => (SetDstBlockSizeWidth)((SetDstBlockSize >> 0) & 0xF);
|
||||
public SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0xF);
|
||||
public SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0xF);
|
||||
public uint SetDstWidth;
|
||||
public uint SetDstHeight;
|
||||
public uint SetDstDepth;
|
||||
public uint SetDstLayer;
|
||||
public uint SetDstOriginBytesX;
|
||||
public int SetDstOriginBytesXV => (int)((SetDstOriginBytesX >> 0) & 0xFFFFF);
|
||||
public uint SetDstOriginSamplesY;
|
||||
public int SetDstOriginSamplesYV => (int)((SetDstOriginSamplesY >> 0) & 0xFFFF);
|
||||
public uint LaunchDma;
|
||||
public LaunchDmaDstMemoryLayout LaunchDmaDstMemoryLayout => (LaunchDmaDstMemoryLayout)((LaunchDma >> 0) & 0x1);
|
||||
public LaunchDmaCompletionType LaunchDmaCompletionType => (LaunchDmaCompletionType)((LaunchDma >> 4) & 0x3);
|
||||
public LaunchDmaInterruptType LaunchDmaInterruptType => (LaunchDmaInterruptType)((LaunchDma >> 8) & 0x3);
|
||||
public LaunchDmaSemaphoreStructSize LaunchDmaSemaphoreStructSize => (LaunchDmaSemaphoreStructSize)((LaunchDma >> 12) & 0x1);
|
||||
public bool LaunchDmaReductionEnable => (LaunchDma & 0x2) != 0;
|
||||
public LaunchDmaReductionOp LaunchDmaReductionOp => (LaunchDmaReductionOp)((LaunchDma >> 13) & 0x7);
|
||||
public LaunchDmaReductionFormat LaunchDmaReductionFormat => (LaunchDmaReductionFormat)((LaunchDma >> 2) & 0x3);
|
||||
public bool LaunchDmaSysmembarDisable => (LaunchDma & 0x40) != 0;
|
||||
public uint LoadInlineData;
|
||||
public fixed uint Reserved1B8[9];
|
||||
public uint SetI2mSemaphoreA;
|
||||
public int SetI2mSemaphoreAOffsetUpper => (int)((SetI2mSemaphoreA >> 0) & 0xFF);
|
||||
public uint SetI2mSemaphoreB;
|
||||
public uint SetI2mSemaphoreC;
|
||||
public fixed uint Reserved1E8[2];
|
||||
public uint SetI2mSpareNoop00;
|
||||
public uint SetI2mSpareNoop01;
|
||||
public uint SetI2mSpareNoop02;
|
||||
public uint SetI2mSpareNoop03;
|
||||
public fixed uint Reserved200[3200];
|
||||
public MmeShadowScratch SetMmeShadowScratch;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
15
src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs
Normal file
15
src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU Macro Arithmetic and Logic unit operation.
|
||||
/// </summary>
|
||||
enum AluOperation
|
||||
{
|
||||
AluReg = 0,
|
||||
AddImmediate = 1,
|
||||
BitfieldReplace = 2,
|
||||
BitfieldExtractLslImm = 3,
|
||||
BitfieldExtractLslReg = 4,
|
||||
ReadImmediate = 5
|
||||
}
|
||||
}
|
||||
18
src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs
Normal file
18
src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU Macro Arithmetic and Logic unit binary register-to-register operation.
|
||||
/// </summary>
|
||||
enum AluRegOperation
|
||||
{
|
||||
Add = 0,
|
||||
AddWithCarry = 1,
|
||||
Subtract = 2,
|
||||
SubtractWithBorrow = 3,
|
||||
BitwiseExclusiveOr = 8,
|
||||
BitwiseOr = 9,
|
||||
BitwiseAnd = 10,
|
||||
BitwiseAndNot = 11,
|
||||
BitwiseNotAnd = 12
|
||||
}
|
||||
}
|
||||
17
src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs
Normal file
17
src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU Macro assignment operation.
|
||||
/// </summary>
|
||||
enum AssignmentOperation
|
||||
{
|
||||
IgnoreAndFetch = 0,
|
||||
Move = 1,
|
||||
MoveAndSetMaddr = 2,
|
||||
FetchAndSend = 3,
|
||||
MoveAndSend = 4,
|
||||
FetchAndSetMaddr = 5,
|
||||
MoveAndSetMaddrThenFetchAndSend = 6,
|
||||
MoveAndSetMaddrThenSendHigh = 7
|
||||
}
|
||||
}
|
||||
52
src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs
Normal file
52
src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// FIFO word.
|
||||
/// </summary>
|
||||
readonly struct FifoWord
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU virtual address where the word is located in memory.
|
||||
/// </summary>
|
||||
public ulong GpuVa { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Word value.
|
||||
/// </summary>
|
||||
public int Word { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FIFO word.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address where the word is located in memory</param>
|
||||
/// <param name="word">Word value</param>
|
||||
public FifoWord(ulong gpuVa, int word)
|
||||
{
|
||||
GpuVa = gpuVa;
|
||||
Word = word;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Macro Execution Engine interface.
|
||||
/// </summary>
|
||||
interface IMacroEE
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments FIFO.
|
||||
/// </summary>
|
||||
Queue<FifoWord> Fifo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Should execute the GPU Macro code being passed.
|
||||
/// </summary>
|
||||
/// <param name="code">Code to be executed</param>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument to be passed to the GPU Macro</param>
|
||||
void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0);
|
||||
}
|
||||
}
|
||||
101
src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs
Normal file
101
src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU macro program.
|
||||
/// </summary>
|
||||
struct Macro
|
||||
{
|
||||
/// <summary>
|
||||
/// Word offset of the code on the code memory.
|
||||
/// </summary>
|
||||
public int Position { get; }
|
||||
|
||||
private IMacroEE _executionEngine;
|
||||
private bool _executionPending;
|
||||
private int _argument;
|
||||
private MacroHLEFunctionName _hleFunction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU cached macro program.
|
||||
/// </summary>
|
||||
/// <param name="position">Macro code start position</param>
|
||||
public Macro(int position)
|
||||
{
|
||||
Position = position;
|
||||
|
||||
_executionEngine = null;
|
||||
_executionPending = false;
|
||||
_argument = 0;
|
||||
_hleFunction = MacroHLEFunctionName.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the first argument for the macro call.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context where the macro code is being executed</param>
|
||||
/// <param name="processor">GPU GP FIFO command processor</param>
|
||||
/// <param name="code">Code to be executed</param>
|
||||
/// <param name="argument">First argument</param>
|
||||
public void StartExecution(GpuContext context, GPFifoProcessor processor, ReadOnlySpan<int> code, int argument)
|
||||
{
|
||||
_argument = argument;
|
||||
|
||||
_executionPending = true;
|
||||
|
||||
if (_executionEngine == null)
|
||||
{
|
||||
if (GraphicsConfig.EnableMacroHLE && MacroHLETable.TryGetMacroHLEFunction(code.Slice(Position), context.Capabilities, out _hleFunction))
|
||||
{
|
||||
_executionEngine = new MacroHLE(processor, _hleFunction);
|
||||
}
|
||||
else if (GraphicsConfig.EnableMacroJit)
|
||||
{
|
||||
_executionEngine = new MacroJit();
|
||||
}
|
||||
else
|
||||
{
|
||||
_executionEngine = new MacroInterpreter();
|
||||
}
|
||||
}
|
||||
|
||||
// We don't consume the parameter buffer value, so we don't need to flush it.
|
||||
// Doing so improves performance if the value was written by a GPU shader.
|
||||
if (_hleFunction == MacroHLEFunctionName.DrawElementsIndirect)
|
||||
{
|
||||
context.GPFifo.SetFlushSkips(1);
|
||||
}
|
||||
else if (_hleFunction == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
||||
{
|
||||
context.GPFifo.SetFlushSkips(2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts executing the macro program code.
|
||||
/// </summary>
|
||||
/// <param name="code">Program code</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
public void Execute(ReadOnlySpan<int> code, IDeviceState state)
|
||||
{
|
||||
if (_executionPending)
|
||||
{
|
||||
_executionPending = false;
|
||||
_executionEngine?.Execute(code.Slice(Position), state, _argument);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an argument to the macro call argument FIFO.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">GPU virtual address where the command word is located</param>
|
||||
/// <param name="argument">Argument to be pushed</param>
|
||||
public void PushArgument(ulong gpuVa, int argument)
|
||||
{
|
||||
_executionEngine?.Fifo.Enqueue(new FifoWord(gpuVa, argument));
|
||||
}
|
||||
}
|
||||
}
|
||||
341
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
Normal file
341
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Macro High-level emulation.
|
||||
/// </summary>
|
||||
class MacroHLE : IMacroEE
|
||||
{
|
||||
private const int ColorLayerCountOffset = 0x818;
|
||||
private const int ColorStructSize = 0x40;
|
||||
private const int ZetaLayerCountOffset = 0x1230;
|
||||
|
||||
private const int IndirectDataEntrySize = 0x10;
|
||||
private const int IndirectIndexedDataEntrySize = 0x14;
|
||||
|
||||
private readonly GPFifoProcessor _processor;
|
||||
private readonly MacroHLEFunctionName _functionName;
|
||||
|
||||
/// <summary>
|
||||
/// Arguments FIFO.
|
||||
/// </summary>
|
||||
public Queue<FifoWord> Fifo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the HLE macro handler.
|
||||
/// </summary>
|
||||
/// <param name="processor">GPU GP FIFO command processor</param>
|
||||
/// <param name="functionName">Name of the HLE macro function to be called</param>
|
||||
public MacroHLE(GPFifoProcessor processor, MacroHLEFunctionName functionName)
|
||||
{
|
||||
_processor = processor;
|
||||
_functionName = functionName;
|
||||
|
||||
Fifo = new Queue<FifoWord>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a macro program until it exits.
|
||||
/// </summary>
|
||||
/// <param name="code">Code of the program to execute</param>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">Optional argument passed to the program, 0 if not used</param>
|
||||
public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0)
|
||||
{
|
||||
switch (_functionName)
|
||||
{
|
||||
case MacroHLEFunctionName.ClearColor:
|
||||
ClearColor(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.ClearDepthStencil:
|
||||
ClearDepthStencil(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.DrawArraysInstanced:
|
||||
DrawArraysInstanced(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.DrawElementsInstanced:
|
||||
DrawElementsInstanced(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.DrawElementsIndirect:
|
||||
DrawElementsIndirect(state, arg0);
|
||||
break;
|
||||
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
|
||||
MultiDrawElementsIndirectCount(state, arg0);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException(_functionName.ToString());
|
||||
}
|
||||
|
||||
// It should be empty at this point, but clear it just to be safe.
|
||||
Fifo.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears one bound color target.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void ClearColor(IDeviceState state, int arg0)
|
||||
{
|
||||
int index = (arg0 >> 6) & 0xf;
|
||||
int layerCount = state.Read(ColorLayerCountOffset + index * ColorStructSize);
|
||||
|
||||
_processor.ThreedClass.Clear(arg0, layerCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current depth-stencil target.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void ClearDepthStencil(IDeviceState state, int arg0)
|
||||
{
|
||||
int layerCount = state.Read(ZetaLayerCountOffset);
|
||||
|
||||
_processor.ThreedClass.Clear(arg0, layerCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a draw.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void DrawArraysInstanced(IDeviceState state, int arg0)
|
||||
{
|
||||
var topology = (PrimitiveTopology)arg0;
|
||||
|
||||
var count = FetchParam();
|
||||
var instanceCount = FetchParam();
|
||||
var firstVertex = FetchParam();
|
||||
var firstInstance = FetchParam();
|
||||
|
||||
if (ShouldSkipDraw(state, instanceCount.Word))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_processor.ThreedClass.Draw(
|
||||
topology,
|
||||
count.Word,
|
||||
instanceCount.Word,
|
||||
0,
|
||||
firstVertex.Word,
|
||||
firstInstance.Word,
|
||||
indexed: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void DrawElementsInstanced(IDeviceState state, int arg0)
|
||||
{
|
||||
var topology = (PrimitiveTopology)arg0;
|
||||
|
||||
var count = FetchParam();
|
||||
var instanceCount = FetchParam();
|
||||
var firstIndex = FetchParam();
|
||||
var firstVertex = FetchParam();
|
||||
var firstInstance = FetchParam();
|
||||
|
||||
if (ShouldSkipDraw(state, instanceCount.Word))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_processor.ThreedClass.Draw(
|
||||
topology,
|
||||
count.Word,
|
||||
instanceCount.Word,
|
||||
firstIndex.Word,
|
||||
firstVertex.Word,
|
||||
firstInstance.Word,
|
||||
indexed: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indirect indexed draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void DrawElementsIndirect(IDeviceState state, int arg0)
|
||||
{
|
||||
var topology = (PrimitiveTopology)arg0;
|
||||
|
||||
var count = FetchParam();
|
||||
var instanceCount = FetchParam();
|
||||
var firstIndex = FetchParam();
|
||||
var firstVertex = FetchParam();
|
||||
var firstInstance = FetchParam();
|
||||
|
||||
ulong indirectBufferGpuVa = count.GpuVa;
|
||||
|
||||
var bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
|
||||
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
|
||||
|
||||
if (useBuffer)
|
||||
{
|
||||
int indexCount = firstIndex.Word + count.Word;
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
indirectBufferAddress,
|
||||
0,
|
||||
1,
|
||||
IndirectIndexedDataEntrySize,
|
||||
indexCount,
|
||||
Threed.IndirectDrawType.DrawIndexedIndirect);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ShouldSkipDraw(state, instanceCount.Word))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_processor.ThreedClass.Draw(
|
||||
topology,
|
||||
count.Word,
|
||||
instanceCount.Word,
|
||||
firstIndex.Word,
|
||||
firstVertex.Word,
|
||||
firstInstance.Word,
|
||||
indexed: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indirect indexed multi-draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state at the time of the call</param>
|
||||
/// <param name="arg0">First argument of the call</param>
|
||||
private void MultiDrawElementsIndirectCount(IDeviceState state, int arg0)
|
||||
{
|
||||
int arg1 = FetchParam().Word;
|
||||
int arg2 = FetchParam().Word;
|
||||
int arg3 = FetchParam().Word;
|
||||
|
||||
int startDraw = arg0;
|
||||
int endDraw = arg1;
|
||||
var topology = (PrimitiveTopology)arg2;
|
||||
int paddingWords = arg3;
|
||||
int stride = paddingWords * 4 + 0x14;
|
||||
|
||||
ulong parameterBufferGpuVa = FetchParam().GpuVa;
|
||||
|
||||
int maxDrawCount = endDraw - startDraw;
|
||||
|
||||
if (startDraw != 0)
|
||||
{
|
||||
int drawCount = _processor.MemoryManager.Read<int>(parameterBufferGpuVa, tracked: true);
|
||||
|
||||
// Calculate maximum draw count based on the previous draw count and current draw count.
|
||||
if ((uint)drawCount <= (uint)startDraw)
|
||||
{
|
||||
// The start draw is past our total draw count, so all draws were already performed.
|
||||
maxDrawCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Perform just the missing number of draws.
|
||||
maxDrawCount = (int)Math.Min((uint)maxDrawCount, (uint)(drawCount - startDraw));
|
||||
}
|
||||
}
|
||||
|
||||
if (maxDrawCount == 0)
|
||||
{
|
||||
Fifo.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ulong indirectBufferGpuVa = 0;
|
||||
int indexCount = 0;
|
||||
|
||||
for (int i = 0; i < maxDrawCount; i++)
|
||||
{
|
||||
var count = FetchParam();
|
||||
var instanceCount = FetchParam();
|
||||
var firstIndex = FetchParam();
|
||||
var firstVertex = FetchParam();
|
||||
var firstInstance = FetchParam();
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
indirectBufferGpuVa = count.GpuVa;
|
||||
}
|
||||
|
||||
indexCount = Math.Max(indexCount, count.Word + firstIndex.Word);
|
||||
|
||||
if (i != maxDrawCount - 1)
|
||||
{
|
||||
for (int j = 0; j < paddingWords; j++)
|
||||
{
|
||||
FetchParam();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bufferCache = _processor.MemoryManager.Physical.BufferCache;
|
||||
|
||||
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
|
||||
|
||||
ulong indirectBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
|
||||
ulong parameterBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, parameterBufferGpuVa, 4);
|
||||
|
||||
_processor.ThreedClass.DrawIndirect(
|
||||
topology,
|
||||
indirectBufferAddress,
|
||||
parameterBufferAddress,
|
||||
maxDrawCount,
|
||||
stride,
|
||||
indexCount,
|
||||
Threed.IndirectDrawType.DrawIndexedIndirectCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the draw should be skipped, because the masked instance count is zero.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="instanceCount">Draw instance count</param>
|
||||
/// <returns>True if the draw should be skipped, false otherwise</returns>
|
||||
private static bool ShouldSkipDraw(IDeviceState state, int instanceCount)
|
||||
{
|
||||
return (Read(state, 0xd1b) & instanceCount) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a arguments from the arguments FIFO.
|
||||
/// </summary>
|
||||
/// <returns>The call argument, or a 0 value with null address if the FIFO is empty</returns>
|
||||
private FifoWord FetchParam()
|
||||
{
|
||||
if (!Fifo.TryDequeue(out var value))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
|
||||
|
||||
return new FifoWord(0UL, 0);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a GPU register.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="reg">Register offset to read</param>
|
||||
/// <returns>GPU register value</returns>
|
||||
private static int Read(IDeviceState state, int reg)
|
||||
{
|
||||
return state.Read(reg * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs
Normal file
16
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the High-level implementation of a Macro function.
|
||||
/// </summary>
|
||||
enum MacroHLEFunctionName
|
||||
{
|
||||
None,
|
||||
ClearColor,
|
||||
ClearDepthStencil,
|
||||
DrawArraysInstanced,
|
||||
DrawElementsInstanced,
|
||||
DrawElementsIndirect,
|
||||
MultiDrawElementsIndirectCount
|
||||
}
|
||||
}
|
||||
113
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs
Normal file
113
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Table with information about High-level implementations of GPU Macro code.
|
||||
/// </summary>
|
||||
static class MacroHLETable
|
||||
{
|
||||
/// <summary>
|
||||
/// Macroo High-level implementation table entry.
|
||||
/// </summary>
|
||||
readonly struct TableEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the Macro function.
|
||||
/// </summary>
|
||||
public MacroHLEFunctionName Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Hash of the original binary Macro function code.
|
||||
/// </summary>
|
||||
public Hash128 Hash { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size (in bytes) of the original binary Macro function code.
|
||||
/// </summary>
|
||||
public int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new table entry.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the Macro function</param>
|
||||
/// <param name="hash">Hash of the original binary Macro function code</param>
|
||||
/// <param name="length">Size (in bytes) of the original binary Macro function code</param>
|
||||
public TableEntry(MacroHLEFunctionName name, Hash128 hash, int length)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
Length = length;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly TableEntry[] _table = new TableEntry[]
|
||||
{
|
||||
new TableEntry(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
|
||||
new TableEntry(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
|
||||
new TableEntry(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
|
||||
new TableEntry(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
|
||||
new TableEntry(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
|
||||
new TableEntry(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the host supports all features required by the HLE macro.
|
||||
/// </summary>
|
||||
/// <param name="caps">Host capabilities</param>
|
||||
/// <param name="name">Name of the HLE macro to be checked</param>
|
||||
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
|
||||
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
|
||||
{
|
||||
if (name == MacroHLEFunctionName.ClearColor ||
|
||||
name == MacroHLEFunctionName.ClearDepthStencil ||
|
||||
name == MacroHLEFunctionName.DrawArraysInstanced ||
|
||||
name == MacroHLEFunctionName.DrawElementsInstanced ||
|
||||
name == MacroHLEFunctionName.DrawElementsIndirect)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (name == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
|
||||
{
|
||||
return caps.SupportsIndirectParameters;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if there's a fast, High-level implementation of the specified Macro code available.
|
||||
/// </summary>
|
||||
/// <param name="code">Macro code to be checked</param>
|
||||
/// <param name="caps">Renderer capabilities to check for this macro HLE support</param>
|
||||
/// <param name="name">Name of the function if a implementation is available and supported, otherwise <see cref="MacroHLEFunctionName.None"/></param>
|
||||
/// <returns>True if there is a implementation available and supported, false otherwise</returns>
|
||||
public static bool TryGetMacroHLEFunction(ReadOnlySpan<int> code, Capabilities caps, out MacroHLEFunctionName name)
|
||||
{
|
||||
var mc = MemoryMarshal.Cast<int, byte>(code);
|
||||
|
||||
for (int i = 0; i < _table.Length; i++)
|
||||
{
|
||||
ref var entry = ref _table[i];
|
||||
|
||||
var hash = XXHash128.ComputeHash(mc.Slice(0, entry.Length));
|
||||
if (hash == entry.Hash)
|
||||
{
|
||||
if (IsMacroHLESupported(caps, entry.Name))
|
||||
{
|
||||
name = entry.Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
name = MacroHLEFunctionName.None;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
400
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs
Normal file
400
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Macro code interpreter.
|
||||
/// </summary>
|
||||
class MacroInterpreter : IMacroEE
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments FIFO.
|
||||
/// </summary>
|
||||
public Queue<FifoWord> Fifo { get; }
|
||||
|
||||
private int[] _gprs;
|
||||
|
||||
private int _methAddr;
|
||||
private int _methIncr;
|
||||
|
||||
private bool _carry;
|
||||
|
||||
private int _opCode;
|
||||
private int _pipeOp;
|
||||
|
||||
private bool _ignoreExitFlag;
|
||||
|
||||
private int _pc;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the macro code interpreter.
|
||||
/// </summary>
|
||||
public MacroInterpreter()
|
||||
{
|
||||
Fifo = new Queue<FifoWord>();
|
||||
|
||||
_gprs = new int[8];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a macro program until it exits.
|
||||
/// </summary>
|
||||
/// <param name="code">Code of the program to execute</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="arg0">Optional argument passed to the program, 0 if not used</param>
|
||||
public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0)
|
||||
{
|
||||
Reset();
|
||||
|
||||
_gprs[1] = arg0;
|
||||
|
||||
_pc = 0;
|
||||
|
||||
FetchOpCode(code);
|
||||
|
||||
while (Step(code, state))
|
||||
{
|
||||
}
|
||||
|
||||
// Due to the delay slot, we still need to execute
|
||||
// one more instruction before we actually exit.
|
||||
Step(code, state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the internal interpreter state.
|
||||
/// Call each time you run a new program.
|
||||
/// </summary>
|
||||
private void Reset()
|
||||
{
|
||||
for (int index = 0; index < _gprs.Length; index++)
|
||||
{
|
||||
_gprs[index] = 0;
|
||||
}
|
||||
|
||||
_methAddr = 0;
|
||||
_methIncr = 0;
|
||||
|
||||
_carry = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a single instruction of the program.
|
||||
/// </summary>
|
||||
/// <param name="code">Program code to execute</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <returns>True to continue execution, false if the program exited</returns>
|
||||
private bool Step(ReadOnlySpan<int> code, IDeviceState state)
|
||||
{
|
||||
int baseAddr = _pc - 1;
|
||||
|
||||
FetchOpCode(code);
|
||||
|
||||
if ((_opCode & 7) < 7)
|
||||
{
|
||||
// Operation produces a value.
|
||||
AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7);
|
||||
|
||||
int result = GetAluResult(state);
|
||||
|
||||
switch (asgOp)
|
||||
{
|
||||
// Fetch parameter and ignore result.
|
||||
case AssignmentOperation.IgnoreAndFetch:
|
||||
SetDstGpr(FetchParam());
|
||||
break;
|
||||
// Move result.
|
||||
case AssignmentOperation.Move:
|
||||
SetDstGpr(result);
|
||||
break;
|
||||
// Move result and use as Method Address.
|
||||
case AssignmentOperation.MoveAndSetMaddr:
|
||||
SetDstGpr(result);
|
||||
SetMethAddr(result);
|
||||
break;
|
||||
// Fetch parameter and send result.
|
||||
case AssignmentOperation.FetchAndSend:
|
||||
SetDstGpr(FetchParam());
|
||||
Send(state, result);
|
||||
break;
|
||||
// Move and send result.
|
||||
case AssignmentOperation.MoveAndSend:
|
||||
SetDstGpr(result);
|
||||
Send(state, result);
|
||||
break;
|
||||
// Fetch parameter and use result as Method Address.
|
||||
case AssignmentOperation.FetchAndSetMaddr:
|
||||
SetDstGpr(FetchParam());
|
||||
SetMethAddr(result);
|
||||
break;
|
||||
// Move result and use as Method Address, then fetch and send parameter.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
|
||||
SetDstGpr(result);
|
||||
SetMethAddr(result);
|
||||
Send(state, FetchParam());
|
||||
break;
|
||||
// Move result and use as Method Address, then send bits 17:12 of result.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
|
||||
SetDstGpr(result);
|
||||
SetMethAddr(result);
|
||||
Send(state, (result >> 12) & 0x3f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Branch.
|
||||
bool onNotZero = ((_opCode >> 4) & 1) != 0;
|
||||
|
||||
bool taken = onNotZero
|
||||
? GetGprA() != 0
|
||||
: GetGprA() == 0;
|
||||
|
||||
if (taken)
|
||||
{
|
||||
_pc = baseAddr + GetImm();
|
||||
|
||||
bool noDelays = (_opCode & 0x20) != 0;
|
||||
|
||||
if (noDelays)
|
||||
{
|
||||
FetchOpCode(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The delay slot instruction exit flag should be ignored.
|
||||
_ignoreExitFlag = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool exit = (_opCode & 0x80) != 0 && !_ignoreExitFlag;
|
||||
|
||||
_ignoreExitFlag = false;
|
||||
|
||||
return !exit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a single operation code from the program code.
|
||||
/// </summary>
|
||||
/// <param name="code">Program code</param>
|
||||
private void FetchOpCode(ReadOnlySpan<int> code)
|
||||
{
|
||||
_opCode = _pipeOp;
|
||||
_pipeOp = code[_pc++];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of the current Arithmetic and Logic unit operation.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <returns>Operation result</returns>
|
||||
private int GetAluResult(IDeviceState state)
|
||||
{
|
||||
AluOperation op = (AluOperation)(_opCode & 7);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case AluOperation.AluReg:
|
||||
return GetAluResult((AluRegOperation)((_opCode >> 17) & 0x1f), GetGprA(), GetGprB());
|
||||
|
||||
case AluOperation.AddImmediate:
|
||||
return GetGprA() + GetImm();
|
||||
|
||||
case AluOperation.BitfieldReplace:
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
int bfSrcBit = (_opCode >> 17) & 0x1f;
|
||||
int bfSize = (_opCode >> 22) & 0x1f;
|
||||
int bfDstBit = (_opCode >> 27) & 0x1f;
|
||||
|
||||
int bfMask = (1 << bfSize) - 1;
|
||||
|
||||
int dst = GetGprA();
|
||||
int src = GetGprB();
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case AluOperation.BitfieldReplace:
|
||||
src = (int)((uint)src >> bfSrcBit) & bfMask;
|
||||
|
||||
dst &= ~(bfMask << bfDstBit);
|
||||
|
||||
dst |= src << bfDstBit;
|
||||
|
||||
return dst;
|
||||
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
src = (int)((uint)src >> dst) & bfMask;
|
||||
|
||||
return src << bfDstBit;
|
||||
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
src = (int)((uint)src >> bfSrcBit) & bfMask;
|
||||
|
||||
return src << dst;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AluOperation.ReadImmediate:
|
||||
return Read(state, GetGprA() + GetImm());
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Invalid operation \"{op}\" on instruction 0x{_opCode:X8}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the result of an Arithmetic and Logic operation using registers.
|
||||
/// </summary>
|
||||
/// <param name="aluOp">Arithmetic and Logic unit operation with registers</param>
|
||||
/// <param name="a">First operand value</param>
|
||||
/// <param name="b">Second operand value</param>
|
||||
/// <returns>Operation result</returns>
|
||||
private int GetAluResult(AluRegOperation aluOp, int a, int b)
|
||||
{
|
||||
ulong result;
|
||||
|
||||
switch (aluOp)
|
||||
{
|
||||
case AluRegOperation.Add:
|
||||
result = (ulong)a + (ulong)b;
|
||||
|
||||
_carry = result > 0xffffffff;
|
||||
|
||||
return (int)result;
|
||||
|
||||
case AluRegOperation.AddWithCarry:
|
||||
result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL);
|
||||
|
||||
_carry = result > 0xffffffff;
|
||||
|
||||
return (int)result;
|
||||
|
||||
case AluRegOperation.Subtract:
|
||||
result = (ulong)a - (ulong)b;
|
||||
|
||||
_carry = result < 0x100000000;
|
||||
|
||||
return (int)result;
|
||||
|
||||
case AluRegOperation.SubtractWithBorrow:
|
||||
result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL);
|
||||
|
||||
_carry = result < 0x100000000;
|
||||
|
||||
return (int)result;
|
||||
|
||||
case AluRegOperation.BitwiseExclusiveOr: return a ^ b;
|
||||
case AluRegOperation.BitwiseOr: return a | b;
|
||||
case AluRegOperation.BitwiseAnd: return a & b;
|
||||
case AluRegOperation.BitwiseAndNot: return a & ~b;
|
||||
case AluRegOperation.BitwiseNotAnd: return ~(a & b);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Invalid operation \"{aluOp}\" on instruction 0x{_opCode:X8}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts a 32-bits signed integer constant from the current operation code.
|
||||
/// </summary>
|
||||
/// <returns>The 32-bits immediate value encoded at the current operation code</returns>
|
||||
private int GetImm()
|
||||
{
|
||||
// Note: The immediate is signed, the sign-extension is intended here.
|
||||
return _opCode >> 14;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current method address, for method calls.
|
||||
/// </summary>
|
||||
/// <param name="value">Packed address and increment value</param>
|
||||
private void SetMethAddr(int value)
|
||||
{
|
||||
_methAddr = (value >> 0) & 0xfff;
|
||||
_methIncr = (value >> 12) & 0x3f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the destination register value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to set (usually the operation result)</param>
|
||||
private void SetDstGpr(int value)
|
||||
{
|
||||
_gprs[(_opCode >> 8) & 7] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets first operand value from the respective register.
|
||||
/// </summary>
|
||||
/// <returns>Operand value</returns>
|
||||
private int GetGprA()
|
||||
{
|
||||
return GetGprValue((_opCode >> 11) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets second operand value from the respective register.
|
||||
/// </summary>
|
||||
/// <returns>Operand value</returns>
|
||||
private int GetGprB()
|
||||
{
|
||||
return GetGprValue((_opCode >> 14) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value from a register, or 0 if the R0 register is specified.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the register</param>
|
||||
/// <returns>Register value</returns>
|
||||
private int GetGprValue(int index)
|
||||
{
|
||||
return index != 0 ? _gprs[index] : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a call argument from the call argument FIFO.
|
||||
/// </summary>
|
||||
/// <returns>The call argument, or 0 if the FIFO is empty</returns>
|
||||
private int FetchParam()
|
||||
{
|
||||
if (!Fifo.TryDequeue(out var value))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value.Word;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a GPU register.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="reg">Register offset to read</param>
|
||||
/// <returns>GPU register value</returns>
|
||||
private int Read(IDeviceState state, int reg)
|
||||
{
|
||||
return state.Read(reg * 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a GPU method call.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="value">Call argument</param>
|
||||
private void Send(IDeviceState state, int value)
|
||||
{
|
||||
state.Write(_methAddr * 4, value);
|
||||
|
||||
_methAddr += _methIncr;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs
Normal file
39
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a execution engine that uses a Just-in-Time compiler for fast execution.
|
||||
/// </summary>
|
||||
class MacroJit : IMacroEE
|
||||
{
|
||||
private readonly MacroJitContext _context = new MacroJitContext();
|
||||
|
||||
/// <summary>
|
||||
/// Arguments FIFO.
|
||||
/// </summary>
|
||||
public Queue<FifoWord> Fifo => _context.Fifo;
|
||||
|
||||
private MacroJitCompiler.MacroExecute _execute;
|
||||
|
||||
/// <summary>
|
||||
/// Executes a macro program until it exits.
|
||||
/// </summary>
|
||||
/// <param name="code">Code of the program to execute</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="arg0">Optional argument passed to the program, 0 if not used</param>
|
||||
public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0)
|
||||
{
|
||||
if (_execute == null)
|
||||
{
|
||||
MacroJitCompiler compiler = new MacroJitCompiler();
|
||||
|
||||
_execute = compiler.Compile(code);
|
||||
}
|
||||
|
||||
_execute(_context, state, arg0);
|
||||
}
|
||||
}
|
||||
}
|
||||
517
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs
Normal file
517
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs
Normal file
@@ -0,0 +1,517 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Macro Just-in-Time compiler.
|
||||
/// </summary>R
|
||||
class MacroJitCompiler
|
||||
{
|
||||
private readonly DynamicMethod _meth;
|
||||
private readonly ILGenerator _ilGen;
|
||||
private readonly LocalBuilder[] _gprs;
|
||||
private readonly LocalBuilder _carry;
|
||||
private readonly LocalBuilder _methAddr;
|
||||
private readonly LocalBuilder _methIncr;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the Macro Just-in-Time compiler.
|
||||
/// </summary>
|
||||
public MacroJitCompiler()
|
||||
{
|
||||
_meth = new DynamicMethod("Macro", typeof(void), new Type[] { typeof(MacroJitContext), typeof(IDeviceState), typeof(int) });
|
||||
_ilGen = _meth.GetILGenerator();
|
||||
_gprs = new LocalBuilder[8];
|
||||
|
||||
for (int i = 1; i < 8; i++)
|
||||
{
|
||||
_gprs[i] = _ilGen.DeclareLocal(typeof(int));
|
||||
}
|
||||
|
||||
_carry = _ilGen.DeclareLocal(typeof(int));
|
||||
_methAddr = _ilGen.DeclareLocal(typeof(int));
|
||||
_methIncr = _ilGen.DeclareLocal(typeof(int));
|
||||
|
||||
_ilGen.Emit(OpCodes.Ldarg_2);
|
||||
_ilGen.Emit(OpCodes.Stloc, _gprs[1]);
|
||||
}
|
||||
|
||||
public delegate void MacroExecute(MacroJitContext context, IDeviceState state, int arg0);
|
||||
|
||||
/// <summary>
|
||||
/// Translates a new piece of GPU Macro code into host executable code.
|
||||
/// </summary>
|
||||
/// <param name="code">Code to be translated</param>
|
||||
/// <returns>Delegate of the host compiled code</returns>
|
||||
public MacroExecute Compile(ReadOnlySpan<int> code)
|
||||
{
|
||||
Dictionary<int, Label> labels = new Dictionary<int, Label>();
|
||||
|
||||
int lastTarget = 0;
|
||||
int i;
|
||||
|
||||
// Collect all branch targets.
|
||||
for (i = 0; i < code.Length; i++)
|
||||
{
|
||||
int opCode = code[i];
|
||||
|
||||
if ((opCode & 7) == 7)
|
||||
{
|
||||
int target = i + (opCode >> 14);
|
||||
|
||||
if (!labels.ContainsKey(target))
|
||||
{
|
||||
labels.Add(target, _ilGen.DefineLabel());
|
||||
}
|
||||
|
||||
if (lastTarget < target)
|
||||
{
|
||||
lastTarget = target;
|
||||
}
|
||||
}
|
||||
|
||||
bool exit = (opCode & 0x80) != 0;
|
||||
|
||||
if (exit && i >= lastTarget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Code generation.
|
||||
for (i = 0; i < code.Length; i++)
|
||||
{
|
||||
if (labels.TryGetValue(i, out Label label))
|
||||
{
|
||||
_ilGen.MarkLabel(label);
|
||||
}
|
||||
|
||||
Emit(code, i, labels);
|
||||
|
||||
int opCode = code[i];
|
||||
|
||||
bool exit = (opCode & 0x80) != 0;
|
||||
|
||||
if (exit)
|
||||
{
|
||||
Emit(code, i + 1, labels);
|
||||
_ilGen.Emit(OpCodes.Ret);
|
||||
|
||||
if (i >= lastTarget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == code.Length)
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
return _meth.CreateDelegate<MacroExecute>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits IL equivalent to the Macro instruction at a given offset.
|
||||
/// </summary>
|
||||
/// <param name="code">GPU Macro code</param>
|
||||
/// <param name="offset">Offset, in words, where the instruction is located</param>
|
||||
/// <param name="labels">Labels for Macro branch targets, used by branch instructions</param>
|
||||
private void Emit(ReadOnlySpan<int> code, int offset, Dictionary<int, Label> labels)
|
||||
{
|
||||
int opCode = code[offset];
|
||||
|
||||
if ((opCode & 7) < 7)
|
||||
{
|
||||
// Operation produces a value.
|
||||
AssignmentOperation asgOp = (AssignmentOperation)((opCode >> 4) & 7);
|
||||
|
||||
EmitAluOp(opCode);
|
||||
|
||||
switch (asgOp)
|
||||
{
|
||||
// Fetch parameter and ignore result.
|
||||
case AssignmentOperation.IgnoreAndFetch:
|
||||
_ilGen.Emit(OpCodes.Pop);
|
||||
EmitFetchParam();
|
||||
EmitStoreDstGpr(opCode);
|
||||
break;
|
||||
// Move result.
|
||||
case AssignmentOperation.Move:
|
||||
EmitStoreDstGpr(opCode);
|
||||
break;
|
||||
// Move result and use as Method Address.
|
||||
case AssignmentOperation.MoveAndSetMaddr:
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitStoreMethAddr();
|
||||
break;
|
||||
// Fetch parameter and send result.
|
||||
case AssignmentOperation.FetchAndSend:
|
||||
EmitFetchParam();
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitSend();
|
||||
break;
|
||||
// Move and send result.
|
||||
case AssignmentOperation.MoveAndSend:
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitSend();
|
||||
break;
|
||||
// Fetch parameter and use result as Method Address.
|
||||
case AssignmentOperation.FetchAndSetMaddr:
|
||||
EmitFetchParam();
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitStoreMethAddr();
|
||||
break;
|
||||
// Move result and use as Method Address, then fetch and send parameter.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitStoreMethAddr();
|
||||
EmitFetchParam();
|
||||
EmitSend();
|
||||
break;
|
||||
// Move result and use as Method Address, then send bits 17:12 of result.
|
||||
case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
EmitStoreDstGpr(opCode);
|
||||
EmitStoreMethAddr();
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, 12);
|
||||
_ilGen.Emit(OpCodes.Shr_Un);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, 0x3f);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
EmitSend();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Branch.
|
||||
bool onNotZero = ((opCode >> 4) & 1) != 0;
|
||||
|
||||
EmitLoadGprA(opCode);
|
||||
|
||||
Label lblSkip = _ilGen.DefineLabel();
|
||||
|
||||
if (onNotZero)
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Brfalse, lblSkip);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Brtrue, lblSkip);
|
||||
}
|
||||
|
||||
bool noDelays = (opCode & 0x20) != 0;
|
||||
|
||||
if (!noDelays)
|
||||
{
|
||||
Emit(code, offset + 1, labels);
|
||||
}
|
||||
|
||||
int target = offset + (opCode >> 14);
|
||||
|
||||
_ilGen.Emit(OpCodes.Br, labels[target]);
|
||||
|
||||
_ilGen.MarkLabel(lblSkip);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits IL for a Arithmetic and Logic Unit instruction.
|
||||
/// </summary>
|
||||
/// <param name="opCode">Instruction to be translated</param>
|
||||
/// <exception cref="InvalidOperationException">Throw when the instruction encoding is invalid</exception>
|
||||
private void EmitAluOp(int opCode)
|
||||
{
|
||||
AluOperation op = (AluOperation)(opCode & 7);
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case AluOperation.AluReg:
|
||||
EmitAluOp((AluRegOperation)((opCode >> 17) & 0x1f), opCode);
|
||||
break;
|
||||
|
||||
case AluOperation.AddImmediate:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadImm(opCode);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
break;
|
||||
|
||||
case AluOperation.BitfieldReplace:
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
int bfSrcBit = (opCode >> 17) & 0x1f;
|
||||
int bfSize = (opCode >> 22) & 0x1f;
|
||||
int bfDstBit = (opCode >> 27) & 0x1f;
|
||||
|
||||
int bfMask = (1 << bfSize) - 1;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case AluOperation.BitfieldReplace:
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit);
|
||||
_ilGen.Emit(OpCodes.Shr_Un);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfMask);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfDstBit);
|
||||
_ilGen.Emit(OpCodes.Shl);
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, ~(bfMask << bfDstBit));
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Or);
|
||||
break;
|
||||
|
||||
case AluOperation.BitfieldExtractLslImm:
|
||||
EmitLoadGprB(opCode);
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Shr_Un);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfMask);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfDstBit);
|
||||
_ilGen.Emit(OpCodes.Shl);
|
||||
break;
|
||||
|
||||
case AluOperation.BitfieldExtractLslReg:
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit);
|
||||
_ilGen.Emit(OpCodes.Shr_Un);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, bfMask);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Shl);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case AluOperation.ReadImmediate:
|
||||
_ilGen.Emit(OpCodes.Ldarg_1);
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadImm(opCode);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
_ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Read)));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid operation \"{op}\" on instruction 0x{opCode:X8}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits IL for a binary Arithmetic and Logic Unit instruction.
|
||||
/// </summary>
|
||||
/// <param name="aluOp">Arithmetic and Logic Unit instruction</param>
|
||||
/// <param name="opCode">Raw instruction</param>
|
||||
/// <exception cref="InvalidOperationException">Throw when the instruction encoding is invalid</exception>
|
||||
private void EmitAluOp(AluRegOperation aluOp, int opCode)
|
||||
{
|
||||
switch (aluOp)
|
||||
{
|
||||
case AluRegOperation.Add:
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL);
|
||||
_ilGen.Emit(OpCodes.Cgt_Un);
|
||||
_ilGen.Emit(OpCodes.Stloc, _carry);
|
||||
_ilGen.Emit(OpCodes.Conv_U4);
|
||||
break;
|
||||
case AluRegOperation.AddWithCarry:
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _carry);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL);
|
||||
_ilGen.Emit(OpCodes.Cgt_Un);
|
||||
_ilGen.Emit(OpCodes.Stloc, _carry);
|
||||
_ilGen.Emit(OpCodes.Conv_U4);
|
||||
break;
|
||||
case AluRegOperation.Subtract:
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Sub);
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L);
|
||||
_ilGen.Emit(OpCodes.Clt_Un);
|
||||
_ilGen.Emit(OpCodes.Stloc, _carry);
|
||||
_ilGen.Emit(OpCodes.Conv_U4);
|
||||
break;
|
||||
case AluRegOperation.SubtractWithBorrow:
|
||||
EmitLoadGprA(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4_1);
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _carry);
|
||||
_ilGen.Emit(OpCodes.Sub);
|
||||
_ilGen.Emit(OpCodes.Conv_U8);
|
||||
_ilGen.Emit(OpCodes.Sub);
|
||||
_ilGen.Emit(OpCodes.Sub);
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L);
|
||||
_ilGen.Emit(OpCodes.Clt_Un);
|
||||
_ilGen.Emit(OpCodes.Stloc, _carry);
|
||||
_ilGen.Emit(OpCodes.Conv_U4);
|
||||
break;
|
||||
case AluRegOperation.BitwiseExclusiveOr:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Xor);
|
||||
break;
|
||||
case AluRegOperation.BitwiseOr:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Or);
|
||||
break;
|
||||
case AluRegOperation.BitwiseAnd:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
break;
|
||||
case AluRegOperation.BitwiseAndNot:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.Not);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
break;
|
||||
case AluRegOperation.BitwiseNotAnd:
|
||||
EmitLoadGprA(opCode);
|
||||
EmitLoadGprB(opCode);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Not);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid operation \"{aluOp}\" on instruction 0x{opCode:X8}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a immediate value on the IL evaluation stack.
|
||||
/// </summary>
|
||||
/// <param name="opCode">Instruction from where the immediate should be extracted</param>
|
||||
private void EmitLoadImm(int opCode)
|
||||
{
|
||||
// Note: The immediate is signed, the sign-extension is intended here.
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, opCode >> 14);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a value from the General Purpose register specified as first operand on the IL evaluation stack.
|
||||
/// </summary>
|
||||
/// <param name="opCode">Instruction from where the register number should be extracted</param>
|
||||
private void EmitLoadGprA(int opCode)
|
||||
{
|
||||
EmitLoadGpr((opCode >> 11) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a value from the General Purpose register specified as second operand on the IL evaluation stack.
|
||||
/// </summary>
|
||||
/// <param name="opCode">Instruction from where the register number should be extracted</param>
|
||||
private void EmitLoadGprB(int opCode)
|
||||
{
|
||||
EmitLoadGpr((opCode >> 14) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a value a General Purpose register on the IL evaluation stack.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Register number 0 has a hardcoded value of 0.
|
||||
/// </remarks>
|
||||
/// <param name="index">Register number</param>
|
||||
private void EmitLoadGpr(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Ldc_I4_0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _gprs[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits a call to the method that fetches an argument from the arguments FIFO.
|
||||
/// The argument is pushed into the IL evaluation stack.
|
||||
/// </summary>
|
||||
private void EmitFetchParam()
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Ldarg_0);
|
||||
_ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.FetchParam)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the value on the top of the IL evaluation stack into a General Purpose register.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Register number 0 does not exist, reads are hardcoded to 0, and writes are simply discarded.
|
||||
/// </remarks>
|
||||
/// <param name="opCode">Instruction from where the register number should be extracted</param>
|
||||
private void EmitStoreDstGpr(int opCode)
|
||||
{
|
||||
int index = (opCode >> 8) & 7;
|
||||
|
||||
if (index == 0)
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Pop);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Stloc_S, _gprs[index]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the value on the top of the IL evaluation stack as method address.
|
||||
/// This will be used on subsequent send calls as the destination method address.
|
||||
/// Additionally, the 6 bits starting at bit 12 will be used as increment value,
|
||||
/// added to the method address after each sent value.
|
||||
/// </summary>
|
||||
private void EmitStoreMethAddr()
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Dup);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, 0xfff);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Stloc_S, _methAddr);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, 12);
|
||||
_ilGen.Emit(OpCodes.Shr_Un);
|
||||
_ilGen.Emit(OpCodes.Ldc_I4, 0x3f);
|
||||
_ilGen.Emit(OpCodes.And);
|
||||
_ilGen.Emit(OpCodes.Stloc_S, _methIncr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the value on the top of the IL evaluation stack to the GPU,
|
||||
/// using the current method address.
|
||||
/// </summary>
|
||||
private void EmitSend()
|
||||
{
|
||||
_ilGen.Emit(OpCodes.Ldarg_1);
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _methAddr);
|
||||
_ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Send)));
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _methAddr);
|
||||
_ilGen.Emit(OpCodes.Ldloc_S, _methIncr);
|
||||
_ilGen.Emit(OpCodes.Add);
|
||||
_ilGen.Emit(OpCodes.Stloc_S, _methAddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs
Normal file
55
src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.MME
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Macro Just-in-Time compiler execution context.
|
||||
/// </summary>
|
||||
class MacroJitContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Arguments FIFO.
|
||||
/// </summary>
|
||||
public Queue<FifoWord> Fifo { get; } = new Queue<FifoWord>();
|
||||
|
||||
/// <summary>
|
||||
/// Fetches a arguments from the arguments FIFO.
|
||||
/// </summary>
|
||||
/// <returns>The call argument, or 0 if the FIFO is empty</returns>
|
||||
public int FetchParam()
|
||||
{
|
||||
if (!Fifo.TryDequeue(out var value))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value.Word;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from a GPU register.
|
||||
/// </summary>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="reg">Register offset to read</param>
|
||||
/// <returns>GPU register value</returns>
|
||||
public static int Read(IDeviceState state, int reg)
|
||||
{
|
||||
return state.Read(reg * 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a GPU method call.
|
||||
/// </summary>
|
||||
/// <param name="value">Call argument</param>
|
||||
/// <param name="state">Current GPU state</param>
|
||||
/// <param name="methAddr">Address, in words, of the method</param>
|
||||
public static void Send(int value, IDeviceState state, int methAddr)
|
||||
{
|
||||
state.Write(methAddr * 4, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs
Normal file
18
src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents temporary storage used by macros.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Size = 1024)]
|
||||
struct MmeShadowScratch
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
private uint _e0;
|
||||
#pragma warning restore CS0169
|
||||
public ref uint this[int index] => ref AsSpan()[index];
|
||||
public Span<uint> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 256);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// MME shadow RAM control mode.
|
||||
/// </summary>
|
||||
enum SetMmeShadowRamControlMode
|
||||
{
|
||||
MethodTrack = 0,
|
||||
MethodTrackWithFilter = 1,
|
||||
MethodPassthrough = 2,
|
||||
MethodReplay = 3,
|
||||
}
|
||||
}
|
||||
111
src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
Normal file
111
src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader texture properties conversion methods.
|
||||
/// </summary>
|
||||
static class ShaderTexture
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a texture target from a sampler type.
|
||||
/// </summary>
|
||||
/// <param name="type">Sampler type</param>
|
||||
/// <returns>Texture target value</returns>
|
||||
public static Target GetTarget(SamplerType type)
|
||||
{
|
||||
type &= ~(SamplerType.Indexed | SamplerType.Shadow);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case SamplerType.Texture1D:
|
||||
return Target.Texture1D;
|
||||
|
||||
case SamplerType.TextureBuffer:
|
||||
return Target.TextureBuffer;
|
||||
|
||||
case SamplerType.Texture1D | SamplerType.Array:
|
||||
return Target.Texture1DArray;
|
||||
|
||||
case SamplerType.Texture2D:
|
||||
return Target.Texture2D;
|
||||
|
||||
case SamplerType.Texture2D | SamplerType.Array:
|
||||
return Target.Texture2DArray;
|
||||
|
||||
case SamplerType.Texture2D | SamplerType.Multisample:
|
||||
return Target.Texture2DMultisample;
|
||||
|
||||
case SamplerType.Texture2D | SamplerType.Multisample | SamplerType.Array:
|
||||
return Target.Texture2DMultisampleArray;
|
||||
|
||||
case SamplerType.Texture3D:
|
||||
return Target.Texture3D;
|
||||
|
||||
case SamplerType.TextureCube:
|
||||
return Target.Cubemap;
|
||||
|
||||
case SamplerType.TextureCube | SamplerType.Array:
|
||||
return Target.CubemapArray;
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Invalid sampler type \"{type}\".");
|
||||
|
||||
return Target.Texture2D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture format from a shader image format.
|
||||
/// </summary>
|
||||
/// <param name="format">Shader image format</param>
|
||||
/// <returns>Texture format</returns>
|
||||
public static Format GetFormat(TextureFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
TextureFormat.R8Unorm => Format.R8Unorm,
|
||||
TextureFormat.R8Snorm => Format.R8Snorm,
|
||||
TextureFormat.R8Uint => Format.R8Uint,
|
||||
TextureFormat.R8Sint => Format.R8Sint,
|
||||
TextureFormat.R16Float => Format.R16Float,
|
||||
TextureFormat.R16Unorm => Format.R16Unorm,
|
||||
TextureFormat.R16Snorm => Format.R16Snorm,
|
||||
TextureFormat.R16Uint => Format.R16Uint,
|
||||
TextureFormat.R16Sint => Format.R16Sint,
|
||||
TextureFormat.R32Float => Format.R32Float,
|
||||
TextureFormat.R32Uint => Format.R32Uint,
|
||||
TextureFormat.R32Sint => Format.R32Sint,
|
||||
TextureFormat.R8G8Unorm => Format.R8G8Unorm,
|
||||
TextureFormat.R8G8Snorm => Format.R8G8Snorm,
|
||||
TextureFormat.R8G8Uint => Format.R8G8Uint,
|
||||
TextureFormat.R8G8Sint => Format.R8G8Sint,
|
||||
TextureFormat.R16G16Float => Format.R16G16Float,
|
||||
TextureFormat.R16G16Unorm => Format.R16G16Unorm,
|
||||
TextureFormat.R16G16Snorm => Format.R16G16Snorm,
|
||||
TextureFormat.R16G16Uint => Format.R16G16Uint,
|
||||
TextureFormat.R16G16Sint => Format.R16G16Sint,
|
||||
TextureFormat.R32G32Float => Format.R32G32Float,
|
||||
TextureFormat.R32G32Uint => Format.R32G32Uint,
|
||||
TextureFormat.R32G32Sint => Format.R32G32Sint,
|
||||
TextureFormat.R8G8B8A8Unorm => Format.R8G8B8A8Unorm,
|
||||
TextureFormat.R8G8B8A8Snorm => Format.R8G8B8A8Snorm,
|
||||
TextureFormat.R8G8B8A8Uint => Format.R8G8B8A8Uint,
|
||||
TextureFormat.R8G8B8A8Sint => Format.R8G8B8A8Sint,
|
||||
TextureFormat.R16G16B16A16Float => Format.R16G16B16A16Float,
|
||||
TextureFormat.R16G16B16A16Unorm => Format.R16G16B16A16Unorm,
|
||||
TextureFormat.R16G16B16A16Snorm => Format.R16G16B16A16Snorm,
|
||||
TextureFormat.R16G16B16A16Uint => Format.R16G16B16A16Uint,
|
||||
TextureFormat.R16G16B16A16Sint => Format.R16G16B16A16Sint,
|
||||
TextureFormat.R32G32B32A32Float => Format.R32G32B32A32Float,
|
||||
TextureFormat.R32G32B32A32Uint => Format.R32G32B32A32Uint,
|
||||
TextureFormat.R32G32B32A32Sint => Format.R32G32B32A32Sint,
|
||||
TextureFormat.R10G10B10A2Unorm => Format.R10G10B10A2Unorm,
|
||||
TextureFormat.R10G10B10A2Uint => Format.R10G10B10A2Uint,
|
||||
TextureFormat.R11G11B10Float => Format.R11G11B10Float,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,115 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend manager.
|
||||
/// </summary>
|
||||
class AdvancedBlendManager
|
||||
{
|
||||
private const int InstructionRamSize = 128;
|
||||
private const int InstructionRamSizeMask = InstructionRamSize - 1;
|
||||
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
private readonly uint[] _code;
|
||||
private int _ip;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the advanced blend manager.
|
||||
/// </summary>
|
||||
/// <param name="state">GPU state of the channel owning this manager</param>
|
||||
public AdvancedBlendManager(DeviceStateWithShadow<ThreedClassState> state)
|
||||
{
|
||||
_state = state;
|
||||
_code = new uint[InstructionRamSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start offset of the blend microcode in memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadBlendUcodeStart(int argument)
|
||||
{
|
||||
_ip = argument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one word of blend microcode.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void LoadBlendUcodeInstruction(int argument)
|
||||
{
|
||||
_code[_ip++ & InstructionRamSizeMask] = (uint)argument;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to identify the current advanced blend function being used,
|
||||
/// given the current state and microcode that was uploaded.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">Advanced blend descriptor</param>
|
||||
/// <returns>True if the function was found, false otherwise</returns>
|
||||
public bool TryGetAdvancedBlend(out AdvancedBlendDescriptor descriptor)
|
||||
{
|
||||
Span<uint> currentCode = new Span<uint>(_code);
|
||||
byte codeLength = (byte)_state.State.BlendUcodeSize;
|
||||
|
||||
if (currentCode.Length > codeLength)
|
||||
{
|
||||
currentCode = currentCode.Slice(0, codeLength);
|
||||
}
|
||||
|
||||
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
|
||||
|
||||
descriptor = default;
|
||||
|
||||
if (!AdvancedBlendPreGenTable.Entries.TryGetValue(hash, out var entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.Constants != null)
|
||||
{
|
||||
bool constantsMatch = true;
|
||||
|
||||
for (int i = 0; i < entry.Constants.Length; i++)
|
||||
{
|
||||
RgbFloat constant = entry.Constants[i];
|
||||
RgbHalf constant2 = _state.State.BlendUcodeConstants[i];
|
||||
|
||||
if ((Half)constant.R != constant2.UnpackR() ||
|
||||
(Half)constant.G != constant2.UnpackG() ||
|
||||
(Half)constant.B != constant2.UnpackB())
|
||||
{
|
||||
constantsMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!constantsMatch)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.Alpha.Enable != _state.State.BlendUcodeEnable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.Alpha.Enable == BlendUcodeEnable.EnableRGBA &&
|
||||
(entry.Alpha.AlphaOp != _state.State.BlendStateCommon.AlphaOp ||
|
||||
entry.Alpha.AlphaSrcFactor != _state.State.BlendStateCommon.AlphaSrcFactor ||
|
||||
entry.Alpha.AlphaDstFactor != _state.State.BlendStateCommon.AlphaDstFactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
descriptor = new AdvancedBlendDescriptor(entry.Op, entry.Overlap, entry.SrcPreMultiplied);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend function entry.
|
||||
/// </summary>
|
||||
struct AdvancedBlendEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend operation.
|
||||
/// </summary>
|
||||
public AdvancedBlendOp Op { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend overlap mode.
|
||||
/// </summary>
|
||||
public AdvancedBlendOverlap Overlap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whenever the source input is pre-multiplied.
|
||||
/// </summary>
|
||||
public bool SrcPreMultiplied { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constants used by the microcode.
|
||||
/// </summary>
|
||||
public RgbFloat[] Constants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fixed function alpha state.
|
||||
/// </summary>
|
||||
public FixedFunctionAlpha Alpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new advanced blend function entry.
|
||||
/// </summary>
|
||||
/// <param name="op">Advanced blend operation</param>
|
||||
/// <param name="overlap">Advanced blend overlap mode</param>
|
||||
/// <param name="srcPreMultiplied">Whenever the source input is pre-multiplied</param>
|
||||
/// <param name="constants">Constants used by the microcode</param>
|
||||
/// <param name="alpha">Fixed function alpha state</param>
|
||||
public AdvancedBlendEntry(
|
||||
AdvancedBlendOp op,
|
||||
AdvancedBlendOverlap overlap,
|
||||
bool srcPreMultiplied,
|
||||
RgbFloat[] constants,
|
||||
FixedFunctionAlpha alpha)
|
||||
{
|
||||
Op = op;
|
||||
Overlap = overlap;
|
||||
SrcPreMultiplied = srcPreMultiplied;
|
||||
Constants = constants;
|
||||
Alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-generated hash table with advanced blend functions used by the driver.
|
||||
/// </summary>
|
||||
static class AdvancedBlendPreGenTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend functions dictionary.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyDictionary<Hash128, AdvancedBlendEntry> Entries = new Dictionary<Hash128, AdvancedBlendEntry>()
|
||||
{
|
||||
{ new Hash128(0x19ECF57B83DE31F7, 0x5BAE759246F264C0), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xDE1B14A356A1A9ED, 0x59D803593C607C1D), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A3C3A6D32DEC368, 0xBCAE519EC6AAA045), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6FD380261A63B240, 0x17C3B335DBB9E3DB), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x1D39164823D3A2D1, 0xC45350959CE1C8FB), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x18DF09FF53B129FE, 0xC02EDA33C36019F6), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x5973E583271EBF06, 0x711497D75D1272E0), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x4759E0E5DA54D5E8, 0x1FDD57C0C38AFA1F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x337684D43CCE97FA, 0x0139E30CC529E1C9), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xDA59E85D8428992D, 0x1D3D7C64C9EF0132), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x9455B949298CE805, 0xE73D3301518BE98A), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xBDD3B4DEDBE336AA, 0xBFA4DCD50D535DEE), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x22D4E970A028649A, 0x4F3FCB055FCED965), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xA346A91311D72114, 0x151A27A3FB0A1904), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8A307241061FACD6, 0xA39D1826440B8EE7), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB3BE569485EFFFE0, 0x0BA4E269B3CFB165), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x36FCA3277DC11822, 0x2BC0F6CAC2029672), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4A6226AF2DE9BD7F, 0xEB890D7DA716F73A), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF364CAA94E160FEB, 0xBF364512C72A3797), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6BF791AB4AC19C87, 0x6FA17A994EA0FCDE), new AdvancedBlendEntry(AdvancedBlendOp.InvertOvg, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x053C75A0AE0BB222, 0x03C791FEEB59754C), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x25762AB40B6CBDE9, 0x595E9A968AC4F01C), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xC2D05E2DBE16955D, 0xB8659C7A3FCFA7CE), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x223F220B8F74CBFB, 0xD3DD19D7C39209A5), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xD0DAE57A9F1FE78A, 0x353796BCFB8CE30B), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x601C8CBEC07FF8FF, 0xB8E22882360E8695), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x3A55B7B78C76A7A8, 0x206F503B2D9FFEAA), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x80BC65C7831388E5, 0xC652457B2C766AEC), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3D3A912E5833EE13, 0x307895951349EE33), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x289105BE92E81803, 0xFD8F1F03D15C53B4), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x007AE3BD140764EB, 0x0EE05A0D2E80BBAE), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x77F7EE0DB3FDDB96, 0xDEA47C881306DB3E), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x66F4E9A7D73CA157, 0x1486058A177DB11C), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x593E9F331612D618, 0x9D217BEFA4EB919A), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x0A5194C5E6891106, 0xDD8EC6586106557C), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8D77173D5E06E916, 0x06AB190E7D10F4D4), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x655B4EBC148981DA, 0x455999EF2B9BD28A), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x98F5437D5F518929, 0xBFF4A6E83183DB63), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6ADDEFE3B9CEF2FD, 0xB6F6272AFECB1AAB), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x80953F0953BF05B1, 0xD59ABFAA34F8196F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xA401D9AA2A39C121, 0xFC0C8005C22AD7E3), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x06274FB7CA9CDD22, 0x6CE8188B1A9AB6EF), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x0B079BE7F7F70817, 0xB72E7736CA51E321), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x66215C99403CEDDE, 0x900B733D62204C48), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x12DEF2AD900CAD6C, 0x58CF5CC3004910DF), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x272BA3A49F64DAE4, 0xAC70B96C00A99EAF), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x206C34AAA7D3F545, 0xDA4B30CACAA483A0), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3D93494920D257BE, 0xDCC573BE1F5F4449), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0D7417D80191107B, 0xEAF40547827E005F), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xEC1B03E8C883F9C9, 0x2D3CA044C58C01B4), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x58A19A0135D68B31, 0x82F35B97AED068E5), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x20489F9AB36CC0E3, 0x20499874219E35EE), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xBB176935E5EE05BF, 0x95B26D4D30EA7A14), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x5FF9393C908ACFED, 0x068B0BD875773ABF), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x03181F8711C9802C, 0x6B02C7C6B224FE7B), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x2EE2209021F6B977, 0xF3AFA1491B8B89FC), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xD8BA4DD2EDE4DC9E, 0x01006114977CF715), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD156B99835A2D8ED, 0x2D0BEE9E135EA7A7), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x20CE8C898ED4BE27, 0x1514900B6F5E8F66), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCDE5F743820BA2D9, 0x917845FE2ECB083D), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xEB03DF4A0C1D14CD, 0xBAE2E831C6E8FFE4), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1DC9E49AABC779AC, 0x4053A1441EB713D3), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xFBDEF776248F7B3E, 0xE05EEFD65AC47CB7), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x415A1A48E03AA6E7, 0x046D7EE33CA46B9A), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x59A6901EC9BB2041, 0x2F3E19CE5EEC3EBE), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x044B2B6E105221DA, 0x3089BBC033F994AF), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x374A5A24AA8E6CC5, 0x29930FAA6215FA2B), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x30CD0F7AF0CF26F9, 0x06CCA6744DE7DCF5), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A6C9A1F6FE494A5, 0xA0CFAF77617E54DD), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x081AF6DAAB1C8717, 0xBFEDCE59AE3DC9AC), new AdvancedBlendEntry(AdvancedBlendOp.Dst, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3518E44573AB68BA, 0xC96EE71AF9F8F546), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF89E81FE8D73C96F, 0x4583A04577A0F21C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xDF4026421CB61119, 0x14115A1F5139AFC7), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x91A20262C3E3A695, 0x0B3A102BFCDC6B1C), new AdvancedBlendEntry(AdvancedBlendOp.DstIn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x44F4C7CCFEB9EBFA, 0xF68394E6D56E5C2F), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB89F17C7021E9760, 0x430357EE0F7188EF), new AdvancedBlendEntry(AdvancedBlendOp.DstOut, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xDA2D20EA4242B8A0, 0x0D1EC05B72E3838F), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x855DFEE1208D11B9, 0x77C6E3DDCFE30B85), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x9B3808439683FD58, 0x123DCBE4705AB25E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xA42CF045C248A00A, 0x0C6C63C24EA0B0C1), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x320A83B6D00C8059, 0x796EDAB3EB7314BC), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x45253AC9ABFFC613, 0x8F92EA70195FB573), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x1A5D263B588274B6, 0x167D305F6C794179), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x709C1A837FE966AC, 0x75D8CE49E8A78EDB), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8265C26F85E4145F, 0x932E6CCBF37CB600), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3F252B3FEF983F27, 0x9370D7EEFEFA1A9E), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x66A334A4AEA41078, 0xCB52254E1E395231), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xFDD05C53B25F0035, 0xB7E3ECEE166C222F), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x25D932A77FFED81A, 0xA50D797B0FCA94E8), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4A953B6F5F7D341C, 0xDC05CFB50DDB5DC1), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x838CB660C4F41F6D, 0x9E7D958697543495), new AdvancedBlendEntry(AdvancedBlendOp.Invert, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4DF6EC1348A8F797, 0xA128E0CD69DB5A64), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x178CDFAB9A015295, 0x2BF40EA72E596D57), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x338FC99050E56AFD, 0x2AF41CF82BE602BF), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x62E02ED60D1E978E, 0xBF726B3E68C11E4D), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xFBAF92DD4C101502, 0x7AF2EDA6596B819D), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x0EF1241F65D4B50A, 0xE8D85DFA6AEDDB84), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x77FE024B5C9D4A18, 0xF19D48A932F6860F), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, true, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x9C88CBFA2E09D857, 0x0A0361704CBEEE1D), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x5B94127FA190E640, 0x8D1FEFF837A91268), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xB9C9105B7E063DDB, 0xF6A70E1D511B96FD), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xF0751AAE332B3ED1, 0xC40146F5C83C2533), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, true, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x579EB12F595F75AD, 0x151BF0504703B81B), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xF9CA152C03AC8C62, 0x1581336205E5CF47), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.DstAlphaGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x98ACD8BB5E195D0F, 0x91F937672BE899F0), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xBF97F10FC301F44C, 0x75721789F0D48548), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x1B982263B8B08A10, 0x3350C76E2E1B27DF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xFF20AC79F64EDED8, 0xAF9025B2D97B9273), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneMinusDstAlphaGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x9FFD986600FB112F, 0x384FDDF4E060139A), new AdvancedBlendEntry(AdvancedBlendOp.PlusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0425E40B5B8B3B52, 0x5880CBED7CAB631C), new AdvancedBlendEntry(AdvancedBlendOp.PlusClampedAlpha, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x16DAC8593F28623A, 0x233DBC82325B8AED), new AdvancedBlendEntry(AdvancedBlendOp.PlusDarker, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB37E5F234B9F0948, 0xD5F957A2ECD98FD6), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xCA0FDADD1D20DBE3, 0x1A5C15CCBF1AC538), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x1C48304D73A9DF3A, 0x891DB93FA36E3450), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x53200F2279B7FA39, 0x051C2462EBF6789C), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xB88BFB80714DCD5C, 0xEBD6938D744E6A41), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xE33DC2A25FC1A976, 0x08B3DBB1F3027D45), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xCE97E71615370316, 0xE131AE49D3A4D62B), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xE059FD265149B256, 0x94AF817AC348F61F), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x16D31333D477E231, 0x9A98AAC84F72CC62), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x47FC3B0776366D3C, 0xE96D9BD83B277874), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x7230401E3FEA1F3B, 0xF0D15F05D3D1E309), new AdvancedBlendEntry(AdvancedBlendOp.Minus, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.ReverseSubtractGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x188212F9303742F5, 0x100C51CB96E03591), new AdvancedBlendEntry(AdvancedBlendOp.MinusClamped, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x52B755D296B44DC5, 0x4003B87275625973), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xD873ED973ADF7EAD, 0x73E68B57D92034E7), new AdvancedBlendEntry(AdvancedBlendOp.Contrast, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f), new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x471F9FA34B945ACB, 0x10524D1410B3C402), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x99F569454EA0EF32, 0x6FC70A8B3A07DC8B), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x5AD55F950067AC7E, 0x4BA60A4FBABDD0AC), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x03FF2C858C9C4C5B, 0xE95AE7F561FB60E9), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x6DC0E510C7BCF9D2, 0xAE805D7CECDCB5C1), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x44832332CED5C054, 0x2F8D5536C085B30A), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x4AB4D387618AC51F, 0x495B46E0555F4B32), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x99282B49405A01A8, 0xD6FA93F864F24A8E), new AdvancedBlendEntry(AdvancedBlendOp.Red, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x37B30C1064FBD23E, 0x5D068366F42317C2), new AdvancedBlendEntry(AdvancedBlendOp.Green, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x760FAE9D59E04BC2, 0xA40AD483EA01435E), new AdvancedBlendEntry(AdvancedBlendOp.Blue, AdvancedBlendOverlap.Uncorrelated, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xE786950FD9D1C6EF, 0xF9FDD5AF6451D239), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x052458BB4788B0CA, 0x8AC58FDCA1F45EF5), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x6AFC3837D1D31920, 0xB9D49C2FE49642C6), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0xAFC2911949317E01, 0xD5B63636F5CB3422), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Uncorrelated, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneMinusSrcAlphaGl)) },
|
||||
{ new Hash128(0x13B46DF507CC2C53, 0x86DE26517E6BF0A7), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x5C372442474BE410, 0x79ECD3C0C496EF2E), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x74AAB45DBF5336E9, 0x01BFC4E181DAD442), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x43239E282A36C85C, 0x36FB65560E46AD0F), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1A3BA8A7583B8F7A, 0xE64E41D548033180), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x32BBB9859E9B565D, 0x3D5CE94FE55F18B5), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD947A0766AE3C0FC, 0x391E5D53E86F4ED6), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0xBD9A7C08BDFD8CE6, 0x905407634901355E), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x8395475BCB0D7A8C, 0x48AF5DD501D44A70), new AdvancedBlendEntry(AdvancedBlendOp.Plus, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x80AAC23FEBD4A3E5, 0xEA8C70F0B4DE52DE), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x2F3AD1B0F1B3FD09, 0xC0EBC784BFAB8EA3), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x52B54032F2F70BFF, 0xC941D6FDED674765), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCA7B86F72EC6A99B, 0x55868A131AFE359E), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x377919B60BD133CA, 0x0FD611627664EF40), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9D4A0C5EE1153887, 0x7B869EBA218C589B), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x311F2A858545D123, 0xB4D09C802480AD62), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xCF78AA6A83AFA689, 0x9DC48B0C2182A3E1), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xC3018CD6F1CF62D1, 0x016E32DD9087B1BB), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9CB62CE0E956EE29, 0x0FB67F503E60B3AD), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3589A13C16EF3BFA, 0x15B29BFC91F3BDFB), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x3502CA5FB7529917, 0xFA51BFD0D1688071), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x62ADC25AD6D0A923, 0x76CB6D238276D3A3), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x09FDEB1116A9D52C, 0x85BB8627CD5C2733), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x0709FED1B65E18EB, 0x5BC3AA4D99EC19CF), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xB18D28AE5DE4C723, 0xE820AA2B75C9C02E), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x6743C51621497480, 0x4B164E40858834AE), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x63D1E181E34A2944, 0x1AE292C9D9F12819), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Disjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x079523298250BFF6, 0xC0C793510603CDB5), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x4C9D0A973C805EA6, 0xD1FF59AD5156B93C), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x1E914678F3057BCD, 0xD503AE389C12D229), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0x9FDBADE5556C5311, 0x03F0CBC798FC5C94), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Disjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xE39451534635403C, 0x606CC1CA1F452388), new AdvancedBlendEntry(AdvancedBlendOp.Src, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x1D39F0F0A1008AA6, 0xBFDF2B97E6C3F125), new AdvancedBlendEntry(AdvancedBlendOp.SrcOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xDB81BED30D5BDBEA, 0xAF0B2856EB93AD2C), new AdvancedBlendEntry(AdvancedBlendOp.DstOver, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x83F69CCF1D0A79B6, 0x70D31332797430AC), new AdvancedBlendEntry(AdvancedBlendOp.SrcIn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MinimumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x7B87F807AB7A8F5C, 0x1241A2A01FB31771), new AdvancedBlendEntry(AdvancedBlendOp.SrcOut, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xF557172E20D5272D, 0xC1961F8C7A5D2820), new AdvancedBlendEntry(AdvancedBlendOp.SrcAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xA8476B3944DBBC9B, 0x84A2F6AF97B15FDF), new AdvancedBlendEntry(AdvancedBlendOp.DstAtop, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.OneGl, BlendFactor.ZeroGl)) },
|
||||
{ new Hash128(0x3259602B55414DA3, 0x72AACCC00B5A9D10), new AdvancedBlendEntry(AdvancedBlendOp.Xor, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, 0, 0, 0)) },
|
||||
{ new Hash128(0xC0CB8C10F36EDCD6, 0x8C2D088AD8191E1C), new AdvancedBlendEntry(AdvancedBlendOp.Multiply, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x81806C451C6255EF, 0x5AA8AC9A08941A15), new AdvancedBlendEntry(AdvancedBlendOp.Screen, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xE55A6537F4568198, 0xCA8735390B799B19), new AdvancedBlendEntry(AdvancedBlendOp.Overlay, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x5C044BA14536DDA3, 0xBCE0123ED7D510EC), new AdvancedBlendEntry(AdvancedBlendOp.Darken, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6788346C405BE130, 0x372A4BB199C01F9F), new AdvancedBlendEntry(AdvancedBlendOp.Lighten, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x510EDC2A34E2856B, 0xE1727A407E294254), new AdvancedBlendEntry(AdvancedBlendOp.ColorDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x4B7BE01BD398C7A8, 0x5BFF79BC00672C18), new AdvancedBlendEntry(AdvancedBlendOp.ColorBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x213B43845540CFEC, 0xDA857411CF1CCFCE), new AdvancedBlendEntry(AdvancedBlendOp.HardLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x765AFA6732E783F1, 0x8F1CABF1BC78A014), new AdvancedBlendEntry(AdvancedBlendOp.SoftLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.2605f, 0.2605f, 0.2605f), new RgbFloat(-0.7817f, -0.7817f, -0.7817f), new RgbFloat(0.3022f, 0.3022f, 0.3022f), new RgbFloat(0.2192f, 0.2192f, 0.2192f), new RgbFloat(0.25f, 0.25f, 0.25f), new RgbFloat(16f, 16f, 16f), new RgbFloat(12f, 12f, 12f), new RgbFloat(3f, 3f, 3f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xA4A5DE1CC06F6CB1, 0xA0634A0011001709), new AdvancedBlendEntry(AdvancedBlendOp.Difference, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x81F32BD8816EA796, 0x697EE86683165170), new AdvancedBlendEntry(AdvancedBlendOp.Exclusion, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xB870C209EAA5F092, 0xAF5FD923909CAA1F), new AdvancedBlendEntry(AdvancedBlendOp.InvertRGB, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.AddGl, BlendFactor.ZeroGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x3649A9F5C936FB83, 0xDD7C834897AA182A), new AdvancedBlendEntry(AdvancedBlendOp.LinearDodge, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD72A2B1097A5995C, 0x3D41B2763A913654), new AdvancedBlendEntry(AdvancedBlendOp.LinearBurn, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x551E212B9F6C454A, 0xB0DFA05BEB3C37FA), new AdvancedBlendEntry(AdvancedBlendOp.VividLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.5f, 0.5f, 0.5f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x681B5A313B7416BF, 0xCB1CBAEEB4D81500), new AdvancedBlendEntry(AdvancedBlendOp.LinearLight, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(2f, 2f, 2f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x9343A18BD4B16777, 0xEDB4AC1C8972C3A4), new AdvancedBlendEntry(AdvancedBlendOp.PinLight, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xC960BF6D8519DE28, 0x78D8557FD405D119), new AdvancedBlendEntry(AdvancedBlendOp.HardMix, AdvancedBlendOverlap.Conjoint, false, Array.Empty<RgbFloat>(), new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x65A7B01FDC73A46C, 0x297E096ED5CC4D8A), new AdvancedBlendEntry(AdvancedBlendOp.HslHue, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0xD9C99BA4A6CDC13B, 0x3CFF0ACEDC2EE150), new AdvancedBlendEntry(AdvancedBlendOp.HslSaturation, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x6BC00DA6EB922BD1, 0x5FD4C11F2A685234), new AdvancedBlendEntry(AdvancedBlendOp.HslColor, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
{ new Hash128(0x8652300E32D93050, 0x9460E7B449132371), new AdvancedBlendEntry(AdvancedBlendOp.HslLuminosity, AdvancedBlendOverlap.Conjoint, false, new[] { new RgbFloat(0.3f, 0.59f, 0.11f) }, new FixedFunctionAlpha(BlendUcodeEnable.EnableRGB, BlendOp.MaximumGl, BlendFactor.OneGl, BlendFactor.OneGl)) },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed function alpha state used for a advanced blend function.
|
||||
/// </summary>
|
||||
struct FixedFunctionAlpha
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed function alpha state with alpha blending disabled.
|
||||
/// </summary>
|
||||
public static FixedFunctionAlpha Disabled => new FixedFunctionAlpha(BlendUcodeEnable.EnableRGBA, default, default, default);
|
||||
|
||||
/// <summary>
|
||||
/// Individual enable bits for the RGB and alpha components.
|
||||
/// </summary>
|
||||
public BlendUcodeEnable Enable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Alpha blend operation.
|
||||
/// </summary>
|
||||
public BlendOp AlphaOp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Value multiplied with the blend source operand.
|
||||
/// </summary>
|
||||
public BlendFactor AlphaSrcFactor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Value multiplied with the blend destination operand.
|
||||
/// </summary>
|
||||
public BlendFactor AlphaDstFactor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend fixed function alpha state.
|
||||
/// </summary>
|
||||
/// <param name="enable">Individual enable bits for the RGB and alpha components</param>
|
||||
/// <param name="alphaOp">Alpha blend operation</param>
|
||||
/// <param name="alphaSrc">Value multiplied with the blend source operand</param>
|
||||
/// <param name="alphaDst">Value multiplied with the blend destination operand</param>
|
||||
public FixedFunctionAlpha(BlendUcodeEnable enable, BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst)
|
||||
{
|
||||
Enable = enable;
|
||||
AlphaOp = alphaOp;
|
||||
AlphaSrcFactor = alphaSrc;
|
||||
AlphaDstFactor = alphaDst;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend fixed function alpha state.
|
||||
/// </summary>
|
||||
/// <param name="alphaOp">Alpha blend operation</param>
|
||||
/// <param name="alphaSrc">Value multiplied with the blend source operand</param>
|
||||
/// <param name="alphaDst">Value multiplied with the blend destination operand</param>
|
||||
public FixedFunctionAlpha(BlendOp alphaOp, BlendFactor alphaSrc, BlendFactor alphaDst) : this(BlendUcodeEnable.EnableRGB, alphaOp, alphaSrc, alphaDst)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode assembly function delegate.
|
||||
/// </summary>
|
||||
/// <param name="asm">Assembler</param>
|
||||
/// <returns>Fixed function alpha state for the microcode</returns>
|
||||
delegate FixedFunctionAlpha GenUcodeFunc(ref UcodeAssembler asm);
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend microcode state.
|
||||
/// </summary>
|
||||
struct AdvancedBlendUcode
|
||||
{
|
||||
/// <summary>
|
||||
/// Advanced blend operation.
|
||||
/// </summary>
|
||||
public AdvancedBlendOp Op { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Advanced blend overlap mode.
|
||||
/// </summary>
|
||||
public AdvancedBlendOverlap Overlap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whenever the source input is pre-multiplied.
|
||||
/// </summary>
|
||||
public bool SrcPreMultiplied { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Fixed function alpha state.
|
||||
/// </summary>
|
||||
public FixedFunctionAlpha Alpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Microcode.
|
||||
/// </summary>
|
||||
public uint[] Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constants used by the microcode.
|
||||
/// </summary>
|
||||
public RgbFloat[] Constants { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new advanced blend state.
|
||||
/// </summary>
|
||||
/// <param name="op">Advanced blend operation</param>
|
||||
/// <param name="overlap">Advanced blend overlap mode</param>
|
||||
/// <param name="srcPreMultiplied">Whenever the source input is pre-multiplied</param>
|
||||
/// <param name="genFunc">Function that will generate the advanced blend microcode</param>
|
||||
public AdvancedBlendUcode(
|
||||
AdvancedBlendOp op,
|
||||
AdvancedBlendOverlap overlap,
|
||||
bool srcPreMultiplied,
|
||||
GenUcodeFunc genFunc)
|
||||
{
|
||||
Op = op;
|
||||
Overlap = overlap;
|
||||
SrcPreMultiplied = srcPreMultiplied;
|
||||
|
||||
UcodeAssembler asm = new UcodeAssembler();
|
||||
Alpha = genFunc(ref asm);
|
||||
Code = asm.GetCode();
|
||||
Constants = asm.GetConstants();
|
||||
}
|
||||
}
|
||||
}
|
||||
305
src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs
Normal file
305
src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs
Normal file
@@ -0,0 +1,305 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
||||
{
|
||||
/// <summary>
|
||||
/// Blend microcode instruction.
|
||||
/// </summary>
|
||||
enum Instruction
|
||||
{
|
||||
Mmadd = 0,
|
||||
Mmsub = 1,
|
||||
Min = 2,
|
||||
Max = 3,
|
||||
Rcp = 4,
|
||||
Add = 5,
|
||||
Sub = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode condition code.
|
||||
/// </summary>
|
||||
enum CC
|
||||
{
|
||||
F = 0,
|
||||
T = 1,
|
||||
EQ = 2,
|
||||
NE = 3,
|
||||
LT = 4,
|
||||
LE = 5,
|
||||
GT = 6,
|
||||
GE = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode opend B or D value.
|
||||
/// </summary>
|
||||
enum OpBD
|
||||
{
|
||||
ConstantZero = 0x0,
|
||||
ConstantOne = 0x1,
|
||||
SrcRGB = 0x2,
|
||||
SrcAAA = 0x3,
|
||||
OneMinusSrcAAA = 0x4,
|
||||
DstRGB = 0x5,
|
||||
DstAAA = 0x6,
|
||||
OneMinusDstAAA = 0x7,
|
||||
Temp0 = 0x9,
|
||||
Temp1 = 0xa,
|
||||
Temp2 = 0xb,
|
||||
PBR = 0xc,
|
||||
ConstantRGB = 0xd
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode operand A or C value.
|
||||
/// </summary>
|
||||
enum OpAC
|
||||
{
|
||||
SrcRGB = 0,
|
||||
DstRGB = 1,
|
||||
SrcAAA = 2,
|
||||
DstAAA = 3,
|
||||
Temp0 = 4,
|
||||
Temp1 = 5,
|
||||
Temp2 = 6,
|
||||
PBR = 7
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode destination operand.
|
||||
/// </summary>
|
||||
enum OpDst
|
||||
{
|
||||
Temp0 = 0,
|
||||
Temp1 = 1,
|
||||
Temp2 = 2,
|
||||
PBR = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode input swizzle.
|
||||
/// </summary>
|
||||
enum Swizzle
|
||||
{
|
||||
RGB = 0,
|
||||
GBR = 1,
|
||||
RRR = 2,
|
||||
GGG = 3,
|
||||
BBB = 4,
|
||||
RToA = 5
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode output components.
|
||||
/// </summary>
|
||||
enum WriteMask
|
||||
{
|
||||
RGB = 0,
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Floating-point RGB color values.
|
||||
/// </summary>
|
||||
struct RgbFloat
|
||||
{
|
||||
/// <summary>
|
||||
/// Red component value.
|
||||
/// </summary>
|
||||
public float R { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Green component value.
|
||||
/// </summary>
|
||||
public float G { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Blue component value.
|
||||
/// </summary>
|
||||
public float B { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new floating-point RGB value.
|
||||
/// </summary>
|
||||
/// <param name="r">Red component value</param>
|
||||
/// <param name="g">Green component value</param>
|
||||
/// <param name="b">Blue component value</param>
|
||||
public RgbFloat(float r, float g, float b)
|
||||
{
|
||||
R = r;
|
||||
G = g;
|
||||
B = b;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode destination operand, including swizzle, write mask and condition code update flag.
|
||||
/// </summary>
|
||||
struct Dest
|
||||
{
|
||||
public static Dest Temp0 => new Dest(OpDst.Temp0, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest Temp1 => new Dest(OpDst.Temp1, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest Temp2 => new Dest(OpDst.Temp2, Swizzle.RGB, WriteMask.RGB, false);
|
||||
public static Dest PBR => new Dest(OpDst.PBR, Swizzle.RGB, WriteMask.RGB, false);
|
||||
|
||||
public Dest GBR => new Dest(Dst, Swizzle.GBR, WriteMask, WriteCC);
|
||||
public Dest RRR => new Dest(Dst, Swizzle.RRR, WriteMask, WriteCC);
|
||||
public Dest GGG => new Dest(Dst, Swizzle.GGG, WriteMask, WriteCC);
|
||||
public Dest BBB => new Dest(Dst, Swizzle.BBB, WriteMask, WriteCC);
|
||||
public Dest RToA => new Dest(Dst, Swizzle.RToA, WriteMask, WriteCC);
|
||||
|
||||
public Dest R => new Dest(Dst, Swizzle, WriteMask.R, WriteCC);
|
||||
public Dest G => new Dest(Dst, Swizzle, WriteMask.G, WriteCC);
|
||||
public Dest B => new Dest(Dst, Swizzle, WriteMask.B, WriteCC);
|
||||
|
||||
public Dest CC => new Dest(Dst, Swizzle, WriteMask, true);
|
||||
|
||||
public OpDst Dst { get; }
|
||||
public Swizzle Swizzle { get; }
|
||||
public WriteMask WriteMask { get; }
|
||||
public bool WriteCC { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend microcode destination operand.
|
||||
/// </summary>
|
||||
/// <param name="dst">Operand</param>
|
||||
/// <param name="swizzle">Swizzle</param>
|
||||
/// <param name="writeMask">Write maks</param>
|
||||
/// <param name="writeCC">Indicates if condition codes should be updated</param>
|
||||
public Dest(OpDst dst, Swizzle swizzle, WriteMask writeMask, bool writeCC)
|
||||
{
|
||||
Dst = dst;
|
||||
Swizzle = swizzle;
|
||||
WriteMask = writeMask;
|
||||
WriteCC = writeCC;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode operaiton.
|
||||
/// </summary>
|
||||
struct UcodeOp
|
||||
{
|
||||
public readonly uint Word;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new blend microcode operation.
|
||||
/// </summary>
|
||||
/// <param name="cc">Condition code that controls whenever the operation is executed or not</param>
|
||||
/// <param name="inst">Instruction</param>
|
||||
/// <param name="constIndex">Index on the constant table of the constant used by any constant operand</param>
|
||||
/// <param name="dest">Destination operand</param>
|
||||
/// <param name="srcA">First input operand</param>
|
||||
/// <param name="srcB">Second input operand</param>
|
||||
/// <param name="srcC">Third input operand</param>
|
||||
/// <param name="srcD">Fourth input operand</param>
|
||||
public UcodeOp(CC cc, Instruction inst, int constIndex, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Word = (uint)cc |
|
||||
((uint)inst << 3) |
|
||||
((uint)constIndex << 6) |
|
||||
((uint)srcA << 9) |
|
||||
((uint)srcB << 12) |
|
||||
((uint)srcC << 16) |
|
||||
((uint)srcD << 19) |
|
||||
((uint)dest.Swizzle << 23) |
|
||||
((uint)dest.WriteMask << 26) |
|
||||
((uint)dest.Dst << 28) |
|
||||
(dest.WriteCC ? (1u << 31) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Blend microcode assembler.
|
||||
/// </summary>
|
||||
struct UcodeAssembler
|
||||
{
|
||||
private List<uint> _code;
|
||||
private RgbFloat[] _constants;
|
||||
private int _constantIndex;
|
||||
|
||||
public void Mul(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Madd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, OpBD.ConstantOne);
|
||||
}
|
||||
|
||||
public void Mmadd(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmadd, dest, srcA, srcB, srcC, srcD);
|
||||
}
|
||||
|
||||
public void Mmsub(CC cc, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Mmsub, dest, srcA, srcB, srcC, srcD);
|
||||
}
|
||||
|
||||
public void Min(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Min, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Max(CC cc, Dest dest, OpAC srcA, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Max, dest, srcA, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Rcp(CC cc, Dest dest, OpAC srcA)
|
||||
{
|
||||
Assemble(cc, Instruction.Rcp, dest, srcA, OpBD.ConstantZero, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Mov(CC cc, Dest dest, OpBD srcB)
|
||||
{
|
||||
Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, OpBD.ConstantZero);
|
||||
}
|
||||
|
||||
public void Add(CC cc, Dest dest, OpBD srcB, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Add, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD);
|
||||
}
|
||||
|
||||
public void Sub(CC cc, Dest dest, OpBD srcB, OpBD srcD)
|
||||
{
|
||||
Assemble(cc, Instruction.Sub, dest, OpAC.SrcRGB, srcB, OpAC.SrcRGB, srcD);
|
||||
}
|
||||
|
||||
private void Assemble(CC cc, Instruction inst, Dest dest, OpAC srcA, OpBD srcB, OpAC srcC, OpBD srcD)
|
||||
{
|
||||
(_code ??= new List<uint>()).Add(new UcodeOp(cc, inst, _constantIndex, dest, srcA, srcB, srcC, srcD).Word);
|
||||
}
|
||||
|
||||
public void SetConstant(int index, float r, float g, float b)
|
||||
{
|
||||
if (_constants == null)
|
||||
{
|
||||
_constants = new RgbFloat[index + 1];
|
||||
}
|
||||
else if (_constants.Length <= index)
|
||||
{
|
||||
Array.Resize(ref _constants, index + 1);
|
||||
}
|
||||
|
||||
_constants[index] = new RgbFloat(r, g, b);
|
||||
_constantIndex = index;
|
||||
}
|
||||
|
||||
public uint[] GetCode()
|
||||
{
|
||||
return _code?.ToArray();
|
||||
}
|
||||
|
||||
public RgbFloat[] GetConstants()
|
||||
{
|
||||
return _constants;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs
Normal file
130
src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods used for conditional rendering.
|
||||
/// </summary>
|
||||
static class ConditionalRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if draws and clears should be performed, according
|
||||
/// to currently set conditional rendering conditions.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="memoryManager">Memory manager bound to the channel currently executing</param>
|
||||
/// <param name="address">Conditional rendering buffer address</param>
|
||||
/// <param name="condition">Conditional rendering condition</param>
|
||||
/// <returns>True if rendering is enabled, false otherwise</returns>
|
||||
public static ConditionalRenderEnabled GetRenderEnable(GpuContext context, MemoryManager memoryManager, GpuVa address, Condition condition)
|
||||
{
|
||||
switch (condition)
|
||||
{
|
||||
case Condition.Always:
|
||||
return ConditionalRenderEnabled.True;
|
||||
case Condition.Never:
|
||||
return ConditionalRenderEnabled.False;
|
||||
case Condition.ResultNonZero:
|
||||
return CounterNonZero(context, memoryManager, address.Pack());
|
||||
case Condition.Equal:
|
||||
return CounterCompare(context, memoryManager, address.Pack(), true);
|
||||
case Condition.NotEqual:
|
||||
return CounterCompare(context, memoryManager, address.Pack(), false);
|
||||
}
|
||||
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Invalid conditional render condition \"{condition}\".");
|
||||
|
||||
return ConditionalRenderEnabled.True;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the counter value at a given GPU memory address is non-zero.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="memoryManager">Memory manager bound to the channel currently executing</param>
|
||||
/// <param name="gpuVa">GPU virtual address of the counter value</param>
|
||||
/// <returns>True if the value is not zero, false otherwise. Returns host if handling with host conditional rendering</returns>
|
||||
private static ConditionalRenderEnabled CounterNonZero(GpuContext context, MemoryManager memoryManager, ulong gpuVa)
|
||||
{
|
||||
ICounterEvent evt = memoryManager.CounterCache.FindEvent(gpuVa);
|
||||
|
||||
if (evt == null)
|
||||
{
|
||||
return ConditionalRenderEnabled.False;
|
||||
}
|
||||
|
||||
if (context.Renderer.Pipeline.TryHostConditionalRendering(evt, 0L, false))
|
||||
{
|
||||
return ConditionalRenderEnabled.Host;
|
||||
}
|
||||
else
|
||||
{
|
||||
evt.Flush();
|
||||
return (memoryManager.Read<ulong>(gpuVa, true) != 0) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the counter at a given GPU memory address passes a specified equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="memoryManager">Memory manager bound to the channel currently executing</param>
|
||||
/// <param name="gpuVa">GPU virtual address</param>
|
||||
/// <param name="isEqual">True to check if the values are equal, false to check if they are not equal</param>
|
||||
/// <returns>True if the condition is met, false otherwise. Returns host if handling with host conditional rendering</returns>
|
||||
private static ConditionalRenderEnabled CounterCompare(GpuContext context, MemoryManager memoryManager, ulong gpuVa, bool isEqual)
|
||||
{
|
||||
ICounterEvent evt = FindEvent(memoryManager.CounterCache, gpuVa);
|
||||
ICounterEvent evt2 = FindEvent(memoryManager.CounterCache, gpuVa + 16);
|
||||
|
||||
bool useHost;
|
||||
|
||||
if (evt != null && evt2 == null)
|
||||
{
|
||||
useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, memoryManager.Read<ulong>(gpuVa + 16), isEqual);
|
||||
}
|
||||
else if (evt == null && evt2 != null)
|
||||
{
|
||||
useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt2, memoryManager.Read<ulong>(gpuVa), isEqual);
|
||||
}
|
||||
else if (evt != null && evt2 != null)
|
||||
{
|
||||
useHost = context.Renderer.Pipeline.TryHostConditionalRendering(evt, evt2, isEqual);
|
||||
}
|
||||
else
|
||||
{
|
||||
useHost = false;
|
||||
}
|
||||
|
||||
if (useHost)
|
||||
{
|
||||
return ConditionalRenderEnabled.Host;
|
||||
}
|
||||
else
|
||||
{
|
||||
evt?.Flush();
|
||||
evt2?.Flush();
|
||||
|
||||
ulong x = memoryManager.Read<ulong>(gpuVa, true);
|
||||
ulong y = memoryManager.Read<ulong>(gpuVa + 16, true);
|
||||
|
||||
return (isEqual ? x == y : x != y) ? ConditionalRenderEnabled.True : ConditionalRenderEnabled.False;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find a counter that is supposed to be written at the specified address,
|
||||
/// returning the related event.
|
||||
/// </summary>
|
||||
/// <param name="counterCache">GPU counter cache to search on</param>
|
||||
/// <param name="gpuVa">GPU virtual address where the counter is supposed to be written</param>
|
||||
/// <returns>The counter event, or null if not present</returns>
|
||||
private static ICounterEvent FindEvent(CounterCache counterCache, ulong gpuVa)
|
||||
{
|
||||
return counterCache.FindEvent(gpuVa);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs
Normal file
183
src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant buffer updater.
|
||||
/// </summary>
|
||||
class ConstantBufferUpdater
|
||||
{
|
||||
private const int UniformDataCacheSize = 512;
|
||||
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
// State associated with direct uniform buffer updates.
|
||||
// This state is used to attempt to batch together consecutive updates.
|
||||
private ulong _ubBeginCpuAddress = 0;
|
||||
private ulong _ubFollowUpAddress = 0;
|
||||
private ulong _ubByteCount = 0;
|
||||
private int _ubIndex = 0;
|
||||
private int[] _ubData = new int[UniformDataCacheSize];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the constant buffer updater.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Channel state</param>
|
||||
public ConstantBufferUpdater(GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state)
|
||||
{
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the vertex shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void BindVertex(int argument)
|
||||
{
|
||||
Bind(argument, ShaderType.Vertex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the tessellation control shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void BindTessControl(int argument)
|
||||
{
|
||||
Bind(argument, ShaderType.TessellationControl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the tessellation evaluation shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void BindTessEvaluation(int argument)
|
||||
{
|
||||
Bind(argument, ShaderType.TessellationEvaluation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the geometry shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void BindGeometry(int argument)
|
||||
{
|
||||
Bind(argument, ShaderType.Geometry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the fragment shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void BindFragment(int argument)
|
||||
{
|
||||
Bind(argument, ShaderType.Fragment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the specified shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
/// <param name="type">Shader stage that will access the uniform buffer</param>
|
||||
private void Bind(int argument, ShaderType type)
|
||||
{
|
||||
bool enable = (argument & 1) != 0;
|
||||
|
||||
int index = (argument >> 4) & 0x1f;
|
||||
|
||||
FlushUboDirty();
|
||||
|
||||
if (enable)
|
||||
{
|
||||
var uniformBuffer = _state.State.UniformBufferState;
|
||||
|
||||
ulong address = uniformBuffer.Address.Pack();
|
||||
|
||||
_channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
_channel.BufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any queued UBO updates.
|
||||
/// </summary>
|
||||
public void FlushUboDirty()
|
||||
{
|
||||
if (_ubFollowUpAddress != 0)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
Span<byte> data = MemoryMarshal.Cast<int, byte>(_ubData.AsSpan(0, (int)(_ubByteCount / 4)));
|
||||
|
||||
if (memoryManager.Physical.WriteWithRedundancyCheck(_ubBeginCpuAddress, data))
|
||||
{
|
||||
memoryManager.Physical.BufferCache.ForceDirty(memoryManager, _ubFollowUpAddress - _ubByteCount, _ubByteCount);
|
||||
}
|
||||
|
||||
_ubFollowUpAddress = 0;
|
||||
_ubIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the uniform buffer data with inline data.
|
||||
/// </summary>
|
||||
/// <param name="argument">New uniform buffer data word</param>
|
||||
public void Update(int argument)
|
||||
{
|
||||
var uniformBuffer = _state.State.UniformBufferState;
|
||||
|
||||
ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
|
||||
|
||||
if (_ubFollowUpAddress != address || _ubIndex == _ubData.Length)
|
||||
{
|
||||
FlushUboDirty();
|
||||
|
||||
_ubByteCount = 0;
|
||||
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
|
||||
}
|
||||
|
||||
_ubData[_ubIndex++] = argument;
|
||||
|
||||
_ubFollowUpAddress = address + 4;
|
||||
_ubByteCount += 4;
|
||||
|
||||
_state.State.UniformBufferState.Offset += 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the uniform buffer data with inline data.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be written to the uniform buffer</param>
|
||||
public void Update(ReadOnlySpan<int> data)
|
||||
{
|
||||
var uniformBuffer = _state.State.UniformBufferState;
|
||||
|
||||
ulong address = uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset;
|
||||
|
||||
ulong size = (ulong)data.Length * 4;
|
||||
|
||||
if (_ubFollowUpAddress != address || _ubIndex + data.Length > _ubData.Length)
|
||||
{
|
||||
FlushUboDirty();
|
||||
|
||||
_ubByteCount = 0;
|
||||
_ubBeginCpuAddress = _channel.MemoryManager.Translate(address);
|
||||
}
|
||||
|
||||
data.CopyTo(_ubData.AsSpan(_ubIndex));
|
||||
_ubIndex += data.Length;
|
||||
|
||||
_ubFollowUpAddress = address + size;
|
||||
_ubByteCount += size;
|
||||
|
||||
_state.State.UniformBufferState.Offset += data.Length * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
856
src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
Normal file
856
src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
Normal file
@@ -0,0 +1,856 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Draw manager.
|
||||
/// </summary>
|
||||
class DrawManager
|
||||
{
|
||||
// Since we don't know the index buffer size for indirect draws,
|
||||
// we must assume a minimum and maximum size and use that for buffer data update purposes.
|
||||
private const int MinIndirectIndexCount = 0x10000;
|
||||
private const int MaxIndirectIndexCount = 0x4000000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
private readonly DrawState _drawState;
|
||||
private readonly SpecializationStateUpdater _currentSpecState;
|
||||
private bool _topologySet;
|
||||
|
||||
private bool _instancedDrawPending;
|
||||
private bool _instancedIndexed;
|
||||
private bool _instancedIndexedInline;
|
||||
|
||||
private int _instancedFirstIndex;
|
||||
private int _instancedFirstVertex;
|
||||
private int _instancedFirstInstance;
|
||||
private int _instancedIndexCount;
|
||||
private int _instancedDrawStateFirst;
|
||||
private int _instancedDrawStateCount;
|
||||
|
||||
private int _instanceIndex;
|
||||
|
||||
private const int VertexBufferFirstMethodOffset = 0x35d;
|
||||
private const int IndexBufferCountMethodOffset = 0x5f8;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the draw manager.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Channel state</param>
|
||||
/// <param name="drawState">Draw state</param>
|
||||
/// <param name="spec">Specialization state updater</param>
|
||||
public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state, DrawState drawState, SpecializationStateUpdater spec)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
_drawState = drawState;
|
||||
_currentSpecState = spec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the entire state as dirty, forcing a full host state update before the next draw.
|
||||
/// </summary>
|
||||
public void ForceStateDirty()
|
||||
{
|
||||
_topologySet = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes four 8-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU8(int argument)
|
||||
{
|
||||
_drawState.IbStreamer.VbElementU8(_context.Renderer, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes two 16-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU16(int argument)
|
||||
{
|
||||
_drawState.IbStreamer.VbElementU16(_context.Renderer, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one 32-bit index buffer element.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU32(int argument)
|
||||
{
|
||||
_drawState.IbStreamer.VbElementU32(_context.Renderer, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the draw call.
|
||||
/// This draws geometry on the bound buffers based on the current GPU state.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawEnd(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawEnd(
|
||||
engine,
|
||||
_state.State.IndexBufferState.First,
|
||||
(int)_state.State.IndexBufferCount,
|
||||
_state.State.VertexBufferDrawState.First,
|
||||
_state.State.VertexBufferDrawState.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the draw call.
|
||||
/// This draws geometry on the bound buffers based on the current GPU state.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="firstIndex">Index of the first index buffer element used on the draw</param>
|
||||
/// <param name="indexCount">Number of index buffer elements used on the draw</param>
|
||||
/// <param name="drawFirstVertex">Index of the first vertex used on the draw</param>
|
||||
/// <param name="drawVertexCount">Number of vertices used on the draw</param>
|
||||
private void DrawEnd(ThreedClass engine, int firstIndex, int indexCount, int drawFirstVertex, int drawVertexCount)
|
||||
{
|
||||
ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable(
|
||||
_context,
|
||||
_channel.MemoryManager,
|
||||
_state.State.RenderEnableAddress,
|
||||
_state.State.RenderEnableCondition);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.False || _instancedDrawPending)
|
||||
{
|
||||
if (renderEnable == ConditionalRenderEnabled.False)
|
||||
{
|
||||
PerformDeferredDraws();
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_drawState.FirstIndex = firstIndex;
|
||||
_drawState.IndexCount = indexCount;
|
||||
_drawState.DrawFirstVertex = drawFirstVertex;
|
||||
_drawState.DrawVertexCount = drawVertexCount;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(false);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
bool instanced = _drawState.VsUsesInstanceId || _drawState.IsAnyVbInstanced;
|
||||
|
||||
if (instanced)
|
||||
{
|
||||
_instancedDrawPending = true;
|
||||
|
||||
int ibCount = _drawState.IbStreamer.InlineIndexCount;
|
||||
|
||||
_instancedIndexed = _drawState.DrawIndexed;
|
||||
_instancedIndexedInline = ibCount != 0;
|
||||
|
||||
_instancedFirstIndex = firstIndex;
|
||||
_instancedFirstVertex = (int)_state.State.FirstVertex;
|
||||
_instancedFirstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
_instancedIndexCount = ibCount != 0 ? ibCount : indexCount;
|
||||
|
||||
_instancedDrawStateFirst = drawFirstVertex;
|
||||
_instancedDrawStateCount = drawVertexCount;
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int firstInstance = (int)_state.State.FirstInstance;
|
||||
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
|
||||
if (inlineIndexCount != 0)
|
||||
{
|
||||
int firstVertex = (int)_state.State.FirstVertex;
|
||||
|
||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(inlineIndexCount, 1, firstIndex, firstVertex, firstInstance);
|
||||
}
|
||||
else if (_drawState.DrawIndexed)
|
||||
{
|
||||
int firstVertex = (int)_state.State.FirstVertex;
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(indexCount, 1, firstIndex, firstVertex, firstInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var drawState = _state.State.VertexBufferDrawState;
|
||||
|
||||
_context.Renderer.Pipeline.Draw(drawVertexCount, 1, drawFirstVertex, firstInstance);
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts draw.
|
||||
/// This sets primitive type and instanced draw parameters.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawBegin(int argument)
|
||||
{
|
||||
bool incrementInstance = (argument & (1 << 26)) != 0;
|
||||
bool resetInstance = (argument & (1 << 27)) == 0;
|
||||
|
||||
PrimitiveType type = (PrimitiveType)(argument & 0xffff);
|
||||
DrawBegin(incrementInstance, resetInstance, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts draw.
|
||||
/// This sets primitive type and instanced draw parameters.
|
||||
/// </summary>
|
||||
/// <param name="incrementInstance">Indicates if the current instance should be incremented</param>
|
||||
/// <param name="resetInstance">Indicates if the current instance should be set to zero</param>
|
||||
/// <param name="primitiveType">Primitive type</param>
|
||||
private void DrawBegin(bool incrementInstance, bool resetInstance, PrimitiveType primitiveType)
|
||||
{
|
||||
if (incrementInstance)
|
||||
{
|
||||
_instanceIndex++;
|
||||
}
|
||||
else if (resetInstance)
|
||||
{
|
||||
PerformDeferredDraws();
|
||||
|
||||
_instanceIndex = 0;
|
||||
}
|
||||
|
||||
PrimitiveTopology topology;
|
||||
|
||||
if (_state.State.PrimitiveTypeOverrideEnable)
|
||||
{
|
||||
PrimitiveTypeOverride typeOverride = _state.State.PrimitiveTypeOverride;
|
||||
topology = typeOverride.Convert();
|
||||
}
|
||||
else
|
||||
{
|
||||
topology = primitiveType.Convert();
|
||||
}
|
||||
|
||||
UpdateTopology(topology);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the current primitive topology if needed.
|
||||
/// </summary>
|
||||
/// <param name="topology">New primitive topology</param>
|
||||
private void UpdateTopology(PrimitiveTopology topology)
|
||||
{
|
||||
if (_drawState.Topology != topology || !_topologySet)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetPrimitiveTopology(topology);
|
||||
_currentSpecState.SetTopology(topology);
|
||||
_drawState.Topology = topology;
|
||||
_topologySet = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index buffer count.
|
||||
/// This also sets internal state that indicates that the next draw is an indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void SetIndexBufferCount(int argument)
|
||||
{
|
||||
_drawState.DrawIndexed = true;
|
||||
}
|
||||
|
||||
// TODO: Verify if the index type is implied from the method that is called,
|
||||
// or if it uses the state index type on hardware.
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 8-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer8BeginEndInstanceFirst(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 16-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer16BeginEndInstanceFirst(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 32-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer32BeginEndInstanceFirst(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 8-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer8BeginEndInstanceSubsequent(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 16-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer16BeginEndInstanceSubsequent(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 32-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawIndexBuffer32BeginEndInstanceSubsequent(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawIndexBufferBeginEndInstance(engine, argument, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with a low number of index buffer elements,
|
||||
/// while optionally also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||
private void DrawIndexBufferBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||
{
|
||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
|
||||
int firstIndex = argument & 0xffff;
|
||||
int indexCount = (argument >> 16) & 0xfff;
|
||||
|
||||
bool oldDrawIndexed = _drawState.DrawIndexed;
|
||||
|
||||
_drawState.DrawIndexed = true;
|
||||
engine.ForceStateDirty(IndexBufferCountMethodOffset * 4);
|
||||
|
||||
DrawEnd(engine, firstIndex, indexCount, 0, 0);
|
||||
|
||||
_drawState.DrawIndexed = oldDrawIndexed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a non-indexed draw with the specified topology, index and count.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawVertexArrayBeginEndInstanceFirst(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawVertexArrayBeginEndInstance(engine, argument, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a non-indexed draw with the specified topology, index and count,
|
||||
/// while incrementing the current instance.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawVertexArrayBeginEndInstanceSubsequent(ThreedClass engine, int argument)
|
||||
{
|
||||
DrawVertexArrayBeginEndInstance(engine, argument, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with a low number of index buffer elements,
|
||||
/// while optionally also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
/// <param name="instanced">True to increment the current instance value, false otherwise</param>
|
||||
private void DrawVertexArrayBeginEndInstance(ThreedClass engine, int argument, bool instanced)
|
||||
{
|
||||
DrawBegin(instanced, !instanced, (PrimitiveType)((argument >> 28) & 0xf));
|
||||
|
||||
int firstVertex = argument & 0xffff;
|
||||
int vertexCount = (argument >> 16) & 0xfff;
|
||||
|
||||
bool oldDrawIndexed = _drawState.DrawIndexed;
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
engine.ForceStateDirty(VertexBufferFirstMethodOffset * 4);
|
||||
|
||||
DrawEnd(engine, 0, 0, firstVertex, vertexCount);
|
||||
|
||||
_drawState.DrawIndexed = oldDrawIndexed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a texture draw with a source texture and sampler ID, along with source
|
||||
/// and destination coordinates and sizes.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void DrawTexture(ThreedClass engine, int argument)
|
||||
{
|
||||
static float FixedToFloat(int fixedValue)
|
||||
{
|
||||
return fixedValue * (1f / 4096);
|
||||
}
|
||||
|
||||
float dstX0 = FixedToFloat(_state.State.DrawTextureDstX);
|
||||
float dstY0 = FixedToFloat(_state.State.DrawTextureDstY);
|
||||
float dstWidth = FixedToFloat(_state.State.DrawTextureDstWidth);
|
||||
float dstHeight = FixedToFloat(_state.State.DrawTextureDstHeight);
|
||||
|
||||
// TODO: Confirm behaviour on hardware.
|
||||
// When this is active, the origin appears to be on the bottom.
|
||||
if (_state.State.YControl.HasFlag(YControl.NegateY))
|
||||
{
|
||||
dstY0 -= dstHeight;
|
||||
}
|
||||
|
||||
float dstX1 = dstX0 + dstWidth;
|
||||
float dstY1 = dstY0 + dstHeight;
|
||||
|
||||
float srcX0 = FixedToFloat(_state.State.DrawTextureSrcX);
|
||||
float srcY0 = FixedToFloat(_state.State.DrawTextureSrcY);
|
||||
float srcX1 = ((float)_state.State.DrawTextureDuDx / (1UL << 32)) * dstWidth + srcX0;
|
||||
float srcY1 = ((float)_state.State.DrawTextureDvDy / (1UL << 32)) * dstHeight + srcY0;
|
||||
|
||||
engine.UpdateState(ulong.MaxValue & ~(1UL << StateUpdater.ShaderStateIndex));
|
||||
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
int textureId = _state.State.DrawTextureTextureId;
|
||||
int samplerId = _state.State.DrawTextureSamplerId;
|
||||
|
||||
(var texture, var sampler) = _channel.TextureManager.GetGraphicsTextureAndSampler(textureId, samplerId);
|
||||
|
||||
srcX0 *= texture.ScaleFactor;
|
||||
srcY0 *= texture.ScaleFactor;
|
||||
srcX1 *= texture.ScaleFactor;
|
||||
srcY1 *= texture.ScaleFactor;
|
||||
|
||||
float dstScale = _channel.TextureManager.RenderTargetScale;
|
||||
|
||||
dstX0 *= dstScale;
|
||||
dstY0 *= dstScale;
|
||||
dstX1 *= dstScale;
|
||||
dstY1 *= dstScale;
|
||||
|
||||
_context.Renderer.Pipeline.DrawTexture(
|
||||
texture?.HostTexture,
|
||||
sampler?.GetHostSampler(texture),
|
||||
new Extents2DF(srcX0, srcY0, srcX1, srcY1),
|
||||
new Extents2DF(dstX0, dstY0, dstX1, dstY1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed or non-indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="count">Index count for indexed draws, vertex count for non-indexed draws</param>
|
||||
/// <param name="instanceCount">Instance count</param>
|
||||
/// <param name="firstIndex">First index on the index buffer for indexed draws, ignored for non-indexed draws</param>
|
||||
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">First instance</param>
|
||||
/// <param name="indexed">True if the draw is indexed, false otherwise</param>
|
||||
public void Draw(
|
||||
ThreedClass engine,
|
||||
PrimitiveTopology topology,
|
||||
int count,
|
||||
int instanceCount,
|
||||
int firstIndex,
|
||||
int firstVertex,
|
||||
int firstInstance,
|
||||
bool indexed)
|
||||
{
|
||||
UpdateTopology(topology);
|
||||
|
||||
ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable(
|
||||
_context,
|
||||
_channel.MemoryManager,
|
||||
_state.State.RenderEnableAddress,
|
||||
_state.State.RenderEnableCondition);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.False)
|
||||
{
|
||||
_drawState.DrawIndexed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
_drawState.FirstIndex = firstIndex;
|
||||
_drawState.IndexCount = count;
|
||||
_state.State.FirstVertex = (uint)firstVertex;
|
||||
engine.ForceStateDirty(IndexBufferCountMethodOffset * 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
_drawState.DrawFirstVertex = firstVertex;
|
||||
_drawState.DrawVertexCount = count;
|
||||
engine.ForceStateDirty(VertexBufferFirstMethodOffset * 4);
|
||||
}
|
||||
|
||||
_state.State.FirstInstance = (uint)firstInstance;
|
||||
|
||||
_drawState.DrawIndexed = indexed;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(true);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndexed(count, instanceCount, firstIndex, firstVertex, firstInstance);
|
||||
_state.State.FirstVertex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.Draw(count, instanceCount, firstVertex, firstInstance);
|
||||
}
|
||||
|
||||
_state.State.FirstInstance = 0;
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="indirectBufferAddress">Address of the buffer with the draw parameters, such as count, first index, etc</param>
|
||||
/// <param name="parameterBufferAddress">Address of the buffer with the draw count</param>
|
||||
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
|
||||
/// <param name="stride">Distance in bytes between each entry on the data pointed to by <paramref name="indirectBufferAddress"/></param>
|
||||
/// <param name="indexCount">Maximum number of indices that the draw can consume</param>
|
||||
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
|
||||
public void DrawIndirect(
|
||||
ThreedClass engine,
|
||||
PrimitiveTopology topology,
|
||||
ulong indirectBufferAddress,
|
||||
ulong parameterBufferAddress,
|
||||
int maxDrawCount,
|
||||
int stride,
|
||||
int indexCount,
|
||||
IndirectDrawType drawType)
|
||||
{
|
||||
UpdateTopology(topology);
|
||||
|
||||
ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable(
|
||||
_context,
|
||||
_channel.MemoryManager,
|
||||
_state.State.RenderEnableAddress,
|
||||
_state.State.RenderEnableCondition);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.False)
|
||||
{
|
||||
_drawState.DrawIndexed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalMemory memory = _channel.MemoryManager.Physical;
|
||||
|
||||
bool hasCount = (drawType & IndirectDrawType.Count) != 0;
|
||||
bool indexed = (drawType & IndirectDrawType.Indexed) != 0;
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
indexCount = Math.Clamp(indexCount, MinIndirectIndexCount, MaxIndirectIndexCount);
|
||||
_drawState.FirstIndex = 0;
|
||||
_drawState.IndexCount = indexCount;
|
||||
engine.ForceStateDirty(IndexBufferCountMethodOffset * 4);
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = indexed;
|
||||
_drawState.DrawIndirect = true;
|
||||
_currentSpecState.SetHasConstantBufferDrawParameters(true);
|
||||
|
||||
engine.UpdateState();
|
||||
|
||||
if (hasCount)
|
||||
{
|
||||
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferAddress, (ulong)maxDrawCount * (ulong)stride);
|
||||
var parameterBuffer = memory.BufferCache.GetBufferRange(parameterBufferAddress, 4);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndexedIndirectCount(indirectBuffer, parameterBuffer, maxDrawCount, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndirectCount(indirectBuffer, parameterBuffer, maxDrawCount, stride);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var indirectBuffer = memory.BufferCache.GetBufferRange(indirectBufferAddress, (ulong)stride);
|
||||
|
||||
if (indexed)
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndexedIndirect(indirectBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.DrawIndirect(indirectBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
_drawState.DrawIndexed = false;
|
||||
_drawState.DrawIndirect = false;
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform any deferred draws.
|
||||
/// This is used for instanced draws.
|
||||
/// Since each instance is a separate draw, we defer the draw and accumulate the instance count.
|
||||
/// Once we detect the last instanced draw, then we perform the host instanced draw,
|
||||
/// with the accumulated instance count.
|
||||
/// </summary>
|
||||
public void PerformDeferredDraws()
|
||||
{
|
||||
// Perform any pending instanced draw.
|
||||
if (_instancedDrawPending)
|
||||
{
|
||||
_instancedDrawPending = false;
|
||||
|
||||
bool indexedInline = _instancedIndexedInline;
|
||||
|
||||
if (_instancedIndexed || indexedInline)
|
||||
{
|
||||
if (indexedInline)
|
||||
{
|
||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||
|
||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.DrawIndexed(
|
||||
_instancedIndexCount,
|
||||
_instanceIndex + 1,
|
||||
_instancedFirstIndex,
|
||||
_instancedFirstVertex,
|
||||
_instancedFirstInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.Pipeline.Draw(
|
||||
_instancedDrawStateCount,
|
||||
_instanceIndex + 1,
|
||||
_instancedDrawStateFirst,
|
||||
_instancedFirstInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current color and depth-stencil buffers.
|
||||
/// Which buffers should be cleared can also be specified with the argument.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void Clear(ThreedClass engine, int argument)
|
||||
{
|
||||
Clear(engine, argument, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current color and depth-stencil buffers.
|
||||
/// Which buffers should be cleared can also specified with the arguments.
|
||||
/// </summary>
|
||||
/// <param name="engine">3D engine where this method is being called</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
/// <param name="layerCount">For array and 3D textures, indicates how many layers should be cleared</param>
|
||||
public void Clear(ThreedClass engine, int argument, int layerCount)
|
||||
{
|
||||
ConditionalRenderEnabled renderEnable = ConditionalRendering.GetRenderEnable(
|
||||
_context,
|
||||
_channel.MemoryManager,
|
||||
_state.State.RenderEnableAddress,
|
||||
_state.State.RenderEnableCondition);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.False)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool clearDepth = (argument & 1) != 0;
|
||||
bool clearStencil = (argument & 2) != 0;
|
||||
uint componentMask = (uint)((argument >> 2) & 0xf);
|
||||
int index = (argument >> 6) & 0xf;
|
||||
int layer = (argument >> 10) & 0x3ff;
|
||||
|
||||
RenderTargetUpdateFlags updateFlags = RenderTargetUpdateFlags.SingleColor;
|
||||
|
||||
if (layer != 0 || layerCount > 1)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.Layered;
|
||||
}
|
||||
|
||||
if (clearDepth || clearStencil)
|
||||
{
|
||||
updateFlags |= RenderTargetUpdateFlags.UpdateDepthStencil;
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(updateFlags, singleUse: componentMask != 0 ? index : -1);
|
||||
|
||||
// If there is a mismatch on the host clip region and the one explicitly defined by the guest
|
||||
// on the screen scissor state, then we need to force only one texture to be bound to avoid
|
||||
// host clipping.
|
||||
var screenScissorState = _state.State.ScreenScissorState;
|
||||
|
||||
// Must happen after UpdateRenderTargetState to have up-to-date clip region values.
|
||||
bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
|
||||
screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
|
||||
screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
|
||||
|
||||
bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
|
||||
bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
|
||||
bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
|
||||
|
||||
// Scissor and rasterizer discard also affect clears.
|
||||
ulong updateMask = 1UL << StateUpdater.RasterizerStateIndex;
|
||||
|
||||
if (!needsCustomScissor)
|
||||
{
|
||||
updateMask |= 1UL << StateUpdater.ScissorStateIndex;
|
||||
}
|
||||
|
||||
engine.UpdateState(updateMask);
|
||||
|
||||
if (needsCustomScissor)
|
||||
{
|
||||
int scissorX = screenScissorState.X;
|
||||
int scissorY = screenScissorState.Y;
|
||||
int scissorW = screenScissorState.Width;
|
||||
int scissorH = screenScissorState.Height;
|
||||
|
||||
if (clearAffectedByScissor && _state.State.ScissorState[0].Enable)
|
||||
{
|
||||
ref var scissorState = ref _state.State.ScissorState[0];
|
||||
|
||||
scissorX = Math.Max(scissorX, scissorState.X1);
|
||||
scissorY = Math.Max(scissorY, scissorState.Y1);
|
||||
scissorW = Math.Min(scissorW, scissorState.X2 - scissorState.X1);
|
||||
scissorH = Math.Min(scissorH, scissorState.Y2 - scissorState.Y1);
|
||||
}
|
||||
|
||||
float scale = _channel.TextureManager.RenderTargetScale;
|
||||
if (scale != 1f)
|
||||
{
|
||||
scissorX = (int)(scissorX * scale);
|
||||
scissorY = (int)(scissorY * scale);
|
||||
scissorW = (int)MathF.Ceiling(scissorW * scale);
|
||||
scissorH = (int)MathF.Ceiling(scissorH * scale);
|
||||
}
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[]
|
||||
{
|
||||
new Rectangle<int>(scissorX, scissorY, scissorW, scissorH)
|
||||
};
|
||||
|
||||
_context.Renderer.Pipeline.SetScissors(scissors);
|
||||
}
|
||||
|
||||
_channel.TextureManager.UpdateRenderTargets();
|
||||
|
||||
if (componentMask != 0)
|
||||
{
|
||||
var clearColor = _state.State.ClearColors;
|
||||
|
||||
ColorF color = new ColorF(clearColor.Red, clearColor.Green, clearColor.Blue, clearColor.Alpha);
|
||||
|
||||
_context.Renderer.Pipeline.ClearRenderTargetColor(index, layer, layerCount, componentMask, color);
|
||||
}
|
||||
|
||||
if (clearDepth || clearStencil)
|
||||
{
|
||||
float depthValue = _state.State.ClearDepthValue;
|
||||
int stencilValue = (int)_state.State.ClearStencilValue;
|
||||
|
||||
int stencilMask = 0;
|
||||
|
||||
if (clearStencil)
|
||||
{
|
||||
stencilMask = clearAffectedByStencilMask ? _state.State.StencilTestState.FrontMask : 0xff;
|
||||
}
|
||||
|
||||
if (clipMismatch)
|
||||
{
|
||||
_channel.TextureManager.UpdateRenderTargetDepthStencil();
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
|
||||
layer,
|
||||
layerCount,
|
||||
depthValue,
|
||||
clearDepth,
|
||||
stencilValue,
|
||||
stencilMask);
|
||||
}
|
||||
|
||||
if (needsCustomScissor)
|
||||
{
|
||||
engine.UpdateScissorState();
|
||||
}
|
||||
|
||||
engine.UpdateRenderTargetState(RenderTargetUpdateFlags.UpdateAll);
|
||||
|
||||
if (renderEnable == ConditionalRenderEnabled.Host)
|
||||
{
|
||||
_context.Renderer.Pipeline.EndHostConditionalRendering();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs
Normal file
65
src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Draw state.
|
||||
/// </summary>
|
||||
class DrawState
|
||||
{
|
||||
/// <summary>
|
||||
/// First index to be used for the draw on the index buffer.
|
||||
/// </summary>
|
||||
public int FirstIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Number of indices to be used for the draw on the index buffer.
|
||||
/// </summary>
|
||||
public int IndexCount;
|
||||
|
||||
/// <summary>
|
||||
/// First vertex used on non-indexed draws. This value is stored somewhere else on indexed draws.
|
||||
/// </summary>
|
||||
public int DrawFirstVertex;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex count used on non-indexed draws. Indexed draws have a index count instead.
|
||||
/// </summary>
|
||||
public int DrawVertexCount;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the next draw will be a indexed draw.
|
||||
/// </summary>
|
||||
public bool DrawIndexed;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the next draw will be a indirect draw.
|
||||
/// </summary>
|
||||
public bool DrawIndirect;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any of the currently used vertex shaders reads the instance ID.
|
||||
/// </summary>
|
||||
public bool VsUsesInstanceId;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any of the currently used vertex buffers is instanced.
|
||||
/// </summary>
|
||||
public bool IsAnyVbInstanced;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||
/// </summary>
|
||||
public bool HasConstantBufferDrawParameters;
|
||||
|
||||
/// <summary>
|
||||
/// Primitive topology for the next draw.
|
||||
/// </summary>
|
||||
public PrimitiveTopology Topology;
|
||||
|
||||
/// <summary>
|
||||
/// Index buffer data streamer for inline index buffer updates, such as those used in legacy OpenGL.
|
||||
/// </summary>
|
||||
public IbStreamer IbStreamer = new IbStreamer();
|
||||
}
|
||||
}
|
||||
194
src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs
Normal file
194
src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds inline index buffer state.
|
||||
/// The inline index buffer data is sent to the GPU through the command buffer.
|
||||
/// </summary>
|
||||
struct IbStreamer
|
||||
{
|
||||
private const int BufferCapacity = 256; // Must be a power of 2.
|
||||
|
||||
private BufferHandle _inlineIndexBuffer;
|
||||
private int _inlineIndexBufferSize;
|
||||
private int _inlineIndexCount;
|
||||
private uint[] _buffer;
|
||||
private int _bufferOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if any index buffer data has been pushed.
|
||||
/// </summary>
|
||||
public bool HasInlineIndexData => _inlineIndexCount != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Total numbers of indices that have been pushed.
|
||||
/// </summary>
|
||||
public int InlineIndexCount => _inlineIndexCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle for the host buffer currently holding the inline index buffer data.
|
||||
/// </summary>
|
||||
/// <returns>Host buffer handle</returns>
|
||||
public BufferHandle GetInlineIndexBuffer()
|
||||
{
|
||||
return _inlineIndexBuffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements on the current inline index buffer,
|
||||
/// while also reseting it to zero for the next draw.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <returns>Inline index bufffer count</returns>
|
||||
public int GetAndResetInlineIndexCount(IRenderer renderer)
|
||||
{
|
||||
UpdateRemaining(renderer);
|
||||
int temp = _inlineIndexCount;
|
||||
_inlineIndexCount = 0;
|
||||
return temp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes four 8-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU8(IRenderer renderer, int argument)
|
||||
{
|
||||
byte i0 = (byte)argument;
|
||||
byte i1 = (byte)(argument >> 8);
|
||||
byte i2 = (byte)(argument >> 16);
|
||||
byte i3 = (byte)(argument >> 24);
|
||||
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
PushData(renderer, offset + 2, i2);
|
||||
PushData(renderer, offset + 3, i3);
|
||||
|
||||
_inlineIndexCount += 4;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes two 16-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU16(IRenderer renderer, int argument)
|
||||
{
|
||||
ushort i0 = (ushort)argument;
|
||||
ushort i1 = (ushort)(argument >> 16);
|
||||
|
||||
int offset = _inlineIndexCount;
|
||||
|
||||
PushData(renderer, offset, i0);
|
||||
PushData(renderer, offset + 1, i1);
|
||||
|
||||
_inlineIndexCount += 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one 32-bit index buffer element.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void VbElementU32(IRenderer renderer, int argument)
|
||||
{
|
||||
uint i0 = (uint)argument;
|
||||
|
||||
int offset = _inlineIndexCount++;
|
||||
|
||||
PushData(renderer, offset, i0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a 32-bit value to the index buffer.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
|
||||
/// <param name="value">Index value to be written</param>
|
||||
private void PushData(IRenderer renderer, int offset, uint value)
|
||||
{
|
||||
if (_buffer == null)
|
||||
{
|
||||
_buffer = new uint[BufferCapacity];
|
||||
}
|
||||
|
||||
// We upload data in chunks.
|
||||
// If we are at the start of a chunk, then the buffer might be full,
|
||||
// in that case we need to submit any existing data before overwriting the buffer.
|
||||
int subOffset = offset & (BufferCapacity - 1);
|
||||
|
||||
if (subOffset == 0 && offset != 0)
|
||||
{
|
||||
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
|
||||
}
|
||||
|
||||
_buffer[subOffset] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
private void UpdateRemaining(IRenderer renderer)
|
||||
{
|
||||
int offset = _inlineIndexCount;
|
||||
if (offset == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int count = offset & (BufferCapacity - 1);
|
||||
if (count == 0)
|
||||
{
|
||||
count = BufferCapacity;
|
||||
}
|
||||
|
||||
int baseOffset = (offset - count) * sizeof(uint);
|
||||
int length = count * sizeof(uint);
|
||||
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
|
||||
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle of a buffer large enough to hold the data that will be written to <paramref name="offset"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
/// <param name="offset">Offset where the data will be written</param>
|
||||
/// <param name="length">Number of bytes that will be written</param>
|
||||
/// <returns>Buffer handle</returns>
|
||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
|
||||
{
|
||||
// Calculate a reasonable size for the buffer that can fit all the data,
|
||||
// and that also won't require frequent resizes if we need to push more data.
|
||||
int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
|
||||
|
||||
if (_inlineIndexBuffer == BufferHandle.Null)
|
||||
{
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size);
|
||||
_inlineIndexBufferSize = size;
|
||||
}
|
||||
else if (_inlineIndexBufferSize < size)
|
||||
{
|
||||
BufferHandle oldBuffer = _inlineIndexBuffer;
|
||||
int oldSize = _inlineIndexBufferSize;
|
||||
|
||||
_inlineIndexBuffer = renderer.CreateBuffer(size);
|
||||
_inlineIndexBufferSize = size;
|
||||
|
||||
renderer.Pipeline.CopyBuffer(oldBuffer, _inlineIndexBuffer, 0, 0, oldSize);
|
||||
renderer.DeleteBuffer(oldBuffer);
|
||||
}
|
||||
|
||||
return _inlineIndexBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs
Normal file
38
src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Indirect draw type, which can be indexed or non-indexed, with or without a draw count.
|
||||
/// </summary>
|
||||
enum IndirectDrawType
|
||||
{
|
||||
/// <summary>
|
||||
/// Non-indexed draw without draw count.
|
||||
/// </summary>
|
||||
DrawIndirect = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indexed draw without draw count.
|
||||
/// </summary>
|
||||
DrawIndexedIndirect = Indexed,
|
||||
|
||||
/// <summary>
|
||||
/// Non-indexed draw with draw count.
|
||||
/// </summary>
|
||||
DrawIndirectCount = Count,
|
||||
|
||||
/// <summary>
|
||||
/// Indexed draw with draw count.
|
||||
/// </summary>
|
||||
DrawIndexedIndirectCount = Indexed | Count,
|
||||
|
||||
/// <summary>
|
||||
/// Indexed flag.
|
||||
/// </summary>
|
||||
Indexed = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Draw count flag.
|
||||
/// </summary>
|
||||
Count = 1 << 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags indicating how the render targets should be updated.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
enum RenderTargetUpdateFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Get render target index from the control register.
|
||||
/// </summary>
|
||||
UseControl = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that all render targets are 2D array textures.
|
||||
/// </summary>
|
||||
Layered = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that only a single color target will be used.
|
||||
/// </summary>
|
||||
SingleColor = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the depth-stencil target will be used.
|
||||
/// </summary>
|
||||
UpdateDepthStencil = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// Default update flags for draw.
|
||||
/// </summary>
|
||||
UpdateAll = UseControl | UpdateDepthStencil
|
||||
}
|
||||
}
|
||||
190
src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs
Normal file
190
src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Semaphore updater.
|
||||
/// </summary>
|
||||
class SemaphoreUpdater
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU semaphore operation.
|
||||
/// </summary>
|
||||
private enum SemaphoreOperation
|
||||
{
|
||||
Release = 0,
|
||||
Acquire = 1,
|
||||
Counter = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counter type for GPU counter reset.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counter type for GPU counter reporting.
|
||||
/// </summary>
|
||||
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<ThreedClassState> _state;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the semaphore updater.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
/// <param name="state">Channel state</param>
|
||||
public SemaphoreUpdater(GpuContext context, GpuChannel channel, DeviceStateWithShadow<ThreedClassState> state)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_state = state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value of an internal GPU counter back to zero.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a GPU counter to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes (or Releases) a GPU semaphore value to guest memory.
|
||||
/// </summary>
|
||||
private void ReleaseSemaphore()
|
||||
{
|
||||
_channel.MemoryManager.Write(_state.State.SemaphoreAddress.Pack(), _state.State.SemaphorePayload);
|
||||
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Packed GPU counter data (including GPU timestamp) in memory.
|
||||
/// </summary>
|
||||
private struct CounterData
|
||||
{
|
||||
public ulong Counter;
|
||||
public ulong Timestamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a GPU counter to guest memory.
|
||||
/// This also writes the current timestamp value.
|
||||
/// </summary>
|
||||
/// <param name="type">Counter to be written to memory</param>
|
||||
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 CounterData
|
||||
{
|
||||
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:
|
||||
counter = _context.Renderer.ReportCounter(CounterType.SamplesPassed, resultHandler, false);
|
||||
break;
|
||||
case ReportCounterType.PrimitivesGenerated:
|
||||
counter = _context.Renderer.ReportCounter(CounterType.PrimitivesGenerated, resultHandler, false);
|
||||
break;
|
||||
case ReportCounterType.TransformFeedbackPrimitivesWritten:
|
||||
counter = _context.Renderer.ReportCounter(CounterType.TransformFeedbackPrimitivesWritten, resultHandler, false);
|
||||
break;
|
||||
}
|
||||
|
||||
_channel.MemoryManager.CounterCache.AddOrUpdate(gpuVa, counter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a "current" specialiation state, and provides a flag to check if it has changed meaningfully.
|
||||
/// </summary>
|
||||
internal class SpecializationStateUpdater
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private GpuChannelGraphicsState _graphics;
|
||||
private GpuChannelPoolState _pool;
|
||||
|
||||
private bool _usesDrawParameters;
|
||||
private bool _usesTopology;
|
||||
|
||||
private bool _changed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the specialization state updater class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
public SpecializationStateUpdater(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the specialization state has changed.
|
||||
/// </summary>
|
||||
private void Signal()
|
||||
{
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the specialization state has changed since the last check.
|
||||
/// </summary>
|
||||
/// <returns>True if it has changed, false otherwise</returns>
|
||||
public bool HasChanged()
|
||||
{
|
||||
if (_changed)
|
||||
{
|
||||
_changed = false;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active shader, clearing the dirty state and recording if certain specializations are noteworthy.
|
||||
/// </summary>
|
||||
/// <param name="gs">The active shader</param>
|
||||
public void SetShader(CachedShaderProgram gs)
|
||||
{
|
||||
_usesDrawParameters = gs.Shaders[1]?.Info.UsesDrawParameters ?? false;
|
||||
_usesTopology = gs.SpecializationState.IsPrimitiveTopologyQueried();
|
||||
|
||||
_changed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current graphics state.
|
||||
/// </summary>
|
||||
/// <returns>GPU graphics state</returns>
|
||||
public ref GpuChannelGraphicsState GetGraphicsState()
|
||||
{
|
||||
return ref _graphics;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current pool state.
|
||||
/// </summary>
|
||||
/// <returns>GPU pool state</returns>
|
||||
public ref GpuChannelPoolState GetPoolState()
|
||||
{
|
||||
return ref _pool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Early Z force enable.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetEarlyZForce(bool value)
|
||||
{
|
||||
_graphics.EarlyZForce = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primitive topology of current draw.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetTopology(PrimitiveTopology value)
|
||||
{
|
||||
if (value != _graphics.Topology)
|
||||
{
|
||||
_graphics.Topology = value;
|
||||
|
||||
if (_usesTopology)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tessellation mode.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetTessellationMode(TessMode value)
|
||||
{
|
||||
if (value.Packed != _graphics.TessellationMode.Packed)
|
||||
{
|
||||
_graphics.TessellationMode = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates alpha-to-coverage state, and sets it as changed.
|
||||
/// </summary>
|
||||
/// <param name="enable">Whether alpha-to-coverage is enabled</param>
|
||||
/// <param name="ditherEnable">Whether alpha-to-coverage dithering is enabled</param>
|
||||
public void SetAlphaToCoverageEnable(bool enable, bool ditherEnable)
|
||||
{
|
||||
_graphics.AlphaToCoverageEnable = enable;
|
||||
_graphics.AlphaToCoverageDitherEnable = ditherEnable;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the viewport transform is disabled.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetViewportTransformDisable(bool value)
|
||||
{
|
||||
if (value != _graphics.ViewportTransformDisable)
|
||||
{
|
||||
_graphics.ViewportTransformDisable = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth mode zero to one or minus one to one.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetDepthMode(bool value)
|
||||
{
|
||||
if (value != _graphics.DepthMode)
|
||||
{
|
||||
_graphics.DepthMode = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the point size is set on the shader or is fixed.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetProgramPointSizeEnable(bool value)
|
||||
{
|
||||
if (value != _graphics.ProgramPointSizeEnable)
|
||||
{
|
||||
_graphics.ProgramPointSizeEnable = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Point size used if <see cref="SetProgramPointSizeEnable" /> is provided false.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetPointSize(float value)
|
||||
{
|
||||
if (value != _graphics.PointSize)
|
||||
{
|
||||
_graphics.PointSize = value;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates alpha test specialization state, and sets it as changed.
|
||||
/// </summary>
|
||||
/// <param name="enable">Whether alpha test is enabled</param>
|
||||
/// <param name="reference">The value to compare with the fragment output alpha</param>
|
||||
/// <param name="op">The comparison that decides if the fragment should be discarded</param>
|
||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||
{
|
||||
_graphics.AlphaTestEnable = enable;
|
||||
_graphics.AlphaTestReference = reference;
|
||||
_graphics.AlphaTestCompare = op;
|
||||
|
||||
Signal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the type of the vertex attributes consumed by the shader.
|
||||
/// </summary>
|
||||
/// <param name="state">The new state</param>
|
||||
public void SetAttributeTypes(ref Array32<VertexAttribState> state)
|
||||
{
|
||||
bool changed = false;
|
||||
ref Array32<AttributeType> attributeTypes = ref _graphics.AttributeTypes;
|
||||
|
||||
for (int location = 0; location < state.Length; location++)
|
||||
{
|
||||
VertexAttribType type = state[location].UnpackType();
|
||||
|
||||
AttributeType value = type switch
|
||||
{
|
||||
VertexAttribType.Sint => AttributeType.Sint,
|
||||
VertexAttribType.Uint => AttributeType.Uint,
|
||||
_ => AttributeType.Float
|
||||
};
|
||||
|
||||
if (attributeTypes[location] != value)
|
||||
{
|
||||
attributeTypes[location] = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the type of the outputs produced by the fragment shader based on the current render target state.
|
||||
/// </summary>
|
||||
/// <param name="rtControl">The render target control register</param>
|
||||
/// <param name="state">The color attachment state</param>
|
||||
public void SetFragmentOutputTypes(RtControl rtControl, ref Array8<RtColorState> state)
|
||||
{
|
||||
bool changed = false;
|
||||
int count = rtControl.UnpackCount();
|
||||
|
||||
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
||||
{
|
||||
int rtIndex = rtControl.UnpackPermutationIndex(index);
|
||||
|
||||
var colorState = state[rtIndex];
|
||||
|
||||
if (index < count && StateUpdater.IsRtEnabled(colorState))
|
||||
{
|
||||
Format format = colorState.Format.Convert().Format;
|
||||
|
||||
AttributeType type = format.IsInteger() ? (format.IsSint() ? AttributeType.Sint : AttributeType.Uint) : AttributeType.Float;
|
||||
|
||||
if (type != _graphics.FragmentOutputTypes[index])
|
||||
{
|
||||
_graphics.FragmentOutputTypes[index] = type;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed && _context.Capabilities.NeedsFragmentOutputSpecialization)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the draw is writing the base vertex, base instance and draw index to Constant Buffer 0.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
public void SetHasConstantBufferDrawParameters(bool value)
|
||||
{
|
||||
if (value != _graphics.HasConstantBufferDrawParameters)
|
||||
{
|
||||
_graphics.HasConstantBufferDrawParameters = value;
|
||||
|
||||
if (_usesDrawParameters)
|
||||
{
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that any storage buffer use is unaligned.
|
||||
/// </summary>
|
||||
/// <param name="value">The new value</param>
|
||||
/// <returns>True if the unaligned state changed, false otherwise</returns>
|
||||
public bool SetHasUnalignedStorageBuffer(bool value)
|
||||
{
|
||||
if (value != _graphics.HasUnalignedStorageBuffer)
|
||||
{
|
||||
_graphics.HasUnalignedStorageBuffer = value;
|
||||
|
||||
Signal();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the GPU pool state.
|
||||
/// </summary>
|
||||
/// <param name="state">The new state</param>
|
||||
public void SetPoolState(GpuChannelPoolState state)
|
||||
{
|
||||
if (!state.Equals(_pool))
|
||||
{
|
||||
_pool = state;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the dual-source blend enabled state.
|
||||
/// </summary>
|
||||
/// <param name="enabled">True if blending is enabled and using dual-source blend</param>
|
||||
public void SetDualSourceBlendEnabled(bool enabled)
|
||||
{
|
||||
if (enabled != _graphics.DualSourceBlendEnable)
|
||||
{
|
||||
_graphics.DualSourceBlendEnable = enabled;
|
||||
|
||||
Signal();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
Normal file
177
src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// State update callback entry, with the callback function and associated field names.
|
||||
/// </summary>
|
||||
readonly struct StateUpdateCallbackEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Callback function, to be called if the register was written as the state needs to be updated.
|
||||
/// </summary>
|
||||
public Action Callback { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the state fields (registers) associated with the callback function.
|
||||
/// </summary>
|
||||
public string[] FieldNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new state update callback entry.
|
||||
/// </summary>
|
||||
/// <param name="callback">Callback function, to be called if the register was written as the state needs to be updated</param>
|
||||
/// <param name="fieldNames">Name of the state fields (registers) associated with the callback function</param>
|
||||
public StateUpdateCallbackEntry(Action callback, params string[] fieldNames)
|
||||
{
|
||||
Callback = callback;
|
||||
FieldNames = fieldNames;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GPU state update tracker.
|
||||
/// </summary>
|
||||
/// <typeparam name="TState">State type</typeparam>
|
||||
class StateUpdateTracker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState>
|
||||
{
|
||||
private const int BlockSize = 0xe00;
|
||||
private const int RegisterSize = sizeof(uint);
|
||||
|
||||
private readonly byte[] _registerToGroupMapping;
|
||||
private readonly Action[] _callbacks;
|
||||
private ulong _dirtyMask;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the state update tracker.
|
||||
/// </summary>
|
||||
/// <param name="entries">Update tracker callback entries</param>
|
||||
public StateUpdateTracker(StateUpdateCallbackEntry[] entries)
|
||||
{
|
||||
_registerToGroupMapping = new byte[BlockSize];
|
||||
_callbacks = new Action[entries.Length];
|
||||
|
||||
var fieldToDelegate = new Dictionary<string, int>();
|
||||
|
||||
for (int entryIndex = 0; entryIndex < entries.Length; entryIndex++)
|
||||
{
|
||||
var entry = entries[entryIndex];
|
||||
|
||||
foreach (var fieldName in entry.FieldNames)
|
||||
{
|
||||
fieldToDelegate.Add(fieldName, entryIndex);
|
||||
}
|
||||
|
||||
_callbacks[entryIndex] = entry.Callback;
|
||||
}
|
||||
|
||||
var fields = typeof(TState).GetFields();
|
||||
int offset = 0;
|
||||
|
||||
for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++)
|
||||
{
|
||||
var field = fields[fieldIndex];
|
||||
|
||||
int sizeOfField = SizeCalculator.SizeOf(field.FieldType);
|
||||
|
||||
if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex))
|
||||
{
|
||||
for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4)
|
||||
{
|
||||
_registerToGroupMapping[(offset + i) / RegisterSize] = (byte)(entryIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
offset += sizeOfField;
|
||||
}
|
||||
|
||||
Debug.Assert(offset == Unsafe.SizeOf<TState>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a register as modified.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register offset in bytes</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetDirty(int offset)
|
||||
{
|
||||
uint index = (uint)offset / RegisterSize;
|
||||
|
||||
if (index < BlockSize)
|
||||
{
|
||||
int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (IntPtr)index);
|
||||
if (groupIndex != 0)
|
||||
{
|
||||
groupIndex--;
|
||||
_dirtyMask |= 1UL << groupIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces a register group as dirty, by index.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group to be dirtied</param>
|
||||
public void ForceDirty(int groupIndex)
|
||||
{
|
||||
if ((uint)groupIndex >= _callbacks.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(groupIndex));
|
||||
}
|
||||
|
||||
_dirtyMask |= 1UL << groupIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces all register groups as dirty, triggering a full update on the next call to <see cref="Update"/>.
|
||||
/// </summary>
|
||||
public void SetAllDirty()
|
||||
{
|
||||
Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8);
|
||||
_dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given register group is dirty without clearing it.
|
||||
/// </summary>
|
||||
/// <param name="groupIndex">Index of the group to check</param>
|
||||
/// <returns>True if dirty, false otherwise</returns>
|
||||
public bool IsDirty(int groupIndex)
|
||||
{
|
||||
return (_dirtyMask & (1UL << groupIndex)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
|
||||
/// </summary>
|
||||
/// <param name="checkMask">Mask, where each bit set corresponds to a group index that should be checked</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(ulong checkMask)
|
||||
{
|
||||
ulong mask = _dirtyMask & checkMask;
|
||||
if (mask == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
int groupIndex = BitOperations.TrailingZeroCount(mask);
|
||||
|
||||
_callbacks[groupIndex]();
|
||||
|
||||
mask &= ~(1UL << groupIndex);
|
||||
}
|
||||
while (mask != 0);
|
||||
|
||||
_dirtyMask &= ~checkMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
1448
src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
Normal file
1448
src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
Normal file
File diff suppressed because it is too large
Load Diff
620
src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
Normal file
620
src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
Normal file
@@ -0,0 +1,620 @@
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Threed.Blender;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a 3D engine class.
|
||||
/// </summary>
|
||||
class ThreedClass : IDeviceState
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GPFifoClass _fifoClass;
|
||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||
|
||||
private readonly InlineToMemoryClass _i2mClass;
|
||||
private readonly AdvancedBlendManager _blendManager;
|
||||
private readonly DrawManager _drawManager;
|
||||
private readonly SemaphoreUpdater _semaphoreUpdater;
|
||||
private readonly ConstantBufferUpdater _cbUpdater;
|
||||
private readonly StateUpdater _stateUpdater;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the 3D engine class.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context</param>
|
||||
/// <param name="channel">GPU channel</param>
|
||||
public ThreedClass(GpuContext context, GpuChannel channel, GPFifoClass fifoClass)
|
||||
{
|
||||
_context = context;
|
||||
_fifoClass = fifoClass;
|
||||
_state = new DeviceStateWithShadow<ThreedClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(ThreedClassState.LaunchDma), new RwCallback(LaunchDma, null) },
|
||||
{ nameof(ThreedClassState.LoadInlineData), new RwCallback(LoadInlineData, null) },
|
||||
{ nameof(ThreedClassState.SyncpointAction), new RwCallback(IncrementSyncpoint, null) },
|
||||
{ nameof(ThreedClassState.InvalidateSamplerCacheNoWfi), new RwCallback(InvalidateSamplerCacheNoWfi, null) },
|
||||
{ nameof(ThreedClassState.InvalidateTextureHeaderCacheNoWfi), new RwCallback(InvalidateTextureHeaderCacheNoWfi, null) },
|
||||
{ nameof(ThreedClassState.TextureBarrier), new RwCallback(TextureBarrier, null) },
|
||||
{ nameof(ThreedClassState.LoadBlendUcodeStart), new RwCallback(LoadBlendUcodeStart, null) },
|
||||
{ nameof(ThreedClassState.LoadBlendUcodeInstruction), new RwCallback(LoadBlendUcodeInstruction, null) },
|
||||
{ nameof(ThreedClassState.TextureBarrierTiled), new RwCallback(TextureBarrierTiled, null) },
|
||||
{ nameof(ThreedClassState.DrawTextureSrcY), new RwCallback(DrawTexture, null) },
|
||||
{ nameof(ThreedClassState.DrawVertexArrayBeginEndInstanceFirst), new RwCallback(DrawVertexArrayBeginEndInstanceFirst, null) },
|
||||
{ nameof(ThreedClassState.DrawVertexArrayBeginEndInstanceSubsequent), new RwCallback(DrawVertexArrayBeginEndInstanceSubsequent, null) },
|
||||
{ nameof(ThreedClassState.VbElementU8), new RwCallback(VbElementU8, null) },
|
||||
{ nameof(ThreedClassState.VbElementU16), new RwCallback(VbElementU16, null) },
|
||||
{ nameof(ThreedClassState.VbElementU32), new RwCallback(VbElementU32, null) },
|
||||
{ nameof(ThreedClassState.ResetCounter), new RwCallback(ResetCounter, null) },
|
||||
{ nameof(ThreedClassState.RenderEnableCondition), new RwCallback(null, Zero) },
|
||||
{ nameof(ThreedClassState.DrawEnd), new RwCallback(DrawEnd, null) },
|
||||
{ nameof(ThreedClassState.DrawBegin), new RwCallback(DrawBegin, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer32BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer32BeginEndInstanceFirst, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer16BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer16BeginEndInstanceFirst, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer8BeginEndInstanceFirst), new RwCallback(DrawIndexBuffer8BeginEndInstanceFirst, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer32BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer32BeginEndInstanceSubsequent, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer16BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer16BeginEndInstanceSubsequent, null) },
|
||||
{ nameof(ThreedClassState.DrawIndexBuffer8BeginEndInstanceSubsequent), new RwCallback(DrawIndexBuffer8BeginEndInstanceSubsequent, null) },
|
||||
{ nameof(ThreedClassState.IndexBufferCount), new RwCallback(SetIndexBufferCount, null) },
|
||||
{ nameof(ThreedClassState.Clear), new RwCallback(Clear, null) },
|
||||
{ nameof(ThreedClassState.SemaphoreControl), new RwCallback(Report, null) },
|
||||
{ nameof(ThreedClassState.SetFalcon04), new RwCallback(SetFalcon04, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferUpdateData), new RwCallback(ConstantBufferUpdate, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferBindVertex), new RwCallback(ConstantBufferBindVertex, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferBindTessControl), new RwCallback(ConstantBufferBindTessControl, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferBindTessEvaluation), new RwCallback(ConstantBufferBindTessEvaluation, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferBindGeometry), new RwCallback(ConstantBufferBindGeometry, null) },
|
||||
{ nameof(ThreedClassState.UniformBufferBindFragment), new RwCallback(ConstantBufferBindFragment, null) }
|
||||
});
|
||||
|
||||
_i2mClass = new InlineToMemoryClass(context, channel, initializeState: false);
|
||||
|
||||
var spec = new SpecializationStateUpdater(context);
|
||||
var drawState = new DrawState();
|
||||
|
||||
_drawManager = new DrawManager(context, channel, _state, drawState, spec);
|
||||
_blendManager = new AdvancedBlendManager(_state);
|
||||
_semaphoreUpdater = new SemaphoreUpdater(context, channel, _state);
|
||||
_cbUpdater = new ConstantBufferUpdater(channel, _state);
|
||||
_stateUpdater = new StateUpdater(context, channel, _state, drawState, _blendManager, spec);
|
||||
|
||||
// This defaults to "always", even without any register write.
|
||||
// Reads just return 0, regardless of what was set there.
|
||||
_state.State.RenderEnableCondition = Condition.Always;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write(int offset, int data)
|
||||
{
|
||||
_state.WriteWithRedundancyCheck(offset, data, out bool valueChanged);
|
||||
|
||||
if (valueChanged)
|
||||
{
|
||||
_stateUpdater.SetDirty(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the shadow ram control value of all sub-channels.
|
||||
/// </summary>
|
||||
/// <param name="control">New shadow ram control value</param>
|
||||
public void SetShadowRamControl(int control)
|
||||
{
|
||||
_state.State.SetMmeShadowRamControl = (uint)control;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates current host state for all registers modified since the last call to this method.
|
||||
/// </summary>
|
||||
public void UpdateState()
|
||||
{
|
||||
_fifoClass.CreatePendingSyncs();
|
||||
_cbUpdater.FlushUboDirty();
|
||||
_stateUpdater.Update();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates current host state for all registers modified since the last call to this method.
|
||||
/// </summary>
|
||||
/// <param name="mask">Mask where each bit set indicates that the respective state group index should be checked</param>
|
||||
public void UpdateState(ulong mask)
|
||||
{
|
||||
_stateUpdater.Update(mask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
||||
/// </summary>
|
||||
/// <param name="updateFlags">Flags indicating which render targets should be updated and how</param>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetState(RenderTargetUpdateFlags updateFlags, int singleUse = -1)
|
||||
{
|
||||
_stateUpdater.UpdateRenderTargetState(updateFlags, singleUse);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates scissor based on current render target state.
|
||||
/// </summary>
|
||||
public void UpdateScissorState()
|
||||
{
|
||||
_stateUpdater.UpdateScissorState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the entire state as dirty, forcing a full host state update before the next draw.
|
||||
/// </summary>
|
||||
public void ForceStateDirty()
|
||||
{
|
||||
_drawManager.ForceStateDirty();
|
||||
_stateUpdater.SetAllDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the specified register offset as dirty, forcing the associated state to update on the next draw.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register offset</param>
|
||||
public void ForceStateDirty(int offset)
|
||||
{
|
||||
_stateUpdater.SetDirty(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the shaders to be rebound on the next draw.
|
||||
/// </summary>
|
||||
public void ForceShaderUpdate()
|
||||
{
|
||||
_stateUpdater.ForceShaderUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create any syncs from WaitForIdle command that are currently pending.
|
||||
/// </summary>
|
||||
public void CreatePendingSyncs()
|
||||
{
|
||||
_fifoClass.CreatePendingSyncs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes any queued UBO updates.
|
||||
/// </summary>
|
||||
public void FlushUboDirty()
|
||||
{
|
||||
_cbUpdater.FlushUboDirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform any deferred draws.
|
||||
/// </summary>
|
||||
public void PerformDeferredDraws()
|
||||
{
|
||||
_drawManager.PerformDeferredDraws();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the currently bound constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to be written to the buffer</param>
|
||||
public void ConstantBufferUpdate(ReadOnlySpan<int> data)
|
||||
{
|
||||
_cbUpdater.Update(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Launches the Inline-to-Memory DMA copy operation.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LaunchDma(int argument)
|
||||
{
|
||||
_i2mClass.LaunchDma(ref Unsafe.As<ThreedClassState, InlineToMemoryClassState>(ref _state.State), argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a block of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="data">Data to push</param>
|
||||
public void LoadInlineData(ReadOnlySpan<int> data)
|
||||
{
|
||||
_i2mClass.LoadInlineData(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a word of data to the Inline-to-Memory engine.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadInlineData(int argument)
|
||||
{
|
||||
_i2mClass.LoadInlineData(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an incrementation on a syncpoint.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
public void IncrementSyncpoint(int argument)
|
||||
{
|
||||
uint syncpointId = (uint)argument & 0xFFFF;
|
||||
|
||||
_context.AdvanceSequence();
|
||||
_context.CreateHostSyncIfNeeded(true, true);
|
||||
_context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result.
|
||||
_context.Synchronization.IncrementSyncpoint(syncpointId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cache with the sampler descriptors from the sampler pool.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument (unused)</param>
|
||||
private void InvalidateSamplerCacheNoWfi(int argument)
|
||||
{
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the cache with the texture descriptors from the texture pool.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument (unused)</param>
|
||||
private void InvalidateTextureHeaderCacheNoWfi(int argument)
|
||||
{
|
||||
_context.AdvanceSequence();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a texture barrier.
|
||||
/// This waits until previous texture writes from the GPU to finish, before
|
||||
/// performing new operations with said textures.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument (unused)</param>
|
||||
private void TextureBarrier(int argument)
|
||||
{
|
||||
_context.Renderer.Pipeline.TextureBarrier();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the start offset of the blend microcode in memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadBlendUcodeStart(int argument)
|
||||
{
|
||||
_blendManager.LoadBlendUcodeStart(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one word of blend microcode.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void LoadBlendUcodeInstruction(int argument)
|
||||
{
|
||||
_blendManager.LoadBlendUcodeInstruction(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a texture barrier.
|
||||
/// This waits until previous texture writes from the GPU to finish, before
|
||||
/// performing new operations with said textures.
|
||||
/// This performs a per-tile wait, it is only valid if both the previous write
|
||||
/// and current access has the same access patterns.
|
||||
/// This may be faster than the regular barrier on tile-based rasterizers.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument (unused)</param>
|
||||
private void TextureBarrierTiled(int argument)
|
||||
{
|
||||
_context.Renderer.Pipeline.TextureBarrierTiled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a texture, without needing to specify shader programs.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawTexture(int argument)
|
||||
{
|
||||
_drawManager.DrawTexture(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a non-indexed draw with the specified topology, index and count.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawVertexArrayBeginEndInstanceFirst(int argument)
|
||||
{
|
||||
_drawManager.DrawVertexArrayBeginEndInstanceFirst(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a non-indexed draw with the specified topology, index and count,
|
||||
/// while incrementing the current instance.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawVertexArrayBeginEndInstanceSubsequent(int argument)
|
||||
{
|
||||
_drawManager.DrawVertexArrayBeginEndInstanceSubsequent(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes four 8-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void VbElementU8(int argument)
|
||||
{
|
||||
_drawManager.VbElementU8(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes two 16-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void VbElementU16(int argument)
|
||||
{
|
||||
_drawManager.VbElementU16(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes one 32-bit index buffer element.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void VbElementU32(int argument)
|
||||
{
|
||||
_drawManager.VbElementU32(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the value of an internal GPU counter back to zero.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ResetCounter(int argument)
|
||||
{
|
||||
_semaphoreUpdater.ResetCounter(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes the draw call.
|
||||
/// This draws geometry on the bound buffers based on the current GPU state.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawEnd(int argument)
|
||||
{
|
||||
_drawManager.DrawEnd(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts draw.
|
||||
/// This sets primitive type and instanced draw parameters.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawBegin(int argument)
|
||||
{
|
||||
_drawManager.DrawBegin(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the index buffer count.
|
||||
/// This also sets internal state that indicates that the next draw is an indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void SetIndexBufferCount(int argument)
|
||||
{
|
||||
_drawManager.SetIndexBufferCount(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 8-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer8BeginEndInstanceFirst(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer8BeginEndInstanceFirst(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 16-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer16BeginEndInstanceFirst(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer16BeginEndInstanceFirst(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 32-bit index buffer elements.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer32BeginEndInstanceFirst(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer32BeginEndInstanceFirst(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 8-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer8BeginEndInstanceSubsequent(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer8BeginEndInstanceSubsequent(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 16-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer16BeginEndInstanceSubsequent(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer16BeginEndInstanceSubsequent(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed draw with 32-bit index buffer elements,
|
||||
/// while also pre-incrementing the current instance value.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void DrawIndexBuffer32BeginEndInstanceSubsequent(int argument)
|
||||
{
|
||||
_drawManager.DrawIndexBuffer32BeginEndInstanceSubsequent(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current color and depth-stencil buffers.
|
||||
/// Which buffers should be cleared is also specified on the argument.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void Clear(int argument)
|
||||
{
|
||||
_drawManager.Clear(this, argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a GPU counter to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void Report(int argument)
|
||||
{
|
||||
_semaphoreUpdater.Report(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs high-level emulation of Falcon microcode function number "4".
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void SetFalcon04(int argument)
|
||||
{
|
||||
_state.State.SetMmeShadowScratch[0] = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the uniform buffer data with inline data.
|
||||
/// </summary>
|
||||
/// <param name="argument">New uniform buffer data word</param>
|
||||
private void ConstantBufferUpdate(int argument)
|
||||
{
|
||||
_cbUpdater.Update(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the vertex shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ConstantBufferBindVertex(int argument)
|
||||
{
|
||||
_cbUpdater.BindVertex(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the tessellation control shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ConstantBufferBindTessControl(int argument)
|
||||
{
|
||||
_cbUpdater.BindTessControl(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the tessellation evaluation shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ConstantBufferBindTessEvaluation(int argument)
|
||||
{
|
||||
_cbUpdater.BindTessEvaluation(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the geometry shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ConstantBufferBindGeometry(int argument)
|
||||
{
|
||||
_cbUpdater.BindGeometry(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a uniform buffer for the fragment shader stage.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void ConstantBufferBindFragment(int argument)
|
||||
{
|
||||
_cbUpdater.BindFragment(argument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic register read function that just returns 0.
|
||||
/// </summary>
|
||||
/// <returns>Zero</returns>
|
||||
private static int Zero()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indexed or non-indexed draw.
|
||||
/// </summary>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="count">Index count for indexed draws, vertex count for non-indexed draws</param>
|
||||
/// <param name="instanceCount">Instance count</param>
|
||||
/// <param name="firstIndex">First index on the index buffer for indexed draws, ignored for non-indexed draws</param>
|
||||
/// <param name="firstVertex">First vertex on the vertex buffer</param>
|
||||
/// <param name="firstInstance">First instance</param>
|
||||
/// <param name="indexed">True if the draw is indexed, false otherwise</param>
|
||||
public void Draw(
|
||||
PrimitiveTopology topology,
|
||||
int count,
|
||||
int instanceCount,
|
||||
int firstIndex,
|
||||
int firstVertex,
|
||||
int firstInstance,
|
||||
bool indexed)
|
||||
{
|
||||
_drawManager.Draw(this, topology, count, instanceCount, firstIndex, firstVertex, firstInstance, indexed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a indirect draw, with parameters from a GPU buffer.
|
||||
/// </summary>
|
||||
/// <param name="topology">Primitive topology</param>
|
||||
/// <param name="indirectBufferAddress">Address of the buffer with the draw parameters, such as count, first index, etc</param>
|
||||
/// <param name="parameterBufferAddress">Address of the buffer with the draw count</param>
|
||||
/// <param name="maxDrawCount">Maximum number of draws that can be made</param>
|
||||
/// <param name="stride">Distance in bytes between each entry on the data pointed to by <paramref name="indirectBufferAddress"/></param>
|
||||
/// <param name="indexCount">Maximum number of indices that the draw can consume</param>
|
||||
/// <param name="drawType">Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count</param>
|
||||
public void DrawIndirect(
|
||||
PrimitiveTopology topology,
|
||||
ulong indirectBufferAddress,
|
||||
ulong parameterBufferAddress,
|
||||
int maxDrawCount,
|
||||
int stride,
|
||||
int indexCount,
|
||||
IndirectDrawType drawType)
|
||||
{
|
||||
_drawManager.DrawIndirect(this, topology, indirectBufferAddress, parameterBufferAddress, maxDrawCount, stride, indexCount, drawType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the current color and depth-stencil buffers.
|
||||
/// Which buffers should be cleared can also specified with the arguments.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
/// <param name="layerCount">For array and 3D textures, indicates how many layers should be cleared</param>
|
||||
public void Clear(int argument, int layerCount)
|
||||
{
|
||||
_drawManager.Clear(this, argument, layerCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
1048
src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
Normal file
1048
src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
Normal file
File diff suppressed because it is too large
Load Diff
379
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs
Normal file
379
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.Device;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a 2D engine class.
|
||||
/// </summary>
|
||||
class TwodClass : IDeviceState
|
||||
{
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly DeviceState<TwodClassState> _state;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the 2D engine class.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel that will make use of the engine</param>
|
||||
public TwodClass(GpuChannel channel)
|
||||
{
|
||||
_channel = channel;
|
||||
_state = new DeviceState<TwodClassState>(new Dictionary<string, RwCallback>
|
||||
{
|
||||
{ nameof(TwodClassState.PixelsFromMemorySrcY0Int), new RwCallback(PixelsFromMemorySrcY0Int, null) }
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads data from the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <returns>Data at the specified offset</returns>
|
||||
public int Read(int offset) => _state.Read(offset);
|
||||
|
||||
/// <summary>
|
||||
/// Writes data to the class registers.
|
||||
/// </summary>
|
||||
/// <param name="offset">Register byte offset</param>
|
||||
/// <param name="data">Data to be written</param>
|
||||
public void Write(int offset, int data) => _state.Write(offset, data);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if data is compatible between the source and destination texture.
|
||||
/// The two textures must have the same size, layout, and bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Info for the first texture</param>
|
||||
/// <param name="rhs">Info for the second texture</param>
|
||||
/// <param name="lhsFormat">Format of the first texture</param>
|
||||
/// <param name="rhsFormat">Format of the second texture</param>
|
||||
/// <returns>True if the data is compatible, false otherwise</returns>
|
||||
private bool IsDataCompatible(TwodTexture lhs, TwodTexture rhs, FormatInfo lhsFormat, FormatInfo rhsFormat)
|
||||
{
|
||||
if (lhsFormat.BytesPerPixel != rhsFormat.BytesPerPixel ||
|
||||
lhs.Height != rhs.Height ||
|
||||
lhs.Depth != rhs.Depth ||
|
||||
lhs.LinearLayout != rhs.LinearLayout ||
|
||||
lhs.MemoryLayout.Packed != rhs.MemoryLayout.Packed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lhs.LinearLayout)
|
||||
{
|
||||
return lhs.Stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs.Width == rhs.Width;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the given region covers the full texture, also considering width alignment.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to check</param>
|
||||
/// <param name="formatInfo"></param>
|
||||
/// <param name="x1">Region start x</param>
|
||||
/// <param name="y1">Region start y</param>
|
||||
/// <param name="x2">Region end x</param>
|
||||
/// <param name="y2">Region end y</param>
|
||||
/// <returns>True if the region covers the full texture, false otherwise</returns>
|
||||
private bool IsCopyRegionComplete(TwodTexture texture, FormatInfo formatInfo, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
if (x1 != 0 || y1 != 0 || y2 != texture.Height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int width;
|
||||
int widthAlignment;
|
||||
|
||||
if (texture.LinearLayout)
|
||||
{
|
||||
widthAlignment = 1;
|
||||
width = texture.Stride / formatInfo.BytesPerPixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
widthAlignment = Constants.GobAlignment / formatInfo.BytesPerPixel;
|
||||
width = texture.Width;
|
||||
}
|
||||
|
||||
return width == BitUtils.AlignUp(x2, widthAlignment);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a full data copy between two textures, reading and writing guest memory directly.
|
||||
/// The textures must have a matching layout, size, and bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="src">The source texture</param>
|
||||
/// <param name="dst">The destination texture</param>
|
||||
/// <param name="w">Copy width</param>
|
||||
/// <param name="h">Copy height</param>
|
||||
/// <param name="bpp">Bytes per pixel</param>
|
||||
private void UnscaledFullCopy(TwodTexture src, TwodTexture dst, int w, int h, int bpp)
|
||||
{
|
||||
var srcCalculator = new OffsetCalculator(
|
||||
w,
|
||||
h,
|
||||
src.Stride,
|
||||
src.LinearLayout,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
bpp);
|
||||
|
||||
(int _, int srcSize) = srcCalculator.GetRectangleRange(0, 0, w, h);
|
||||
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
ulong srcGpuVa = src.Address.Pack();
|
||||
ulong dstGpuVa = dst.Address.Pack();
|
||||
|
||||
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa, srcSize, true);
|
||||
|
||||
int width;
|
||||
int height = src.Height;
|
||||
if (src.LinearLayout)
|
||||
{
|
||||
width = src.Stride / bpp;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = src.Width;
|
||||
}
|
||||
|
||||
// If the copy is not equal to the width and height of the texture, we will need to copy partially.
|
||||
// It's worth noting that it has already been established that the src and dst are the same size.
|
||||
|
||||
if (w == width && h == height)
|
||||
{
|
||||
memoryManager.Write(dstGpuVa, srcSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
using WritableRegion dstRegion = memoryManager.GetWritableRegion(dstGpuVa, srcSize, true);
|
||||
Span<byte> dstSpan = dstRegion.Memory.Span;
|
||||
|
||||
if (src.LinearLayout)
|
||||
{
|
||||
int stride = src.Stride;
|
||||
int offset = 0;
|
||||
int lineSize = width * bpp;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
srcSpan.Slice(offset, lineSize).CopyTo(dstSpan.Slice(offset));
|
||||
|
||||
offset += stride;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copy with the block linear layout in mind.
|
||||
// Recreate the offset calculate with bpp 1 for copy.
|
||||
|
||||
int stride = w * bpp;
|
||||
|
||||
srcCalculator = new OffsetCalculator(
|
||||
stride,
|
||||
h,
|
||||
0,
|
||||
false,
|
||||
src.MemoryLayout.UnpackGobBlocksInY(),
|
||||
src.MemoryLayout.UnpackGobBlocksInZ(),
|
||||
1);
|
||||
|
||||
int strideTrunc = BitUtils.AlignDown(stride, 16);
|
||||
|
||||
ReadOnlySpan<Vector128<byte>> srcVec = MemoryMarshal.Cast<byte, Vector128<byte>>(srcSpan);
|
||||
Span<Vector128<byte>> dstVec = MemoryMarshal.Cast<byte, Vector128<byte>>(dstSpan);
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
srcCalculator.SetY(y);
|
||||
|
||||
for (; x < strideTrunc; x += 16)
|
||||
{
|
||||
int offset = srcCalculator.GetOffset(x) >> 4;
|
||||
|
||||
dstVec[offset] = srcVec[offset];
|
||||
}
|
||||
|
||||
for (; x < stride; x++)
|
||||
{
|
||||
int offset = srcCalculator.GetOffset(x);
|
||||
|
||||
dstSpan[offset] = srcSpan[offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the blit operation, triggered by the register write.
|
||||
/// </summary>
|
||||
/// <param name="argument">Method call argument</param>
|
||||
private void PixelsFromMemorySrcY0Int(int argument)
|
||||
{
|
||||
var memoryManager = _channel.MemoryManager;
|
||||
|
||||
var dstCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetDstFormat);
|
||||
var srcCopyTexture = Unsafe.As<uint, TwodTexture>(ref _state.State.SetSrcFormat);
|
||||
|
||||
long srcX = ((long)_state.State.SetPixelsFromMemorySrcX0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcX0Frac;
|
||||
long srcY = ((long)_state.State.PixelsFromMemorySrcY0Int << 32) | (long)(ulong)_state.State.SetPixelsFromMemorySrcY0Frac;
|
||||
|
||||
long duDx = ((long)_state.State.SetPixelsFromMemoryDuDxInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDuDxFrac;
|
||||
long dvDy = ((long)_state.State.SetPixelsFromMemoryDvDyInt << 32) | (long)(ulong)_state.State.SetPixelsFromMemoryDvDyFrac;
|
||||
|
||||
bool originCorner = _state.State.SetPixelsFromMemorySampleModeOrigin == SetPixelsFromMemorySampleModeOrigin.Corner;
|
||||
|
||||
if (originCorner)
|
||||
{
|
||||
// If the origin is corner, it is assumed that the guest API
|
||||
// is manually centering the origin by adding a offset to the
|
||||
// source region X/Y coordinates.
|
||||
// Here we attempt to remove such offset to ensure we have the correct region.
|
||||
// The offset is calculated as FactorXY / 2.0, where FactorXY = SrcXY / DstXY,
|
||||
// so we do the same here by dividing the fixed point value by 2, while
|
||||
// throwing away the fractional part to avoid rounding errors.
|
||||
srcX -= (duDx >> 33) << 32;
|
||||
srcY -= (dvDy >> 33) << 32;
|
||||
}
|
||||
|
||||
int srcX1 = (int)(srcX >> 32);
|
||||
int srcY1 = (int)(srcY >> 32);
|
||||
|
||||
int srcX2 = srcX1 + (int)((duDx * _state.State.SetPixelsFromMemoryDstWidth + uint.MaxValue) >> 32);
|
||||
int srcY2 = srcY1 + (int)((dvDy * _state.State.SetPixelsFromMemoryDstHeight + uint.MaxValue) >> 32);
|
||||
|
||||
int dstX1 = (int)_state.State.SetPixelsFromMemoryDstX0;
|
||||
int dstY1 = (int)_state.State.SetPixelsFromMemoryDstY0;
|
||||
|
||||
int dstX2 = dstX1 + (int)_state.State.SetPixelsFromMemoryDstWidth;
|
||||
int dstY2 = dstY1 + (int)_state.State.SetPixelsFromMemoryDstHeight;
|
||||
|
||||
// The source and destination textures should at least be as big as the region being requested.
|
||||
// The hints will only resize within alignment constraints, so out of bound copies won't resize in most cases.
|
||||
var srcHint = new Size(srcX2, srcY2, 1);
|
||||
var dstHint = new Size(dstX2, dstY2, 1);
|
||||
|
||||
var srcCopyTextureFormat = srcCopyTexture.Format.Convert();
|
||||
|
||||
int srcWidthAligned = srcCopyTexture.Stride / srcCopyTextureFormat.BytesPerPixel;
|
||||
|
||||
ulong offset = 0;
|
||||
|
||||
// For an out of bounds copy, we must ensure that the copy wraps to the next line,
|
||||
// so for a copy from a 64x64 texture, in the region [32, 96[, there are 32 pixels that are
|
||||
// outside the bounds of the texture. We fill the destination with the first 32 pixels
|
||||
// of the next line on the source texture.
|
||||
// This can be done by simply adding an offset to the texture address, so that the initial
|
||||
// gap is skipped and the copy is inside bounds again.
|
||||
// This is required by the proprietary guest OpenGL driver.
|
||||
if (srcCopyTexture.LinearLayout && srcCopyTexture.Width == srcX2 && srcX2 > srcWidthAligned && srcX1 > 0)
|
||||
{
|
||||
offset = (ulong)(srcX1 * srcCopyTextureFormat.BytesPerPixel);
|
||||
srcCopyTexture.Width -= srcX1;
|
||||
srcX2 -= srcX1;
|
||||
srcX1 = 0;
|
||||
}
|
||||
|
||||
FormatInfo dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||
|
||||
bool canDirectCopy = GraphicsConfig.Fast2DCopy &&
|
||||
srcX2 == dstX2 && srcY2 == dstY2 &&
|
||||
IsDataCompatible(srcCopyTexture, dstCopyTexture, srcCopyTextureFormat, dstCopyTextureFormat) &&
|
||||
IsCopyRegionComplete(srcCopyTexture, srcCopyTextureFormat, srcX1, srcY1, srcX2, srcY2) &&
|
||||
IsCopyRegionComplete(dstCopyTexture, dstCopyTextureFormat, dstX1, dstY1, dstX2, dstY2);
|
||||
|
||||
var srcTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
srcCopyTexture,
|
||||
offset,
|
||||
srcCopyTextureFormat,
|
||||
!canDirectCopy,
|
||||
false,
|
||||
srcHint);
|
||||
|
||||
if (srcTexture == null)
|
||||
{
|
||||
if (canDirectCopy)
|
||||
{
|
||||
// Directly copy the data on CPU.
|
||||
UnscaledFullCopy(srcCopyTexture, dstCopyTexture, srcX2, srcY2, srcCopyTextureFormat.BytesPerPixel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
memoryManager.Physical.TextureCache.Lift(srcTexture);
|
||||
|
||||
// When the source texture that was found has a depth format,
|
||||
// we must enforce the target texture also has a depth format,
|
||||
// as copies between depth and color formats are not allowed.
|
||||
|
||||
if (srcTexture.Format.IsDepthOrStencil())
|
||||
{
|
||||
dstCopyTextureFormat = srcTexture.Info.FormatInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
dstCopyTextureFormat = dstCopyTexture.Format.Convert();
|
||||
}
|
||||
|
||||
var dstTexture = memoryManager.Physical.TextureCache.FindOrCreateTexture(
|
||||
memoryManager,
|
||||
dstCopyTexture,
|
||||
0,
|
||||
dstCopyTextureFormat,
|
||||
true,
|
||||
srcTexture.ScaleMode == TextureScaleMode.Scaled,
|
||||
dstHint);
|
||||
|
||||
if (dstTexture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (srcTexture.Info.Samples > 1 || dstTexture.Info.Samples > 1)
|
||||
{
|
||||
srcTexture.PropagateScale(dstTexture);
|
||||
}
|
||||
|
||||
float scale = srcTexture.ScaleFactor;
|
||||
float dstScale = dstTexture.ScaleFactor;
|
||||
|
||||
Extents2D srcRegion = new Extents2D(
|
||||
(int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (srcY1 / srcTexture.Info.SamplesInY)),
|
||||
(int)Math.Ceiling(scale * (srcX2 / srcTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
|
||||
|
||||
Extents2D dstRegion = new Extents2D(
|
||||
(int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)),
|
||||
(int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)),
|
||||
(int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY)));
|
||||
|
||||
bool linearFilter = _state.State.SetPixelsFromMemorySampleModeFilter == SetPixelsFromMemorySampleModeFilter.Bilinear;
|
||||
|
||||
srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
|
||||
|
||||
dstTexture.SignalModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
816
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs
Normal file
816
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs
Normal file
@@ -0,0 +1,816 @@
|
||||
// This file was auto-generated from NVIDIA official Maxwell definitions.
|
||||
|
||||
using Ryujinx.Common.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
{
|
||||
/// <summary>
|
||||
/// Notify type.
|
||||
/// </summary>
|
||||
enum NotifyType
|
||||
{
|
||||
WriteOnly = 0,
|
||||
WriteThenAwaken = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstFormatV
|
||||
{
|
||||
A8r8g8b8 = 207,
|
||||
A8rl8gl8bl8 = 208,
|
||||
A2r10g10b10 = 223,
|
||||
A8b8g8r8 = 213,
|
||||
A8bl8gl8rl8 = 214,
|
||||
A2b10g10r10 = 209,
|
||||
X8r8g8b8 = 230,
|
||||
X8rl8gl8bl8 = 231,
|
||||
X8b8g8r8 = 249,
|
||||
X8bl8gl8rl8 = 250,
|
||||
R5g6b5 = 232,
|
||||
A1r5g5b5 = 233,
|
||||
X1r5g5b5 = 248,
|
||||
Y8 = 243,
|
||||
Y16 = 238,
|
||||
Y32 = 255,
|
||||
Z1r5g5b5 = 251,
|
||||
O1r5g5b5 = 252,
|
||||
Z8r8g8b8 = 253,
|
||||
O8r8g8b8 = 254,
|
||||
Y18x8 = 28,
|
||||
Rf16 = 242,
|
||||
Rf32 = 229,
|
||||
Rf32Gf32 = 203,
|
||||
Rf16Gf16Bf16Af16 = 202,
|
||||
Rf16Gf16Bf16X16 = 206,
|
||||
Rf32Gf32Bf32Af32 = 192,
|
||||
Rf32Gf32Bf32X32 = 195,
|
||||
R16G16B16A16 = 198,
|
||||
Rn16Gn16Bn16An16 = 199,
|
||||
Bf10gf11rf11 = 224,
|
||||
An8bn8gn8rn8 = 215,
|
||||
Rf16Gf16 = 222,
|
||||
R16G16 = 218,
|
||||
Rn16Gn16 = 219,
|
||||
G8r8 = 234,
|
||||
Gn8rn8 = 235,
|
||||
Rn16 = 239,
|
||||
Rn8 = 244,
|
||||
A8 = 247,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory layout of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstMemoryLayoutV
|
||||
{
|
||||
Blocklinear = 0,
|
||||
Pitch = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstBlockSizeHeight
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth in GOBs of the destination texture.
|
||||
/// </summary>
|
||||
enum SetDstBlockSizeDepth
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format of the source texture.
|
||||
/// </summary>
|
||||
enum SetSrcFormatV
|
||||
{
|
||||
A8r8g8b8 = 207,
|
||||
A8rl8gl8bl8 = 208,
|
||||
A2r10g10b10 = 223,
|
||||
A8b8g8r8 = 213,
|
||||
A8bl8gl8rl8 = 214,
|
||||
A2b10g10r10 = 209,
|
||||
X8r8g8b8 = 230,
|
||||
X8rl8gl8bl8 = 231,
|
||||
X8b8g8r8 = 249,
|
||||
X8bl8gl8rl8 = 250,
|
||||
R5g6b5 = 232,
|
||||
A1r5g5b5 = 233,
|
||||
X1r5g5b5 = 248,
|
||||
Y8 = 243,
|
||||
Ay8 = 29,
|
||||
Y16 = 238,
|
||||
Y32 = 255,
|
||||
Z1r5g5b5 = 251,
|
||||
O1r5g5b5 = 252,
|
||||
Z8r8g8b8 = 253,
|
||||
O8r8g8b8 = 254,
|
||||
Y18x8 = 28,
|
||||
Rf16 = 242,
|
||||
Rf32 = 229,
|
||||
Rf32Gf32 = 203,
|
||||
Rf16Gf16Bf16Af16 = 202,
|
||||
Rf16Gf16Bf16X16 = 206,
|
||||
Rf32Gf32Bf32Af32 = 192,
|
||||
Rf32Gf32Bf32X32 = 195,
|
||||
R16G16B16A16 = 198,
|
||||
Rn16Gn16Bn16An16 = 199,
|
||||
Bf10gf11rf11 = 224,
|
||||
An8bn8gn8rn8 = 215,
|
||||
Rf16Gf16 = 222,
|
||||
R16G16 = 218,
|
||||
Rn16Gn16 = 219,
|
||||
G8r8 = 234,
|
||||
Gn8rn8 = 235,
|
||||
Rn16 = 239,
|
||||
Rn8 = 244,
|
||||
A8 = 247,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory layout of the source texture.
|
||||
/// </summary>
|
||||
enum SetSrcMemoryLayoutV
|
||||
{
|
||||
Blocklinear = 0,
|
||||
Pitch = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Height in GOBs of the source texture.
|
||||
/// </summary>
|
||||
enum SetSrcBlockSizeHeight
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Depth in GOBs of the source texture.
|
||||
/// </summary>
|
||||
enum SetSrcBlockSizeDepth
|
||||
{
|
||||
OneGob = 0,
|
||||
TwoGobs = 1,
|
||||
FourGobs = 2,
|
||||
EightGobs = 3,
|
||||
SixteenGobs = 4,
|
||||
ThirtytwoGobs = 5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture data caches to invalidate.
|
||||
/// </summary>
|
||||
enum TwodInvalidateTextureDataCacheV
|
||||
{
|
||||
L1Only = 0,
|
||||
L2Only = 1,
|
||||
L1AndL2 = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sector promotion parameters.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemorySectorPromotionV
|
||||
{
|
||||
NoPromotion = 0,
|
||||
PromoteTo2V = 1,
|
||||
PromoteTo2H = 2,
|
||||
PromoteTo4 = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of processing clusters.
|
||||
/// </summary>
|
||||
enum SetNumProcessingClustersV
|
||||
{
|
||||
All = 0,
|
||||
One = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color key format.
|
||||
/// </summary>
|
||||
enum SetColorKeyFormatV
|
||||
{
|
||||
A16r5g6b5 = 0,
|
||||
A1r5g5b5 = 1,
|
||||
A8r8g8b8 = 2,
|
||||
A2r10g10b10 = 3,
|
||||
Y8 = 4,
|
||||
Y16 = 5,
|
||||
Y32 = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color blit operation.
|
||||
/// </summary>
|
||||
enum SetOperationV
|
||||
{
|
||||
SrccopyAnd = 0,
|
||||
RopAnd = 1,
|
||||
BlendAnd = 2,
|
||||
Srccopy = 3,
|
||||
Rop = 4,
|
||||
SrccopyPremult = 5,
|
||||
BlendPremult = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Texture pattern selection.
|
||||
/// </summary>
|
||||
enum SetPatternSelectV
|
||||
{
|
||||
Monochrome8x8 = 0,
|
||||
Monochrome64x1 = 1,
|
||||
Monochrome1x64 = 2,
|
||||
Color = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render enable override mode.
|
||||
/// </summary>
|
||||
enum SetRenderEnableOverrideMode
|
||||
{
|
||||
UseRenderEnable = 0,
|
||||
AlwaysRender = 1,
|
||||
NeverRender = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from memory horizontal direction.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemoryDirectionHorizontal
|
||||
{
|
||||
HwDecides = 0,
|
||||
LeftToRight = 1,
|
||||
RightToLeft = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from memory vertical direction.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemoryDirectionVertical
|
||||
{
|
||||
HwDecides = 0,
|
||||
TopToBottom = 1,
|
||||
BottomToTop = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Color format of the monochrome pattern.
|
||||
/// </summary>
|
||||
enum SetMonochromePatternColorFormatV
|
||||
{
|
||||
A8x8r5g6b5 = 0,
|
||||
A1r5g5b5 = 1,
|
||||
A8r8g8b8 = 2,
|
||||
A8y8 = 3,
|
||||
A8x8y16 = 4,
|
||||
Y32 = 5,
|
||||
ByteExpand = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format of the monochrome pattern.
|
||||
/// </summary>
|
||||
enum SetMonochromePatternFormatV
|
||||
{
|
||||
Cga6M1 = 0,
|
||||
LeM1 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction operation.
|
||||
/// </summary>
|
||||
enum MmeDmaReductionReductionOp
|
||||
{
|
||||
RedAdd = 0,
|
||||
RedMin = 1,
|
||||
RedMax = 2,
|
||||
RedInc = 3,
|
||||
RedDec = 4,
|
||||
RedAnd = 5,
|
||||
RedOr = 6,
|
||||
RedXor = 7,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction format.
|
||||
/// </summary>
|
||||
enum MmeDmaReductionReductionFormat
|
||||
{
|
||||
Unsigned = 0,
|
||||
Signed = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DMA semaphore reduction size.
|
||||
/// </summary>
|
||||
enum MmeDmaReductionReductionSize
|
||||
{
|
||||
FourBytes = 0,
|
||||
EightBytes = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data FIFO size.
|
||||
/// </summary>
|
||||
enum SetMmeDataFifoConfigFifoSize
|
||||
{
|
||||
Size0kb = 0,
|
||||
Size4kb = 1,
|
||||
Size8kb = 2,
|
||||
Size12kb = 3,
|
||||
Size16kb = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render solid primitive mode.
|
||||
/// </summary>
|
||||
enum RenderSolidPrimModeV
|
||||
{
|
||||
Points = 0,
|
||||
Lines = 1,
|
||||
Polyline = 2,
|
||||
Triangles = 3,
|
||||
Rects = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render solid primitive color format.
|
||||
/// </summary>
|
||||
enum SetRenderSolidPrimColorFormatV
|
||||
{
|
||||
Rf32Gf32Bf32Af32 = 192,
|
||||
Rf16Gf16Bf16Af16 = 202,
|
||||
Rf32Gf32 = 203,
|
||||
A8r8g8b8 = 207,
|
||||
A2r10g10b10 = 223,
|
||||
A8b8g8r8 = 213,
|
||||
A2b10g10r10 = 209,
|
||||
X8r8g8b8 = 230,
|
||||
X8b8g8r8 = 249,
|
||||
R5g6b5 = 232,
|
||||
A1r5g5b5 = 233,
|
||||
X1r5g5b5 = 248,
|
||||
Y8 = 243,
|
||||
Y16 = 238,
|
||||
Y32 = 255,
|
||||
Z1r5g5b5 = 251,
|
||||
O1r5g5b5 = 252,
|
||||
Z8r8g8b8 = 253,
|
||||
O8r8g8b8 = 254,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU data type.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuDataTypeV
|
||||
{
|
||||
Color = 0,
|
||||
Index = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU color format.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuColorFormatV
|
||||
{
|
||||
A8r8g8b8 = 207,
|
||||
A2r10g10b10 = 223,
|
||||
A8b8g8r8 = 213,
|
||||
A2b10g10r10 = 209,
|
||||
X8r8g8b8 = 230,
|
||||
X8b8g8r8 = 249,
|
||||
R5g6b5 = 232,
|
||||
A1r5g5b5 = 233,
|
||||
X1r5g5b5 = 248,
|
||||
Y8 = 243,
|
||||
Y16 = 238,
|
||||
Y32 = 255,
|
||||
Z1r5g5b5 = 251,
|
||||
O1r5g5b5 = 252,
|
||||
Z8r8g8b8 = 253,
|
||||
O8r8g8b8 = 254,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU palette index format.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuIndexFormatV
|
||||
{
|
||||
I1 = 0,
|
||||
I4 = 1,
|
||||
I8 = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU monochrome format.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuMonoFormatV
|
||||
{
|
||||
Cga6M1 = 0,
|
||||
LeM1 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU wrap mode.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuWrapV
|
||||
{
|
||||
WrapPixel = 0,
|
||||
WrapByte = 1,
|
||||
WrapDword = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from CPU monochrome opacity.
|
||||
/// </summary>
|
||||
enum SetPixelsFromCpuMonoOpacityV
|
||||
{
|
||||
Transparent = 0,
|
||||
Opaque = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from memory block shape.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemoryBlockShapeV
|
||||
{
|
||||
Auto = 0,
|
||||
Shape8x8 = 1,
|
||||
Shape16x4 = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from memory origin.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemorySampleModeOrigin
|
||||
{
|
||||
Center = 0,
|
||||
Corner = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pixels from memory filter mode.
|
||||
/// </summary>
|
||||
enum SetPixelsFromMemorySampleModeFilter
|
||||
{
|
||||
Point = 0,
|
||||
Bilinear = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Render solid primitive point coordinates.
|
||||
/// </summary>
|
||||
struct RenderSolidPrimPoint
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint SetX;
|
||||
public uint Y;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2D class state.
|
||||
/// </summary>
|
||||
unsafe struct TwodClassState : IShadowState
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint SetObject;
|
||||
public int SetObjectClassId => (int)((SetObject >> 0) & 0xFFFF);
|
||||
public int SetObjectEngineId => (int)((SetObject >> 16) & 0x1F);
|
||||
public fixed uint Reserved04[63];
|
||||
public uint NoOperation;
|
||||
public uint SetNotifyA;
|
||||
public int SetNotifyAAddressUpper => (int)((SetNotifyA >> 0) & 0x1FFFFFF);
|
||||
public uint SetNotifyB;
|
||||
public uint Notify;
|
||||
public NotifyType NotifyType => (NotifyType)(Notify);
|
||||
public uint WaitForIdle;
|
||||
public uint LoadMmeInstructionRamPointer;
|
||||
public uint LoadMmeInstructionRam;
|
||||
public uint LoadMmeStartAddressRamPointer;
|
||||
public uint LoadMmeStartAddressRam;
|
||||
public uint SetMmeShadowRamControl;
|
||||
public SetMmeShadowRamControlMode SetMmeShadowRamControlMode => (SetMmeShadowRamControlMode)((SetMmeShadowRamControl >> 0) & 0x3);
|
||||
public fixed uint Reserved128[2];
|
||||
public uint SetGlobalRenderEnableA;
|
||||
public int SetGlobalRenderEnableAOffsetUpper => (int)((SetGlobalRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetGlobalRenderEnableB;
|
||||
public uint SetGlobalRenderEnableC;
|
||||
public int SetGlobalRenderEnableCMode => (int)((SetGlobalRenderEnableC >> 0) & 0x7);
|
||||
public uint SendGoIdle;
|
||||
public uint PmTrigger;
|
||||
public fixed uint Reserved144[3];
|
||||
public uint SetInstrumentationMethodHeader;
|
||||
public uint SetInstrumentationMethodData;
|
||||
public fixed uint Reserved158[37];
|
||||
public uint SetMmeSwitchState;
|
||||
public bool SetMmeSwitchStateValid => (SetMmeSwitchState & 0x1) != 0;
|
||||
public int SetMmeSwitchStateSaveMacro => (int)((SetMmeSwitchState >> 4) & 0xFF);
|
||||
public int SetMmeSwitchStateRestoreMacro => (int)((SetMmeSwitchState >> 12) & 0xFF);
|
||||
public fixed uint Reserved1F0[4];
|
||||
public uint SetDstFormat;
|
||||
public SetDstFormatV SetDstFormatV => (SetDstFormatV)((SetDstFormat >> 0) & 0xFF);
|
||||
public uint SetDstMemoryLayout;
|
||||
public SetDstMemoryLayoutV SetDstMemoryLayoutV => (SetDstMemoryLayoutV)((SetDstMemoryLayout >> 0) & 0x1);
|
||||
public uint SetDstBlockSize;
|
||||
public SetDstBlockSizeHeight SetDstBlockSizeHeight => (SetDstBlockSizeHeight)((SetDstBlockSize >> 4) & 0x7);
|
||||
public SetDstBlockSizeDepth SetDstBlockSizeDepth => (SetDstBlockSizeDepth)((SetDstBlockSize >> 8) & 0x7);
|
||||
public uint SetDstDepth;
|
||||
public uint SetDstLayer;
|
||||
public uint SetDstPitch;
|
||||
public uint SetDstWidth;
|
||||
public uint SetDstHeight;
|
||||
public uint SetDstOffsetUpper;
|
||||
public int SetDstOffsetUpperV => (int)((SetDstOffsetUpper >> 0) & 0xFF);
|
||||
public uint SetDstOffsetLower;
|
||||
public uint FlushAndInvalidateRopMiniCache;
|
||||
public bool FlushAndInvalidateRopMiniCacheV => (FlushAndInvalidateRopMiniCache & 0x1) != 0;
|
||||
public uint SetSpareNoop06;
|
||||
public uint SetSrcFormat;
|
||||
public SetSrcFormatV SetSrcFormatV => (SetSrcFormatV)((SetSrcFormat >> 0) & 0xFF);
|
||||
public uint SetSrcMemoryLayout;
|
||||
public SetSrcMemoryLayoutV SetSrcMemoryLayoutV => (SetSrcMemoryLayoutV)((SetSrcMemoryLayout >> 0) & 0x1);
|
||||
public uint SetSrcBlockSize;
|
||||
public SetSrcBlockSizeHeight SetSrcBlockSizeHeight => (SetSrcBlockSizeHeight)((SetSrcBlockSize >> 4) & 0x7);
|
||||
public SetSrcBlockSizeDepth SetSrcBlockSizeDepth => (SetSrcBlockSizeDepth)((SetSrcBlockSize >> 8) & 0x7);
|
||||
public uint SetSrcDepth;
|
||||
public uint TwodInvalidateTextureDataCache;
|
||||
public TwodInvalidateTextureDataCacheV TwodInvalidateTextureDataCacheV => (TwodInvalidateTextureDataCacheV)((TwodInvalidateTextureDataCache >> 0) & 0x3);
|
||||
public uint SetSrcPitch;
|
||||
public uint SetSrcWidth;
|
||||
public uint SetSrcHeight;
|
||||
public uint SetSrcOffsetUpper;
|
||||
public int SetSrcOffsetUpperV => (int)((SetSrcOffsetUpper >> 0) & 0xFF);
|
||||
public uint SetSrcOffsetLower;
|
||||
public uint SetPixelsFromMemorySectorPromotion;
|
||||
public SetPixelsFromMemorySectorPromotionV SetPixelsFromMemorySectorPromotionV => (SetPixelsFromMemorySectorPromotionV)((SetPixelsFromMemorySectorPromotion >> 0) & 0x3);
|
||||
public uint SetSpareNoop12;
|
||||
public uint SetNumProcessingClusters;
|
||||
public SetNumProcessingClustersV SetNumProcessingClustersV => (SetNumProcessingClustersV)((SetNumProcessingClusters >> 0) & 0x1);
|
||||
public uint SetRenderEnableA;
|
||||
public int SetRenderEnableAOffsetUpper => (int)((SetRenderEnableA >> 0) & 0xFF);
|
||||
public uint SetRenderEnableB;
|
||||
public uint SetRenderEnableC;
|
||||
public int SetRenderEnableCMode => (int)((SetRenderEnableC >> 0) & 0x7);
|
||||
public uint SetSpareNoop08;
|
||||
public uint SetSpareNoop01;
|
||||
public uint SetSpareNoop11;
|
||||
public uint SetSpareNoop07;
|
||||
public uint SetClipX0;
|
||||
public uint SetClipY0;
|
||||
public uint SetClipWidth;
|
||||
public uint SetClipHeight;
|
||||
public uint SetClipEnable;
|
||||
public bool SetClipEnableV => (SetClipEnable & 0x1) != 0;
|
||||
public uint SetColorKeyFormat;
|
||||
public SetColorKeyFormatV SetColorKeyFormatV => (SetColorKeyFormatV)((SetColorKeyFormat >> 0) & 0x7);
|
||||
public uint SetColorKey;
|
||||
public uint SetColorKeyEnable;
|
||||
public bool SetColorKeyEnableV => (SetColorKeyEnable & 0x1) != 0;
|
||||
public uint SetRop;
|
||||
public int SetRopV => (int)((SetRop >> 0) & 0xFF);
|
||||
public uint SetBeta1;
|
||||
public uint SetBeta4;
|
||||
public int SetBeta4B => (int)((SetBeta4 >> 0) & 0xFF);
|
||||
public int SetBeta4G => (int)((SetBeta4 >> 8) & 0xFF);
|
||||
public int SetBeta4R => (int)((SetBeta4 >> 16) & 0xFF);
|
||||
public int SetBeta4A => (int)((SetBeta4 >> 24) & 0xFF);
|
||||
public uint SetOperation;
|
||||
public SetOperationV SetOperationV => (SetOperationV)((SetOperation >> 0) & 0x7);
|
||||
public uint SetPatternOffset;
|
||||
public int SetPatternOffsetX => (int)((SetPatternOffset >> 0) & 0x3F);
|
||||
public int SetPatternOffsetY => (int)((SetPatternOffset >> 8) & 0x3F);
|
||||
public uint SetPatternSelect;
|
||||
public SetPatternSelectV SetPatternSelectV => (SetPatternSelectV)((SetPatternSelect >> 0) & 0x3);
|
||||
public uint SetDstColorRenderToZetaSurface;
|
||||
public bool SetDstColorRenderToZetaSurfaceV => (SetDstColorRenderToZetaSurface & 0x1) != 0;
|
||||
public uint SetSpareNoop04;
|
||||
public uint SetSpareNoop15;
|
||||
public uint SetSpareNoop13;
|
||||
public uint SetSpareNoop03;
|
||||
public uint SetSpareNoop14;
|
||||
public uint SetSpareNoop02;
|
||||
public uint SetCompression;
|
||||
public bool SetCompressionEnable => (SetCompression & 0x1) != 0;
|
||||
public uint SetSpareNoop09;
|
||||
public uint SetRenderEnableOverride;
|
||||
public SetRenderEnableOverrideMode SetRenderEnableOverrideMode => (SetRenderEnableOverrideMode)((SetRenderEnableOverride >> 0) & 0x3);
|
||||
public uint SetPixelsFromMemoryDirection;
|
||||
public SetPixelsFromMemoryDirectionHorizontal SetPixelsFromMemoryDirectionHorizontal => (SetPixelsFromMemoryDirectionHorizontal)((SetPixelsFromMemoryDirection >> 0) & 0x3);
|
||||
public SetPixelsFromMemoryDirectionVertical SetPixelsFromMemoryDirectionVertical => (SetPixelsFromMemoryDirectionVertical)((SetPixelsFromMemoryDirection >> 4) & 0x3);
|
||||
public uint SetSpareNoop10;
|
||||
public uint SetMonochromePatternColorFormat;
|
||||
public SetMonochromePatternColorFormatV SetMonochromePatternColorFormatV => (SetMonochromePatternColorFormatV)((SetMonochromePatternColorFormat >> 0) & 0x7);
|
||||
public uint SetMonochromePatternFormat;
|
||||
public SetMonochromePatternFormatV SetMonochromePatternFormatV => (SetMonochromePatternFormatV)((SetMonochromePatternFormat >> 0) & 0x1);
|
||||
public uint SetMonochromePatternColor0;
|
||||
public uint SetMonochromePatternColor1;
|
||||
public uint SetMonochromePattern0;
|
||||
public uint SetMonochromePattern1;
|
||||
public Array64<uint> ColorPatternX8r8g8b8;
|
||||
public int ColorPatternX8r8g8b8B0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 0) & 0xFF);
|
||||
public int ColorPatternX8r8g8b8G0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 8) & 0xFF);
|
||||
public int ColorPatternX8r8g8b8R0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 16) & 0xFF);
|
||||
public int ColorPatternX8r8g8b8Ignore0(int i) => (int)((ColorPatternX8r8g8b8[i] >> 24) & 0xFF);
|
||||
public Array32<uint> ColorPatternR5g6b5;
|
||||
public int ColorPatternR5g6b5B0(int i) => (int)((ColorPatternR5g6b5[i] >> 0) & 0x1F);
|
||||
public int ColorPatternR5g6b5G0(int i) => (int)((ColorPatternR5g6b5[i] >> 5) & 0x3F);
|
||||
public int ColorPatternR5g6b5R0(int i) => (int)((ColorPatternR5g6b5[i] >> 11) & 0x1F);
|
||||
public int ColorPatternR5g6b5B1(int i) => (int)((ColorPatternR5g6b5[i] >> 16) & 0x1F);
|
||||
public int ColorPatternR5g6b5G1(int i) => (int)((ColorPatternR5g6b5[i] >> 21) & 0x3F);
|
||||
public int ColorPatternR5g6b5R1(int i) => (int)((ColorPatternR5g6b5[i] >> 27) & 0x1F);
|
||||
public Array32<uint> ColorPatternX1r5g5b5;
|
||||
public int ColorPatternX1r5g5b5B0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 0) & 0x1F);
|
||||
public int ColorPatternX1r5g5b5G0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 5) & 0x1F);
|
||||
public int ColorPatternX1r5g5b5R0(int i) => (int)((ColorPatternX1r5g5b5[i] >> 10) & 0x1F);
|
||||
public bool ColorPatternX1r5g5b5Ignore0(int i) => (ColorPatternX1r5g5b5[i] & 0x8000) != 0;
|
||||
public int ColorPatternX1r5g5b5B1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 16) & 0x1F);
|
||||
public int ColorPatternX1r5g5b5G1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 21) & 0x1F);
|
||||
public int ColorPatternX1r5g5b5R1(int i) => (int)((ColorPatternX1r5g5b5[i] >> 26) & 0x1F);
|
||||
public bool ColorPatternX1r5g5b5Ignore1(int i) => (ColorPatternX1r5g5b5[i] & 0x80000000) != 0;
|
||||
public Array16<uint> ColorPatternY8;
|
||||
public int ColorPatternY8Y0(int i) => (int)((ColorPatternY8[i] >> 0) & 0xFF);
|
||||
public int ColorPatternY8Y1(int i) => (int)((ColorPatternY8[i] >> 8) & 0xFF);
|
||||
public int ColorPatternY8Y2(int i) => (int)((ColorPatternY8[i] >> 16) & 0xFF);
|
||||
public int ColorPatternY8Y3(int i) => (int)((ColorPatternY8[i] >> 24) & 0xFF);
|
||||
public uint SetRenderSolidPrimColor0;
|
||||
public uint SetRenderSolidPrimColor1;
|
||||
public uint SetRenderSolidPrimColor2;
|
||||
public uint SetRenderSolidPrimColor3;
|
||||
public uint SetMmeMemAddressA;
|
||||
public int SetMmeMemAddressAUpper => (int)((SetMmeMemAddressA >> 0) & 0x1FFFFFF);
|
||||
public uint SetMmeMemAddressB;
|
||||
public uint SetMmeDataRamAddress;
|
||||
public uint MmeDmaRead;
|
||||
public uint MmeDmaReadFifoed;
|
||||
public uint MmeDmaWrite;
|
||||
public uint MmeDmaReduction;
|
||||
public MmeDmaReductionReductionOp MmeDmaReductionReductionOp => (MmeDmaReductionReductionOp)((MmeDmaReduction >> 0) & 0x7);
|
||||
public MmeDmaReductionReductionFormat MmeDmaReductionReductionFormat => (MmeDmaReductionReductionFormat)((MmeDmaReduction >> 4) & 0x3);
|
||||
public MmeDmaReductionReductionSize MmeDmaReductionReductionSize => (MmeDmaReductionReductionSize)((MmeDmaReduction >> 8) & 0x1);
|
||||
public uint MmeDmaSysmembar;
|
||||
public bool MmeDmaSysmembarV => (MmeDmaSysmembar & 0x1) != 0;
|
||||
public uint MmeDmaSync;
|
||||
public uint SetMmeDataFifoConfig;
|
||||
public SetMmeDataFifoConfigFifoSize SetMmeDataFifoConfigFifoSize => (SetMmeDataFifoConfigFifoSize)((SetMmeDataFifoConfig >> 0) & 0x7);
|
||||
public fixed uint Reserved578[2];
|
||||
public uint RenderSolidPrimMode;
|
||||
public RenderSolidPrimModeV RenderSolidPrimModeV => (RenderSolidPrimModeV)((RenderSolidPrimMode >> 0) & 0x7);
|
||||
public uint SetRenderSolidPrimColorFormat;
|
||||
public SetRenderSolidPrimColorFormatV SetRenderSolidPrimColorFormatV => (SetRenderSolidPrimColorFormatV)((SetRenderSolidPrimColorFormat >> 0) & 0xFF);
|
||||
public uint SetRenderSolidPrimColor;
|
||||
public uint SetRenderSolidLineTieBreakBits;
|
||||
public bool SetRenderSolidLineTieBreakBitsXmajXincYinc => (SetRenderSolidLineTieBreakBits & 0x1) != 0;
|
||||
public bool SetRenderSolidLineTieBreakBitsXmajXdecYinc => (SetRenderSolidLineTieBreakBits & 0x10) != 0;
|
||||
public bool SetRenderSolidLineTieBreakBitsYmajXincYinc => (SetRenderSolidLineTieBreakBits & 0x100) != 0;
|
||||
public bool SetRenderSolidLineTieBreakBitsYmajXdecYinc => (SetRenderSolidLineTieBreakBits & 0x1000) != 0;
|
||||
public fixed uint Reserved590[20];
|
||||
public uint RenderSolidPrimPointXY;
|
||||
public int RenderSolidPrimPointXYX => (int)((RenderSolidPrimPointXY >> 0) & 0xFFFF);
|
||||
public int RenderSolidPrimPointXYY => (int)((RenderSolidPrimPointXY >> 16) & 0xFFFF);
|
||||
public fixed uint Reserved5E4[7];
|
||||
public Array64<RenderSolidPrimPoint> RenderSolidPrimPoint;
|
||||
public uint SetPixelsFromCpuDataType;
|
||||
public SetPixelsFromCpuDataTypeV SetPixelsFromCpuDataTypeV => (SetPixelsFromCpuDataTypeV)((SetPixelsFromCpuDataType >> 0) & 0x1);
|
||||
public uint SetPixelsFromCpuColorFormat;
|
||||
public SetPixelsFromCpuColorFormatV SetPixelsFromCpuColorFormatV => (SetPixelsFromCpuColorFormatV)((SetPixelsFromCpuColorFormat >> 0) & 0xFF);
|
||||
public uint SetPixelsFromCpuIndexFormat;
|
||||
public SetPixelsFromCpuIndexFormatV SetPixelsFromCpuIndexFormatV => (SetPixelsFromCpuIndexFormatV)((SetPixelsFromCpuIndexFormat >> 0) & 0x3);
|
||||
public uint SetPixelsFromCpuMonoFormat;
|
||||
public SetPixelsFromCpuMonoFormatV SetPixelsFromCpuMonoFormatV => (SetPixelsFromCpuMonoFormatV)((SetPixelsFromCpuMonoFormat >> 0) & 0x1);
|
||||
public uint SetPixelsFromCpuWrap;
|
||||
public SetPixelsFromCpuWrapV SetPixelsFromCpuWrapV => (SetPixelsFromCpuWrapV)((SetPixelsFromCpuWrap >> 0) & 0x3);
|
||||
public uint SetPixelsFromCpuColor0;
|
||||
public uint SetPixelsFromCpuColor1;
|
||||
public uint SetPixelsFromCpuMonoOpacity;
|
||||
public SetPixelsFromCpuMonoOpacityV SetPixelsFromCpuMonoOpacityV => (SetPixelsFromCpuMonoOpacityV)((SetPixelsFromCpuMonoOpacity >> 0) & 0x1);
|
||||
public fixed uint Reserved820[6];
|
||||
public uint SetPixelsFromCpuSrcWidth;
|
||||
public uint SetPixelsFromCpuSrcHeight;
|
||||
public uint SetPixelsFromCpuDxDuFrac;
|
||||
public uint SetPixelsFromCpuDxDuInt;
|
||||
public uint SetPixelsFromCpuDyDvFrac;
|
||||
public uint SetPixelsFromCpuDyDvInt;
|
||||
public uint SetPixelsFromCpuDstX0Frac;
|
||||
public uint SetPixelsFromCpuDstX0Int;
|
||||
public uint SetPixelsFromCpuDstY0Frac;
|
||||
public uint SetPixelsFromCpuDstY0Int;
|
||||
public uint PixelsFromCpuData;
|
||||
public fixed uint Reserved864[3];
|
||||
public uint SetBigEndianControl;
|
||||
public bool SetBigEndianControlX32Swap1 => (SetBigEndianControl & 0x1) != 0;
|
||||
public bool SetBigEndianControlX32Swap4 => (SetBigEndianControl & 0x2) != 0;
|
||||
public bool SetBigEndianControlX32Swap8 => (SetBigEndianControl & 0x4) != 0;
|
||||
public bool SetBigEndianControlX32Swap16 => (SetBigEndianControl & 0x8) != 0;
|
||||
public bool SetBigEndianControlX16Swap1 => (SetBigEndianControl & 0x10) != 0;
|
||||
public bool SetBigEndianControlX16Swap4 => (SetBigEndianControl & 0x20) != 0;
|
||||
public bool SetBigEndianControlX16Swap8 => (SetBigEndianControl & 0x40) != 0;
|
||||
public bool SetBigEndianControlX16Swap16 => (SetBigEndianControl & 0x80) != 0;
|
||||
public bool SetBigEndianControlX8Swap1 => (SetBigEndianControl & 0x100) != 0;
|
||||
public bool SetBigEndianControlX8Swap4 => (SetBigEndianControl & 0x200) != 0;
|
||||
public bool SetBigEndianControlX8Swap8 => (SetBigEndianControl & 0x400) != 0;
|
||||
public bool SetBigEndianControlX8Swap16 => (SetBigEndianControl & 0x800) != 0;
|
||||
public bool SetBigEndianControlI1X8Cga6Swap1 => (SetBigEndianControl & 0x1000) != 0;
|
||||
public bool SetBigEndianControlI1X8Cga6Swap4 => (SetBigEndianControl & 0x2000) != 0;
|
||||
public bool SetBigEndianControlI1X8Cga6Swap8 => (SetBigEndianControl & 0x4000) != 0;
|
||||
public bool SetBigEndianControlI1X8Cga6Swap16 => (SetBigEndianControl & 0x8000) != 0;
|
||||
public bool SetBigEndianControlI1X8LeSwap1 => (SetBigEndianControl & 0x10000) != 0;
|
||||
public bool SetBigEndianControlI1X8LeSwap4 => (SetBigEndianControl & 0x20000) != 0;
|
||||
public bool SetBigEndianControlI1X8LeSwap8 => (SetBigEndianControl & 0x40000) != 0;
|
||||
public bool SetBigEndianControlI1X8LeSwap16 => (SetBigEndianControl & 0x80000) != 0;
|
||||
public bool SetBigEndianControlI4Swap1 => (SetBigEndianControl & 0x100000) != 0;
|
||||
public bool SetBigEndianControlI4Swap4 => (SetBigEndianControl & 0x200000) != 0;
|
||||
public bool SetBigEndianControlI4Swap8 => (SetBigEndianControl & 0x400000) != 0;
|
||||
public bool SetBigEndianControlI4Swap16 => (SetBigEndianControl & 0x800000) != 0;
|
||||
public bool SetBigEndianControlI8Swap1 => (SetBigEndianControl & 0x1000000) != 0;
|
||||
public bool SetBigEndianControlI8Swap4 => (SetBigEndianControl & 0x2000000) != 0;
|
||||
public bool SetBigEndianControlI8Swap8 => (SetBigEndianControl & 0x4000000) != 0;
|
||||
public bool SetBigEndianControlI8Swap16 => (SetBigEndianControl & 0x8000000) != 0;
|
||||
public bool SetBigEndianControlOverride => (SetBigEndianControl & 0x10000000) != 0;
|
||||
public fixed uint Reserved874[3];
|
||||
public uint SetPixelsFromMemoryBlockShape;
|
||||
public SetPixelsFromMemoryBlockShapeV SetPixelsFromMemoryBlockShapeV => (SetPixelsFromMemoryBlockShapeV)((SetPixelsFromMemoryBlockShape >> 0) & 0x7);
|
||||
public uint SetPixelsFromMemoryCorralSize;
|
||||
public int SetPixelsFromMemoryCorralSizeV => (int)((SetPixelsFromMemoryCorralSize >> 0) & 0x3FF);
|
||||
public uint SetPixelsFromMemorySafeOverlap;
|
||||
public bool SetPixelsFromMemorySafeOverlapV => (SetPixelsFromMemorySafeOverlap & 0x1) != 0;
|
||||
public uint SetPixelsFromMemorySampleMode;
|
||||
public SetPixelsFromMemorySampleModeOrigin SetPixelsFromMemorySampleModeOrigin => (SetPixelsFromMemorySampleModeOrigin)((SetPixelsFromMemorySampleMode >> 0) & 0x1);
|
||||
public SetPixelsFromMemorySampleModeFilter SetPixelsFromMemorySampleModeFilter => (SetPixelsFromMemorySampleModeFilter)((SetPixelsFromMemorySampleMode >> 4) & 0x1);
|
||||
public fixed uint Reserved890[8];
|
||||
public uint SetPixelsFromMemoryDstX0;
|
||||
public uint SetPixelsFromMemoryDstY0;
|
||||
public uint SetPixelsFromMemoryDstWidth;
|
||||
public uint SetPixelsFromMemoryDstHeight;
|
||||
public uint SetPixelsFromMemoryDuDxFrac;
|
||||
public uint SetPixelsFromMemoryDuDxInt;
|
||||
public uint SetPixelsFromMemoryDvDyFrac;
|
||||
public uint SetPixelsFromMemoryDvDyInt;
|
||||
public uint SetPixelsFromMemorySrcX0Frac;
|
||||
public uint SetPixelsFromMemorySrcX0Int;
|
||||
public uint SetPixelsFromMemorySrcY0Frac;
|
||||
public uint PixelsFromMemorySrcY0Int;
|
||||
public uint SetFalcon00;
|
||||
public uint SetFalcon01;
|
||||
public uint SetFalcon02;
|
||||
public uint SetFalcon03;
|
||||
public uint SetFalcon04;
|
||||
public uint SetFalcon05;
|
||||
public uint SetFalcon06;
|
||||
public uint SetFalcon07;
|
||||
public uint SetFalcon08;
|
||||
public uint SetFalcon09;
|
||||
public uint SetFalcon10;
|
||||
public uint SetFalcon11;
|
||||
public uint SetFalcon12;
|
||||
public uint SetFalcon13;
|
||||
public uint SetFalcon14;
|
||||
public uint SetFalcon15;
|
||||
public uint SetFalcon16;
|
||||
public uint SetFalcon17;
|
||||
public uint SetFalcon18;
|
||||
public uint SetFalcon19;
|
||||
public uint SetFalcon20;
|
||||
public uint SetFalcon21;
|
||||
public uint SetFalcon22;
|
||||
public uint SetFalcon23;
|
||||
public uint SetFalcon24;
|
||||
public uint SetFalcon25;
|
||||
public uint SetFalcon26;
|
||||
public uint SetFalcon27;
|
||||
public uint SetFalcon28;
|
||||
public uint SetFalcon29;
|
||||
public uint SetFalcon30;
|
||||
public uint SetFalcon31;
|
||||
public fixed uint Reserved960[291];
|
||||
public uint MmeDmaWriteMethodBarrier;
|
||||
public bool MmeDmaWriteMethodBarrierV => (MmeDmaWriteMethodBarrier & 0x1) != 0;
|
||||
public fixed uint ReservedDF0[2436];
|
||||
public MmeShadowScratch SetMmeShadowScratch;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
22
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs
Normal file
22
src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Twod
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture to texture (with optional resizing) copy parameters.
|
||||
/// </summary>
|
||||
struct TwodTexture
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public ColorFormat Format;
|
||||
public Boolean32 LinearLayout;
|
||||
public MemoryLayout MemoryLayout;
|
||||
public int Depth;
|
||||
public int Layer;
|
||||
public int Stride;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public GpuVa Address;
|
||||
#pragma warning restore CS0649
|
||||
}
|
||||
}
|
||||
17
src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs
Normal file
17
src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Boolean value, stored as a 32-bits integer in memory.
|
||||
/// </summary>
|
||||
struct Boolean32
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
private uint _value;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public static implicit operator bool(Boolean32 value)
|
||||
{
|
||||
return (value._value & 1) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
165
src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs
Normal file
165
src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs
Normal file
@@ -0,0 +1,165 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Color texture format.
|
||||
/// </summary>
|
||||
enum ColorFormat
|
||||
{
|
||||
R32G32B32A32Float = 0xc0,
|
||||
R32G32B32A32Sint = 0xc1,
|
||||
R32G32B32A32Uint = 0xc2,
|
||||
R32G32B32X32Float = 0xc3,
|
||||
R32G32B32X32Sint = 0xc4,
|
||||
R32G32B32X32Uint = 0xc5,
|
||||
R16G16B16X16Unorm = 0xc6,
|
||||
R16G16B16X16Snorm = 0xc7,
|
||||
R16G16B16X16Sint = 0xc8,
|
||||
R16G16B16X16Uint = 0xc9,
|
||||
R16G16B16A16Float = 0xca,
|
||||
R32G32Float = 0xcb,
|
||||
R32G32Sint = 0xcc,
|
||||
R32G32Uint = 0xcd,
|
||||
R16G16B16X16Float = 0xce,
|
||||
B8G8R8A8Unorm = 0xcf,
|
||||
B8G8R8A8Srgb = 0xd0,
|
||||
R10G10B10A2Unorm = 0xd1,
|
||||
R10G10B10A2Uint = 0xd2,
|
||||
R8G8B8A8Unorm = 0xd5,
|
||||
R8G8B8A8Srgb = 0xd6,
|
||||
R8G8B8X8Snorm = 0xd7,
|
||||
R8G8B8X8Sint = 0xd8,
|
||||
R8G8B8X8Uint = 0xd9,
|
||||
R16G16Unorm = 0xda,
|
||||
R16G16Snorm = 0xdb,
|
||||
R16G16Sint = 0xdc,
|
||||
R16G16Uint = 0xdd,
|
||||
R16G16Float = 0xde,
|
||||
R11G11B10Float = 0xe0,
|
||||
R32Sint = 0xe3,
|
||||
R32Uint = 0xe4,
|
||||
R32Float = 0xe5,
|
||||
B8G8R8X8Unorm = 0xe6,
|
||||
B8G8R8X8Srgb = 0xe7,
|
||||
B5G6R5Unorm = 0xe8,
|
||||
B5G5R5A1Unorm = 0xe9,
|
||||
R8G8Unorm = 0xea,
|
||||
R8G8Snorm = 0xeb,
|
||||
R8G8Sint = 0xec,
|
||||
R8G8Uint = 0xed,
|
||||
R16Unorm = 0xee,
|
||||
R16Snorm = 0xef,
|
||||
R16Sint = 0xf0,
|
||||
R16Uint = 0xf1,
|
||||
R16Float = 0xf2,
|
||||
R8Unorm = 0xf3,
|
||||
R8Snorm = 0xf4,
|
||||
R8Sint = 0xf5,
|
||||
R8Uint = 0xf6,
|
||||
B5G5R5X1Unorm = 0xf8,
|
||||
R8G8B8X8Unorm = 0xf9,
|
||||
R8G8B8X8Srgb = 0xfa
|
||||
}
|
||||
|
||||
static class ColorFormatConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the color texture format to a host compatible format.
|
||||
/// </summary>
|
||||
/// <param name="format">Color format</param>
|
||||
/// <returns>Host compatible format enum value</returns>
|
||||
public static FormatInfo Convert(this ColorFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ColorFormat.R32G32B32A32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4),
|
||||
ColorFormat.R32G32B32A32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4),
|
||||
ColorFormat.R32G32B32A32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
|
||||
ColorFormat.R32G32B32X32Float => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4),
|
||||
ColorFormat.R32G32B32X32Sint => new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4),
|
||||
ColorFormat.R32G32B32X32Uint => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
|
||||
ColorFormat.R16G16B16X16Unorm => new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4),
|
||||
ColorFormat.R16G16B16X16Snorm => new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4),
|
||||
ColorFormat.R16G16B16X16Sint => new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4),
|
||||
ColorFormat.R16G16B16X16Uint => new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4),
|
||||
ColorFormat.R16G16B16A16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4),
|
||||
ColorFormat.R32G32Float => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2),
|
||||
ColorFormat.R32G32Sint => new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2),
|
||||
ColorFormat.R32G32Uint => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
|
||||
ColorFormat.R16G16B16X16Float => new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4),
|
||||
ColorFormat.B8G8R8A8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.B8G8R8A8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4),
|
||||
ColorFormat.R10G10B10A2Unorm => new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.R10G10B10A2Uint => new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8A8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8A8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8X8Snorm => new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8X8Sint => new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8X8Uint => new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4),
|
||||
ColorFormat.R16G16Unorm => new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Snorm => new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2),
|
||||
ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2),
|
||||
ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3),
|
||||
ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1),
|
||||
ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
||||
ColorFormat.R32Float => new FormatInfo(Format.R32Float, 1, 1, 4, 1),
|
||||
ColorFormat.B8G8R8X8Unorm => new FormatInfo(Format.B8G8R8A8Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.B8G8R8X8Srgb => new FormatInfo(Format.B8G8R8A8Srgb, 1, 1, 4, 4),
|
||||
ColorFormat.B5G6R5Unorm => new FormatInfo(Format.B5G6R5Unorm, 1, 1, 2, 3),
|
||||
ColorFormat.B5G5R5A1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2, 4),
|
||||
ColorFormat.R8G8Unorm => new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2),
|
||||
ColorFormat.R8G8Snorm => new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2),
|
||||
ColorFormat.R8G8Sint => new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2),
|
||||
ColorFormat.R8G8Uint => new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2),
|
||||
ColorFormat.R16Unorm => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
|
||||
ColorFormat.R16Snorm => new FormatInfo(Format.R16Snorm, 1, 1, 2, 1),
|
||||
ColorFormat.R16Sint => new FormatInfo(Format.R16Sint, 1, 1, 2, 1),
|
||||
ColorFormat.R16Uint => new FormatInfo(Format.R16Uint, 1, 1, 2, 1),
|
||||
ColorFormat.R16Float => new FormatInfo(Format.R16Float, 1, 1, 2, 1),
|
||||
ColorFormat.R8Unorm => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
|
||||
ColorFormat.R8Snorm => new FormatInfo(Format.R8Snorm, 1, 1, 1, 1),
|
||||
ColorFormat.R8Sint => new FormatInfo(Format.R8Sint, 1, 1, 1, 1),
|
||||
ColorFormat.R8Uint => new FormatInfo(Format.R8Uint, 1, 1, 1, 1),
|
||||
ColorFormat.B5G5R5X1Unorm => new FormatInfo(Format.B5G5R5A1Unorm, 1, 1, 2, 4),
|
||||
ColorFormat.R8G8B8X8Unorm => new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4),
|
||||
ColorFormat.R8G8B8X8Srgb => new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4),
|
||||
_ => FormatInfo.Default
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a format has an alpha component.
|
||||
/// </summary>
|
||||
/// <param name="format">Format to be checked</param>
|
||||
/// <returns>True if the format has no alpha component (RGBX), false if it does (RGBA)</returns>
|
||||
public static bool NoAlpha(this ColorFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ColorFormat.R32G32B32X32Float:
|
||||
case ColorFormat.R32G32B32X32Sint:
|
||||
case ColorFormat.R32G32B32X32Uint:
|
||||
case ColorFormat.R16G16B16X16Unorm:
|
||||
case ColorFormat.R16G16B16X16Snorm:
|
||||
case ColorFormat.R16G16B16X16Sint:
|
||||
case ColorFormat.R16G16B16X16Uint:
|
||||
case ColorFormat.R16G16B16X16Float:
|
||||
case ColorFormat.R8G8B8X8Snorm:
|
||||
case ColorFormat.R8G8B8X8Sint:
|
||||
case ColorFormat.R8G8B8X8Uint:
|
||||
case ColorFormat.B8G8R8X8Unorm:
|
||||
case ColorFormat.B8G8R8X8Srgb:
|
||||
case ColorFormat.B5G5R5X1Unorm:
|
||||
case ColorFormat.R8G8B8X8Unorm:
|
||||
case ColorFormat.R8G8B8X8Srgb:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs
Normal file
22
src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Split GPU virtual address.
|
||||
/// </summary>
|
||||
struct GpuVa
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint High;
|
||||
public uint Low;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
/// <summary>
|
||||
/// Packs the split address into a 64-bits address value.
|
||||
/// </summary>
|
||||
/// <returns>The 64-bits address value</returns>
|
||||
public ulong Pack()
|
||||
{
|
||||
return Low | ((ulong)High << 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs
Normal file
37
src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Memory layout parameters, for block linear textures.
|
||||
/// </summary>
|
||||
struct MemoryLayout
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint Packed;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public int UnpackGobBlocksInX()
|
||||
{
|
||||
return 1 << (int)(Packed & 0xf);
|
||||
}
|
||||
|
||||
public int UnpackGobBlocksInY()
|
||||
{
|
||||
return 1 << (int)((Packed >> 4) & 0xf);
|
||||
}
|
||||
|
||||
public int UnpackGobBlocksInZ()
|
||||
{
|
||||
return 1 << (int)((Packed >> 8) & 0xf);
|
||||
}
|
||||
|
||||
public bool UnpackIsLinear()
|
||||
{
|
||||
return (Packed & 0x1000) != 0;
|
||||
}
|
||||
|
||||
public bool UnpackIsTarget3D()
|
||||
{
|
||||
return (Packed & 0x10000) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs
Normal file
99
src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Draw primitive type.
|
||||
/// </summary>
|
||||
enum PrimitiveType
|
||||
{
|
||||
Points,
|
||||
Lines,
|
||||
LineLoop,
|
||||
LineStrip,
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan,
|
||||
Quads,
|
||||
QuadStrip,
|
||||
Polygon,
|
||||
LinesAdjacency,
|
||||
LineStripAdjacency,
|
||||
TrianglesAdjacency,
|
||||
TriangleStripAdjacency,
|
||||
Patches
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Alternative primitive type that might override <see cref="PrimitiveType"/>.
|
||||
/// </summary>
|
||||
enum PrimitiveTypeOverride
|
||||
{
|
||||
Points = 1,
|
||||
Lines = 2,
|
||||
LineStrip = 3,
|
||||
Triangles = 4,
|
||||
TriangleStrip = 5,
|
||||
TriangleFan = 0x1015,
|
||||
LinesAdjacency = 10,
|
||||
LineStripAdjacency = 11,
|
||||
TrianglesAdjacency = 12,
|
||||
TriangleStripAdjacency = 13,
|
||||
Patches = 14
|
||||
}
|
||||
|
||||
static class PrimitiveTypeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the primitive type into something that can be used with the host API.
|
||||
/// </summary>
|
||||
/// <param name="type">The primitive type to convert</param>
|
||||
/// <returns>A host compatible enum value</returns>
|
||||
public static PrimitiveTopology Convert(this PrimitiveType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PrimitiveType.Points => PrimitiveTopology.Points,
|
||||
PrimitiveType.Lines => PrimitiveTopology.Lines,
|
||||
PrimitiveType.LineLoop => PrimitiveTopology.LineLoop,
|
||||
PrimitiveType.LineStrip => PrimitiveTopology.LineStrip,
|
||||
PrimitiveType.Triangles => PrimitiveTopology.Triangles,
|
||||
PrimitiveType.TriangleStrip => PrimitiveTopology.TriangleStrip,
|
||||
PrimitiveType.TriangleFan => PrimitiveTopology.TriangleFan,
|
||||
PrimitiveType.Quads => PrimitiveTopology.Quads,
|
||||
PrimitiveType.QuadStrip => PrimitiveTopology.QuadStrip,
|
||||
PrimitiveType.Polygon => PrimitiveTopology.Polygon,
|
||||
PrimitiveType.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
|
||||
PrimitiveType.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency,
|
||||
PrimitiveType.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
|
||||
PrimitiveType.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency,
|
||||
PrimitiveType.Patches => PrimitiveTopology.Patches,
|
||||
_ => PrimitiveTopology.Triangles
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the primitive type into something that can be used with the host API.
|
||||
/// </summary>
|
||||
/// <param name="type">The primitive type to convert</param>
|
||||
/// <returns>A host compatible enum value</returns>
|
||||
public static PrimitiveTopology Convert(this PrimitiveTypeOverride type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PrimitiveTypeOverride.Points => PrimitiveTopology.Points,
|
||||
PrimitiveTypeOverride.Lines => PrimitiveTopology.Lines,
|
||||
PrimitiveTypeOverride.LineStrip => PrimitiveTopology.LineStrip,
|
||||
PrimitiveTypeOverride.Triangles => PrimitiveTopology.Triangles,
|
||||
PrimitiveTypeOverride.TriangleStrip => PrimitiveTopology.TriangleStrip,
|
||||
PrimitiveTypeOverride.TriangleFan => PrimitiveTopology.TriangleFan,
|
||||
PrimitiveTypeOverride.LinesAdjacency => PrimitiveTopology.LinesAdjacency,
|
||||
PrimitiveTypeOverride.LineStripAdjacency => PrimitiveTopology.LineStripAdjacency,
|
||||
PrimitiveTypeOverride.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency,
|
||||
PrimitiveTypeOverride.TriangleStripAdjacency => PrimitiveTopology.TriangleStripAdjacency,
|
||||
PrimitiveTypeOverride.Patches => PrimitiveTopology.Patches,
|
||||
_ => PrimitiveTopology.Triangles
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs
Normal file
11
src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampler pool indexing mode.
|
||||
/// </summary>
|
||||
enum SamplerIndex
|
||||
{
|
||||
Independently = 0,
|
||||
ViaHeaderIndex = 1
|
||||
}
|
||||
}
|
||||
20
src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs
Normal file
20
src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Storage buffer address and size information.
|
||||
/// </summary>
|
||||
struct SbDescriptor
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint AddressLow;
|
||||
public uint AddressHigh;
|
||||
public int Size;
|
||||
public int Padding;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
public ulong PackAddress()
|
||||
{
|
||||
return AddressLow | ((ulong)AddressHigh << 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs
Normal file
42
src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Engine.Types
|
||||
{
|
||||
/// <summary>
|
||||
/// Depth-stencil texture format.
|
||||
/// </summary>
|
||||
enum ZetaFormat
|
||||
{
|
||||
D32Float = 0xa,
|
||||
D16Unorm = 0x13,
|
||||
D24UnormS8Uint = 0x14,
|
||||
D24Unorm = 0x15,
|
||||
S8UintD24Unorm = 0x16,
|
||||
S8Uint = 0x17,
|
||||
D32FloatS8Uint = 0x19
|
||||
}
|
||||
|
||||
static class ZetaFormatConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the depth-stencil texture format to a host compatible format.
|
||||
/// </summary>
|
||||
/// <param name="format">Depth-stencil format</param>
|
||||
/// <returns>Host compatible format enum value</returns>
|
||||
public static FormatInfo Convert(this ZetaFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
ZetaFormat.D32Float => new FormatInfo(Format.D32Float, 1, 1, 4, 1),
|
||||
ZetaFormat.D16Unorm => new FormatInfo(Format.D16Unorm, 1, 1, 2, 1),
|
||||
ZetaFormat.D24UnormS8Uint => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2),
|
||||
ZetaFormat.D24Unorm => new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 1),
|
||||
ZetaFormat.S8UintD24Unorm => new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2),
|
||||
ZetaFormat.S8Uint => new FormatInfo(Format.S8Uint, 1, 1, 1, 1),
|
||||
ZetaFormat.D32FloatS8Uint => new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2),
|
||||
_ => FormatInfo.Default
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/Ryujinx.Graphics.Gpu/GpuChannel.cs
Normal file
148
src/Ryujinx.Graphics.Gpu/GpuChannel.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a GPU channel.
|
||||
/// </summary>
|
||||
public class GpuChannel : IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GPFifoDevice _device;
|
||||
private readonly GPFifoProcessor _processor;
|
||||
private MemoryManager _memoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Channel buffer bindings manager.
|
||||
/// </summary>
|
||||
internal BufferManager BufferManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Channel texture bindings manager.
|
||||
/// </summary>
|
||||
internal TextureManager TextureManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Current channel memory manager.
|
||||
/// </summary>
|
||||
internal MemoryManager MemoryManager => _memoryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Host hardware capabilities from the GPU context.
|
||||
/// </summary>
|
||||
internal ref Capabilities Capabilities => ref _context.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a GPU channel.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the channel belongs to</param>
|
||||
internal GpuChannel(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
_device = context.GPFifo;
|
||||
_processor = new GPFifoProcessor(context, this);
|
||||
BufferManager = new BufferManager(context, this);
|
||||
TextureManager = new TextureManager(context, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds a memory manager to the channel.
|
||||
/// All submitted and in-flight commands will use the specified memory manager for any memory operations.
|
||||
/// </summary>
|
||||
/// <param name="memoryManager">The new memory manager to be bound</param>
|
||||
public void BindMemory(MemoryManager memoryManager)
|
||||
{
|
||||
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, memoryManager ?? throw new ArgumentNullException(nameof(memoryManager)));
|
||||
|
||||
memoryManager.Physical.IncrementReferenceCount();
|
||||
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||
}
|
||||
|
||||
memoryManager.Physical.BufferCache.NotifyBuffersModified += BufferManager.Rebind;
|
||||
memoryManager.MemoryUnmapped += MemoryUnmappedHandler;
|
||||
|
||||
// Since the memory manager changed, make sure we will get pools from addresses of the new memory manager.
|
||||
TextureManager.ReloadPools();
|
||||
memoryManager.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Memory mappings change event handler.
|
||||
/// </summary>
|
||||
/// <param name="sender">Memory manager where the mappings changed</param>
|
||||
/// <param name="e">Information about the region that is being changed</param>
|
||||
private void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
|
||||
{
|
||||
TextureManager.ReloadPools();
|
||||
|
||||
var memoryManager = Volatile.Read(ref _memoryManager);
|
||||
memoryManager?.Physical.BufferCache.QueuePrune();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes data directly to the state of the specified class.
|
||||
/// </summary>
|
||||
/// <param name="classId">ID of the class to write the data into</param>
|
||||
/// <param name="offset">State offset in bytes</param>
|
||||
/// <param name="value">Value to be written</param>
|
||||
public void Write(ClassId classId, int offset, uint value)
|
||||
{
|
||||
_processor.Write(classId, offset, (int)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push a GPFIFO entry in the form of a prefetched command buffer.
|
||||
/// It is intended to be used by nvservices to handle special cases.
|
||||
/// </summary>
|
||||
/// <param name="commandBuffer">The command buffer containing the prefetched commands</param>
|
||||
public void PushHostCommandBuffer(int[] commandBuffer)
|
||||
{
|
||||
_device.PushHostCommandBuffer(_processor, commandBuffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes GPFIFO entries.
|
||||
/// </summary>
|
||||
/// <param name="entries">GPFIFO entries</param>
|
||||
public void PushEntries(ReadOnlySpan<ulong> entries)
|
||||
{
|
||||
_device.PushEntries(_processor, entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the GPU channel.
|
||||
/// It's an error to use the GPU channel after disposal.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_context.DeferredActions.Enqueue(Destroy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs disposal of the host GPU resources used by this channel, that are not shared.
|
||||
/// This must only be called from the render thread.
|
||||
/// </summary>
|
||||
private void Destroy()
|
||||
{
|
||||
TextureManager.Dispose();
|
||||
|
||||
var oldMemoryManager = Interlocked.Exchange(ref _memoryManager, null);
|
||||
if (oldMemoryManager != null)
|
||||
{
|
||||
oldMemoryManager.Physical.BufferCache.NotifyBuffersModified -= BufferManager.Rebind;
|
||||
oldMemoryManager.Physical.DecrementReferenceCount();
|
||||
oldMemoryManager.MemoryUnmapped -= MemoryUnmappedHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
397
src/Ryujinx.Graphics.Gpu/GpuContext.cs
Normal file
397
src/Ryujinx.Graphics.Gpu/GpuContext.cs
Normal file
@@ -0,0 +1,397 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Gpu.Synchronization;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
/// <summary>
|
||||
/// GPU emulation context.
|
||||
/// </summary>
|
||||
public sealed class GpuContext : IDisposable
|
||||
{
|
||||
private const int NsToTicksFractionNumerator = 384;
|
||||
private const int NsToTicksFractionDenominator = 625;
|
||||
|
||||
/// <summary>
|
||||
/// Event signaled when the host emulation context is ready to be used by the gpu context.
|
||||
/// </summary>
|
||||
public ManualResetEvent HostInitalized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Host renderer.
|
||||
/// </summary>
|
||||
public IRenderer Renderer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU General Purpose FIFO queue.
|
||||
/// </summary>
|
||||
public GPFifoDevice GPFifo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GPU synchronization manager.
|
||||
/// </summary>
|
||||
public SynchronizationManager Synchronization { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Presentation window.
|
||||
/// </summary>
|
||||
public Window Window { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal sequence number, used to avoid needless resource data updates
|
||||
/// in the middle of a command buffer before synchronizations.
|
||||
/// </summary>
|
||||
internal int SequenceNumber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal sync number, used to denote points at which host synchronization can be requested.
|
||||
/// </summary>
|
||||
internal ulong SyncNumber { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Actions to be performed when a CPU waiting syncpoint or barrier is triggered.
|
||||
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
||||
/// and the SyncNumber will be incremented.
|
||||
/// </summary>
|
||||
internal List<Action> SyncActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Actions to be performed when a CPU waiting syncpoint is triggered.
|
||||
/// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
|
||||
/// and the SyncNumber will be incremented.
|
||||
/// </summary>
|
||||
internal List<Action> SyncpointActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
|
||||
/// copies have completed on the GPU, and their data can be freed.
|
||||
/// </summary>
|
||||
internal List<BufferMigration> BufferMigrations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue with deferred actions that must run on the render thread.
|
||||
/// </summary>
|
||||
internal Queue<Action> DeferredActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Registry with physical memories that can be used with this GPU context, keyed by owner process ID.
|
||||
/// </summary>
|
||||
internal ConcurrentDictionary<ulong, PhysicalMemory> PhysicalMemoryRegistry { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Host hardware capabilities.
|
||||
/// </summary>
|
||||
internal Capabilities Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Event for signalling shader cache loading progress.
|
||||
/// </summary>
|
||||
public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
|
||||
|
||||
private Thread _gpuThread;
|
||||
private bool _pendingSync;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU emulation context.
|
||||
/// </summary>
|
||||
/// <param name="renderer">Host renderer</param>
|
||||
public GpuContext(IRenderer renderer)
|
||||
{
|
||||
Renderer = renderer;
|
||||
|
||||
GPFifo = new GPFifoDevice(this);
|
||||
|
||||
Synchronization = new SynchronizationManager();
|
||||
|
||||
Window = new Window(this);
|
||||
|
||||
HostInitalized = new ManualResetEvent(false);
|
||||
|
||||
SyncActions = new List<Action>();
|
||||
SyncpointActions = new List<Action>();
|
||||
BufferMigrations = new List<BufferMigration>();
|
||||
|
||||
DeferredActions = new Queue<Action>();
|
||||
|
||||
PhysicalMemoryRegistry = new ConcurrentDictionary<ulong, PhysicalMemory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU channel.
|
||||
/// </summary>
|
||||
/// <returns>The GPU channel</returns>
|
||||
public GpuChannel CreateChannel()
|
||||
{
|
||||
return new GpuChannel(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU memory manager.
|
||||
/// </summary>
|
||||
/// <param name="pid">ID of the process that owns the memory manager</param>
|
||||
/// <returns>The memory manager</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception>
|
||||
public MemoryManager CreateMemoryManager(ulong pid)
|
||||
{
|
||||
if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
|
||||
{
|
||||
throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
|
||||
}
|
||||
|
||||
return new MemoryManager(physicalMemory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers virtual memory used by a process for GPU memory access, caching and read/write tracking.
|
||||
/// </summary>
|
||||
/// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
|
||||
/// <param name="cpuMemory">Virtual memory owned by the process</param>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
|
||||
public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
|
||||
{
|
||||
var physicalMemory = new PhysicalMemory(this, cpuMemory);
|
||||
if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
|
||||
{
|
||||
throw new ArgumentException("The PID was already registered", nameof(pid));
|
||||
}
|
||||
|
||||
physicalMemory.ShaderCache.ShaderCacheStateChanged += ShaderCacheStateUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a process, indicating that its memory will no longer be used, and that caches can be freed.
|
||||
/// </summary>
|
||||
/// <param name="pid">ID of the process</param>
|
||||
public void UnregisterProcess(ulong pid)
|
||||
{
|
||||
if (PhysicalMemoryRegistry.TryRemove(pid, out var physicalMemory))
|
||||
{
|
||||
physicalMemory.ShaderCache.ShaderCacheStateChanged -= ShaderCacheStateUpdate;
|
||||
physicalMemory.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a nanoseconds timestamp value to Maxwell time ticks.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The frequency is 614400000 Hz.
|
||||
/// </remarks>
|
||||
/// <param name="nanoseconds">Timestamp in nanoseconds</param>
|
||||
/// <returns>Maxwell ticks</returns>
|
||||
private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
|
||||
{
|
||||
// We need to divide first to avoid overflows.
|
||||
// We fix up the result later by calculating the difference and adding
|
||||
// that to the result.
|
||||
ulong divided = nanoseconds / NsToTicksFractionDenominator;
|
||||
|
||||
ulong rounded = divided * NsToTicksFractionDenominator;
|
||||
|
||||
ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator;
|
||||
|
||||
return divided * NsToTicksFractionNumerator + errorBias;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the GPU timer.
|
||||
/// </summary>
|
||||
/// <returns>The current GPU timestamp</returns>
|
||||
public ulong GetTimestamp()
|
||||
{
|
||||
ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
|
||||
|
||||
if (GraphicsConfig.FastGpuTime)
|
||||
{
|
||||
// Divide by some amount to report time as if operations were performed faster than they really are.
|
||||
// This can prevent some games from switching to a lower resolution because rendering is too slow.
|
||||
ticks /= 256;
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shader cache state update handler.
|
||||
/// </summary>
|
||||
/// <param name="state">Current state of the shader cache load process</param>
|
||||
/// <param name="current">Number of the current shader being processed</param>
|
||||
/// <param name="total">Total number of shaders to process</param>
|
||||
private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
|
||||
{
|
||||
ShaderCacheStateChanged?.Invoke(state, current, total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the GPU shader cache.
|
||||
/// </summary>
|
||||
public void InitializeShaderCache(CancellationToken cancellationToken)
|
||||
{
|
||||
HostInitalized.WaitOne();
|
||||
|
||||
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
|
||||
{
|
||||
physicalMemory.ShaderCache.Initialize(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current thread as the main GPU thread.
|
||||
/// </summary>
|
||||
public void SetGpuThread()
|
||||
{
|
||||
_gpuThread = Thread.CurrentThread;
|
||||
|
||||
Capabilities = Renderer.GetCapabilities();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the current thread is the GPU thread.
|
||||
/// </summary>
|
||||
/// <returns>True if the thread is the GPU thread, false otherwise</returns>
|
||||
public bool IsGpuThread()
|
||||
{
|
||||
return _gpuThread == Thread.CurrentThread;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the queue of shaders that must save their binaries to the disk cache.
|
||||
/// </summary>
|
||||
public void ProcessShaderCacheQueue()
|
||||
{
|
||||
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
|
||||
{
|
||||
physicalMemory.ShaderCache.ProcessShaderCacheQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances internal sequence number.
|
||||
/// This forces the update of any modified GPU resource.
|
||||
/// </summary>
|
||||
internal void AdvanceSequence()
|
||||
{
|
||||
SequenceNumber++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases,
|
||||
/// and the migration copy has completed.
|
||||
/// </summary>
|
||||
/// <param name="migration">The buffer migration</param>
|
||||
internal void RegisterBufferMigration(BufferMigration migration)
|
||||
{
|
||||
BufferMigrations.Add(migration);
|
||||
_pendingSync = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an action to be performed the next time a syncpoint is incremented.
|
||||
/// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
|
||||
/// </summary>
|
||||
/// <param name="action">The action to be performed on sync object creation</param>
|
||||
/// <param name="syncpointOnly">True if the sync action should only run when syncpoints are incremented</param>
|
||||
public void RegisterSyncAction(Action action, bool syncpointOnly = false)
|
||||
{
|
||||
if (syncpointOnly)
|
||||
{
|
||||
SyncpointActions.Add(action);
|
||||
}
|
||||
else
|
||||
{
|
||||
SyncActions.Add(action);
|
||||
_pendingSync = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a host sync object if there are any pending sync actions. The actions will then be called.
|
||||
/// If no actions are present, a host sync object is not created.
|
||||
/// </summary>
|
||||
/// <param name="syncpoint">True if host sync is being created by a syncpoint</param>
|
||||
/// <param name="strict">True if the sync should signal as soon as possible</param>
|
||||
public void CreateHostSyncIfNeeded(bool syncpoint, bool strict)
|
||||
{
|
||||
if (BufferMigrations.Count > 0)
|
||||
{
|
||||
ulong currentSyncNumber = Renderer.GetCurrentSync();
|
||||
|
||||
for (int i = 0; i < BufferMigrations.Count; i++)
|
||||
{
|
||||
BufferMigration migration = BufferMigrations[i];
|
||||
long diff = (long)(currentSyncNumber - migration.SyncNumber);
|
||||
|
||||
if (diff >= 0)
|
||||
{
|
||||
migration.Dispose();
|
||||
BufferMigrations.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingSync || (syncpoint && SyncpointActions.Count > 0))
|
||||
{
|
||||
Renderer.CreateSync(SyncNumber, strict);
|
||||
|
||||
SyncNumber++;
|
||||
|
||||
foreach (Action action in SyncActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
foreach (Action action in SyncpointActions)
|
||||
{
|
||||
action();
|
||||
}
|
||||
|
||||
SyncActions.Clear();
|
||||
SyncpointActions.Clear();
|
||||
}
|
||||
|
||||
_pendingSync = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs deferred actions.
|
||||
/// This is useful for actions that must run on the render thread, such as resource disposal.
|
||||
/// </summary>
|
||||
internal void RunDeferredActions()
|
||||
{
|
||||
while (DeferredActions.TryDequeue(out Action action))
|
||||
{
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes all GPU resources currently cached.
|
||||
/// It's an error to push any GPU commands after disposal.
|
||||
/// Additionally, the GPU commands FIFO must be empty for disposal,
|
||||
/// and processing of all commands must have finished.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Renderer.Dispose();
|
||||
GPFifo.Dispose();
|
||||
HostInitalized.Dispose();
|
||||
|
||||
// Has to be disposed before processing deferred actions, as it will produce some.
|
||||
foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
|
||||
{
|
||||
physicalMemory.Dispose();
|
||||
}
|
||||
|
||||
PhysicalMemoryRegistry.Clear();
|
||||
|
||||
RunDeferredActions();
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
Normal file
70
src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace Ryujinx.Graphics.Gpu
|
||||
{
|
||||
/// <summary>
|
||||
/// General GPU and graphics configuration.
|
||||
/// </summary>
|
||||
public static class GraphicsConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolution scale.
|
||||
/// </summary>
|
||||
public static float ResScale = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Max Anisotropy. Values range from 0 - 16. Set to -1 to let the game decide.
|
||||
/// </summary>
|
||||
public static float MaxAnisotropy = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Base directory used to write shader code dumps.
|
||||
/// Set to null to disable code dumping.
|
||||
/// </summary>
|
||||
public static string ShadersDumpPath;
|
||||
|
||||
/// <summary>
|
||||
/// Fast GPU time calculates the internal GPU time ticks as if the GPU was capable of
|
||||
/// processing commands almost instantly, instead of using the host timer.
|
||||
/// This can avoid lower resolution on some games when GPU performance is poor.
|
||||
/// </summary>
|
||||
public static bool FastGpuTime = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables fast 2d engine texture copies entirely on CPU when possible.
|
||||
/// Reduces stuttering and # of textures in games that copy textures around for streaming,
|
||||
/// as textures will not need to be created for the copy, and the data does not need to be
|
||||
/// flushed from GPU.
|
||||
/// </summary>
|
||||
public static bool Fast2DCopy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the Just-in-Time compiler for GPU Macro code.
|
||||
/// </summary>
|
||||
public static bool EnableMacroJit = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables high-level emulation of common GPU Macro code.
|
||||
/// </summary>
|
||||
public static bool EnableMacroHLE = true;
|
||||
|
||||
/// <summary>
|
||||
/// Title id of the current running game.
|
||||
/// Used by the shader cache.
|
||||
/// </summary>
|
||||
public static string TitleId;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the shader cache.
|
||||
/// </summary>
|
||||
public static bool EnableShaderCache;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables shader SPIR-V compilation.
|
||||
/// </summary>
|
||||
public static bool EnableSpirvCompilationOnVulkan = true;
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables recompression of compressed textures that are not natively supported by the host.
|
||||
/// </summary>
|
||||
public static bool EnableTextureRecompression = false;
|
||||
}
|
||||
}
|
||||
256
src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Normal file
256
src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// An entry on the short duration texture cache.
|
||||
/// </summary>
|
||||
class ShortTextureCacheEntry
|
||||
{
|
||||
public readonly TextureDescriptor Descriptor;
|
||||
public readonly int InvalidatedSequence;
|
||||
public readonly Texture Texture;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new entry on the short duration texture cache.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">Last descriptor that referenced the texture</param>
|
||||
/// <param name="texture">The texture</param>
|
||||
public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture)
|
||||
{
|
||||
Descriptor = descriptor;
|
||||
InvalidatedSequence = texture.InvalidatedSequence;
|
||||
Texture = texture;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A texture cache that automatically removes older textures that are not used for some time.
|
||||
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
||||
/// old ones at the bottom of the list are deleted.
|
||||
/// </summary>
|
||||
class AutoDeleteCache : IEnumerable<Texture>
|
||||
{
|
||||
private const int MinCountForDeletion = 32;
|
||||
private const int MaxCapacity = 2048;
|
||||
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
|
||||
|
||||
private readonly LinkedList<Texture> _textures;
|
||||
private ulong _totalSize;
|
||||
|
||||
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
|
||||
private HashSet<ShortTextureCacheEntry> _shortCache;
|
||||
|
||||
private Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the automatic deletion cache.
|
||||
/// </summary>
|
||||
public AutoDeleteCache()
|
||||
{
|
||||
_textures = new LinkedList<Texture>();
|
||||
|
||||
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
|
||||
_shortCache = new HashSet<ShortTextureCacheEntry>();
|
||||
|
||||
_shortCacheLookup = new Dictionary<TextureDescriptor, ShortTextureCacheEntry>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new texture to the cache, even if the texture added is already on the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Using this method is only recommended if you know that the texture is not yet on the cache,
|
||||
/// otherwise it would store the same texture more than once.
|
||||
/// </remarks>
|
||||
/// <param name="texture">The texture to be added to the cache</param>
|
||||
public void Add(Texture texture)
|
||||
{
|
||||
_totalSize += texture.Size;
|
||||
|
||||
texture.IncrementReferenceCount();
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
|
||||
if (_textures.Count > MaxCapacity ||
|
||||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
|
||||
{
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new texture to the cache, or just moves it to the top of the list if the
|
||||
/// texture is already on the cache.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Moving the texture to the top of the list prevents it from being deleted,
|
||||
/// as the textures on the bottom of the list are deleted when new ones are added.
|
||||
/// </remarks>
|
||||
/// <param name="texture">The texture to be added, or moved to the top</param>
|
||||
public void Lift(Texture texture)
|
||||
{
|
||||
if (texture.CacheNode != null)
|
||||
{
|
||||
if (texture.CacheNode != _textures.Last)
|
||||
{
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
texture.CacheNode = _textures.AddLast(texture);
|
||||
}
|
||||
|
||||
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
|
||||
{
|
||||
RemoveLeastUsedTexture();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(texture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the least used texture from the cache.
|
||||
/// </summary>
|
||||
private void RemoveLeastUsedTexture()
|
||||
{
|
||||
Texture oldestTexture = _textures.First.Value;
|
||||
|
||||
_totalSize -= oldestTexture.Size;
|
||||
|
||||
if (!oldestTexture.CheckModified(false))
|
||||
{
|
||||
// The texture must be flushed if it falls out of the auto delete cache.
|
||||
// Flushes out of the auto delete cache do not trigger write tracking,
|
||||
// as it is expected that other overlapping textures exist that have more up-to-date contents.
|
||||
|
||||
oldestTexture.Group.SynchronizeDependents(oldestTexture);
|
||||
oldestTexture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.RemoveFirst();
|
||||
|
||||
oldestTexture.DecrementReferenceCount();
|
||||
oldestTexture.CacheNode = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a texture from the cache.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to be removed from the cache</param>
|
||||
/// <param name="flush">True to remove the texture if it was on the cache</param>
|
||||
/// <returns>True if the texture was found and removed, false otherwise</returns>
|
||||
public bool Remove(Texture texture, bool flush)
|
||||
{
|
||||
if (texture.CacheNode == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove our reference to this texture.
|
||||
if (flush)
|
||||
{
|
||||
texture.FlushModified(false);
|
||||
}
|
||||
|
||||
_textures.Remove(texture.CacheNode);
|
||||
|
||||
_totalSize -= texture.Size;
|
||||
|
||||
texture.CacheNode = null;
|
||||
|
||||
return texture.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to find a texture on the short duration cache.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The texture descriptor</param>
|
||||
/// <returns>The texture if found, null otherwise</returns>
|
||||
public Texture FindShortCache(in TextureDescriptor descriptor)
|
||||
{
|
||||
if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry))
|
||||
{
|
||||
if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence)
|
||||
{
|
||||
return entry.Texture;
|
||||
}
|
||||
else
|
||||
{
|
||||
_shortCacheLookup.Remove(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a texture from the short duration cache.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to remove from the short cache</param>
|
||||
public void RemoveShortCache(Texture texture)
|
||||
{
|
||||
bool removed = _shortCache.Remove(texture.ShortCacheEntry);
|
||||
removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry);
|
||||
|
||||
if (removed)
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
|
||||
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
|
||||
texture.ShortCacheEntry = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a texture to the short duration cache.
|
||||
/// It starts in the builder set, and it is moved into the deletion set on next process.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to add to the short cache</param>
|
||||
/// <param name="descriptor">Last used texture descriptor</param>
|
||||
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
|
||||
{
|
||||
var entry = new ShortTextureCacheEntry(descriptor, texture);
|
||||
|
||||
_shortCacheBuilder.Add(entry);
|
||||
_shortCacheLookup.Add(entry.Descriptor, entry);
|
||||
|
||||
texture.ShortCacheEntry = entry;
|
||||
|
||||
texture.IncrementReferenceCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete textures from the short duration cache.
|
||||
/// Moves the builder set to be deleted on next process.
|
||||
/// </summary>
|
||||
public void ProcessShortCache()
|
||||
{
|
||||
HashSet<ShortTextureCacheEntry> toRemove = _shortCache;
|
||||
|
||||
foreach (var entry in toRemove)
|
||||
{
|
||||
entry.Texture.DecrementReferenceCount();
|
||||
|
||||
_shortCacheLookup.Remove(entry.Descriptor);
|
||||
entry.Texture.ShortCacheEntry = null;
|
||||
}
|
||||
|
||||
toRemove.Clear();
|
||||
_shortCache = _shortCacheBuilder;
|
||||
_shortCacheBuilder = toRemove;
|
||||
}
|
||||
|
||||
public IEnumerator<Texture> GetEnumerator()
|
||||
{
|
||||
return _textures.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _textures.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
Normal file
72
src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents texture format information.
|
||||
/// </summary>
|
||||
readonly struct FormatInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// A default, generic RGBA8 texture format.
|
||||
/// </summary>
|
||||
public static FormatInfo Default { get; } = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
|
||||
/// <summary>
|
||||
/// The format of the texture data.
|
||||
/// </summary>
|
||||
public Format Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The block width for compressed formats.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must be 1 for non-compressed formats.
|
||||
/// </remarks>
|
||||
public int BlockWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The block height for compressed formats.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Must be 1 for non-compressed formats.
|
||||
/// </remarks>
|
||||
public int BlockHeight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes occupied by a single pixel in memory of the texture data.
|
||||
/// </summary>
|
||||
public int BytesPerPixel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of components this format has defined (in RGBA order).
|
||||
/// </summary>
|
||||
public int Components { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whenever or not the texture format is a compressed format. Determined from block size.
|
||||
/// </summary>
|
||||
public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the texture format info structure.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of the texture data</param>
|
||||
/// <param name="blockWidth">The block width for compressed formats. Must be 1 for non-compressed formats</param>
|
||||
/// <param name="blockHeight">The block height for compressed formats. Must be 1 for non-compressed formats</param>
|
||||
/// <param name="bytesPerPixel">The number of bytes occupied by a single pixel in memory of the texture data</param>
|
||||
public FormatInfo(
|
||||
Format format,
|
||||
int blockWidth,
|
||||
int blockHeight,
|
||||
int bytesPerPixel,
|
||||
int components)
|
||||
{
|
||||
Format = format;
|
||||
BlockWidth = blockWidth;
|
||||
BlockHeight = blockHeight;
|
||||
BytesPerPixel = bytesPerPixel;
|
||||
Components = components;
|
||||
}
|
||||
}
|
||||
}
|
||||
578
src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
Normal file
578
src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
Normal file
@@ -0,0 +1,578 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains format tables, for texture and vertex attribute formats.
|
||||
/// </summary>
|
||||
static class FormatTable
|
||||
{
|
||||
private enum TextureFormat : uint
|
||||
{
|
||||
// Formats
|
||||
R32G32B32A32 = 0x01,
|
||||
R32G32B32 = 0x02,
|
||||
R16G16B16A16 = 0x03,
|
||||
R32G32 = 0x04,
|
||||
R32B24G8 = 0x05,
|
||||
X8B8G8R8 = 0x07,
|
||||
A8B8G8R8 = 0x08,
|
||||
A2B10G10R10 = 0x09,
|
||||
R16G16 = 0x0c,
|
||||
G8R24 = 0x0d,
|
||||
G24R8 = 0x0e,
|
||||
R32 = 0x0f,
|
||||
A4B4G4R4 = 0x12,
|
||||
A5B5G5R1 = 0x13,
|
||||
A1B5G5R5 = 0x14,
|
||||
B5G6R5 = 0x15,
|
||||
B6G5R5 = 0x16,
|
||||
G8R8 = 0x18,
|
||||
R16 = 0x1b,
|
||||
Y8Video = 0x1c,
|
||||
R8 = 0x1d,
|
||||
G4R4 = 0x1e,
|
||||
R1 = 0x1f,
|
||||
E5B9G9R9SharedExp = 0x20,
|
||||
Bf10Gf11Rf11 = 0x21,
|
||||
G8B8G8R8 = 0x22,
|
||||
B8G8R8G8 = 0x23,
|
||||
Bc1 = 0x24,
|
||||
Bc2 = 0x25,
|
||||
Bc3 = 0x26,
|
||||
Bc4 = 0x27,
|
||||
Bc5 = 0x28,
|
||||
Bc6HSf16 = 0x10,
|
||||
Bc6HUf16 = 0x11,
|
||||
Bc7U = 0x17,
|
||||
Etc2Rgb = 0x06,
|
||||
Etc2RgbPta = 0x0a,
|
||||
Etc2Rgba = 0x0b,
|
||||
Eac = 0x19,
|
||||
Eacx2 = 0x1a,
|
||||
Z24S8 = 0x29,
|
||||
X8Z24 = 0x2a,
|
||||
S8Z24 = 0x2b,
|
||||
X4V4Z24Cov4R4V = 0x2c,
|
||||
X4V4Z24Cov8R8V = 0x2d,
|
||||
V8Z24Cov4R12V = 0x2e,
|
||||
Zf32 = 0x2f,
|
||||
Zf32X24S8 = 0x30,
|
||||
X8Z24X20V4S8Cov4R4V = 0x31,
|
||||
X8Z24X20V4S8Cov8R8V = 0x32,
|
||||
Zf32X20V4X8Cov4R4V = 0x33,
|
||||
Zf32X20V4X8Cov8R8V = 0x34,
|
||||
Zf32X20V4S8Cov4R4V = 0x35,
|
||||
Zf32X20V4S8Cov8R8V = 0x36,
|
||||
X8Z24X16V8S8Cov4R12V = 0x37,
|
||||
Zf32X16V8X8Cov4R12V = 0x38,
|
||||
Zf32X16V8S8Cov4R12V = 0x39,
|
||||
Z16 = 0x3a,
|
||||
V8Z24Cov8R24V = 0x3b,
|
||||
X8Z24X16V8S8Cov8R24V = 0x3c,
|
||||
Zf32X16V8X8Cov8R24V = 0x3d,
|
||||
Zf32X16V8S8Cov8R24V = 0x3e,
|
||||
Astc2D4x4 = 0x40,
|
||||
Astc2D5x4 = 0x50,
|
||||
Astc2D5x5 = 0x41,
|
||||
Astc2D6x5 = 0x51,
|
||||
Astc2D6x6 = 0x42,
|
||||
Astc2D8x5 = 0x55,
|
||||
Astc2D8x6 = 0x52,
|
||||
Astc2D8x8 = 0x44,
|
||||
Astc2D10x5 = 0x56,
|
||||
Astc2D10x6 = 0x57,
|
||||
Astc2D10x8 = 0x53,
|
||||
Astc2D10x10 = 0x45,
|
||||
Astc2D12x10 = 0x54,
|
||||
Astc2D12x12 = 0x46,
|
||||
|
||||
// Types
|
||||
Snorm = 0x1,
|
||||
Unorm = 0x2,
|
||||
Sint = 0x3,
|
||||
Uint = 0x4,
|
||||
SnormForceFp16 = 0x5,
|
||||
UnormForceFp16 = 0x6,
|
||||
Float = 0x7,
|
||||
|
||||
// Component Types
|
||||
RSnorm = Snorm << 7,
|
||||
GSnorm = Snorm << 10,
|
||||
BSnorm = Snorm << 13,
|
||||
ASnorm = Snorm << 16,
|
||||
|
||||
RUnorm = Unorm << 7,
|
||||
GUnorm = Unorm << 10,
|
||||
BUnorm = Unorm << 13,
|
||||
AUnorm = Unorm << 16,
|
||||
|
||||
RSint = Sint << 7,
|
||||
GSint = Sint << 10,
|
||||
BSint = Sint << 13,
|
||||
ASint = Sint << 16,
|
||||
|
||||
RUint = Uint << 7,
|
||||
GUint = Uint << 10,
|
||||
BUint = Uint << 13,
|
||||
AUint = Uint << 16,
|
||||
|
||||
RSnormForceFp16 = SnormForceFp16 << 7,
|
||||
GSnormForceFp16 = SnormForceFp16 << 10,
|
||||
BSnormForceFp16 = SnormForceFp16 << 13,
|
||||
ASnormForceFp16 = SnormForceFp16 << 16,
|
||||
|
||||
RUnormForceFp16 = UnormForceFp16 << 7,
|
||||
GUnormForceFp16 = UnormForceFp16 << 10,
|
||||
BUnormForceFp16 = UnormForceFp16 << 13,
|
||||
AUnormForceFp16 = UnormForceFp16 << 16,
|
||||
|
||||
RFloat = Float << 7,
|
||||
GFloat = Float << 10,
|
||||
BFloat = Float << 13,
|
||||
AFloat = Float << 16,
|
||||
|
||||
Srgb = 0x1 << 19, // Custom encoding
|
||||
|
||||
// Combinations
|
||||
R8Unorm = R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491d
|
||||
R8Snorm = R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249d
|
||||
R8Uint = R8 | RUint | GUint | BUint | AUint, // 0x4921d
|
||||
R8Sint = R8 | RSint | GSint | BSint | ASint, // 0x36d9d
|
||||
R16Float = R16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff9b
|
||||
R16Unorm = R16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491b
|
||||
R16Snorm = R16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1249b
|
||||
R16Uint = R16 | RUint | GUint | BUint | AUint, // 0x4921b
|
||||
R16Sint = R16 | RSint | GSint | BSint | ASint, // 0x36d9b
|
||||
R32Float = R32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8f
|
||||
R32Uint = R32 | RUint | GUint | BUint | AUint, // 0x4920f
|
||||
R32Sint = R32 | RSint | GSint | BSint | ASint, // 0x36d8f
|
||||
G8R8Unorm = G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24918
|
||||
G8R8Snorm = G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12498
|
||||
G8R8Uint = G8R8 | RUint | GUint | BUint | AUint, // 0x49218
|
||||
G8R8Sint = G8R8 | RSint | GSint | BSint | ASint, // 0x36d98
|
||||
R16G16Float = R16G16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff8c
|
||||
R16G16Unorm = R16G16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490c
|
||||
R16G16Snorm = R16G16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x1248c
|
||||
R16G16Uint = R16G16 | RUint | GUint | BUint | AUint, // 0x4920c
|
||||
R16G16Sint = R16G16 | RSint | GSint | BSint | ASint, // 0x36d8c
|
||||
R32G32Float = R32G32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff84
|
||||
R32G32Uint = R32G32 | RUint | GUint | BUint | AUint, // 0x49204
|
||||
R32G32Sint = R32G32 | RSint | GSint | BSint | ASint, // 0x36d84
|
||||
R32G32B32Float = R32G32B32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff82
|
||||
R32G32B32Uint = R32G32B32 | RUint | GUint | BUint | AUint, // 0x49202
|
||||
R32G32B32Sint = R32G32B32 | RSint | GSint | BSint | ASint, // 0x36d82
|
||||
A8B8G8R8Unorm = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24908
|
||||
A8B8G8R8Snorm = A8B8G8R8 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12488
|
||||
A8B8G8R8Uint = A8B8G8R8 | RUint | GUint | BUint | AUint, // 0x49208
|
||||
A8B8G8R8Sint = A8B8G8R8 | RSint | GSint | BSint | ASint, // 0x36d88
|
||||
R16G16B16A16Float = R16G16B16A16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff83
|
||||
R16G16B16A16Unorm = R16G16B16A16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24903
|
||||
R16G16B16A16Snorm = R16G16B16A16 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x12483
|
||||
R16G16B16A16Uint = R16G16B16A16 | RUint | GUint | BUint | AUint, // 0x49203
|
||||
R16G16B16A16Sint = R16G16B16A16 | RSint | GSint | BSint | ASint, // 0x36d83
|
||||
R32G32B32A32Float = R32G32B32A32 | RFloat | GFloat | BFloat | AFloat, // 0x7ff81
|
||||
R32G32B32A32Uint = R32G32B32A32 | RUint | GUint | BUint | AUint, // 0x49201
|
||||
R32G32B32A32Sint = R32G32B32A32 | RSint | GSint | BSint | ASint, // 0x36d81
|
||||
Z16Unorm = Z16 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2493a
|
||||
Zf32RFloatGUintBUintAUint = Zf32 | RFloat | GUint | BUint | AUint, // 0x493af
|
||||
Zf32Float = Zf32 | RFloat | GFloat | BFloat | AFloat, // 0x7ffaf
|
||||
G24R8RUintGUnormBUnormAUnorm = G24R8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a0e
|
||||
Z24S8RUintGUnormBUnormAUnorm = Z24S8 | RUint | GUnorm | BUnorm | AUnorm, // 0x24a29
|
||||
Z24S8RUintGUnormBUintAUint = Z24S8 | RUint | GUnorm | BUint | AUint, // 0x48a29
|
||||
S8Z24RUnormGUintBUintAUint = S8Z24 | RUnorm | GUint | BUint | AUint, // 0x4912b
|
||||
R32B24G8RFloatGUintBUnormAUnorm = R32B24G8 | RFloat | GUint | BUnorm | AUnorm, // 0x25385
|
||||
Zf32X24S8RFloatGUintBUnormAUnorm = Zf32X24S8 | RFloat | GUint | BUnorm | AUnorm, // 0x253b0
|
||||
A8B8G8R8UnormSrgb = A8B8G8R8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4908
|
||||
G4R4Unorm = G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2491e
|
||||
A4B4G4R4Unorm = A4B4G4R4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24912
|
||||
A1B5G5R5Unorm = A1B5G5R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24914
|
||||
B5G6R5Unorm = B5G6R5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24915
|
||||
A2B10G10R10Unorm = A2B10G10R10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24909
|
||||
A2B10G10R10Uint = A2B10G10R10 | RUint | GUint | BUint | AUint, // 0x49209
|
||||
Bf10Gf11Rf11Float = Bf10Gf11Rf11 | RFloat | GFloat | BFloat | AFloat, // 0x7ffa1
|
||||
E5B9G9R9SharedExpFloat = E5B9G9R9SharedExp | RFloat | GFloat | BFloat | AFloat, // 0x7ffa0
|
||||
Bc1Unorm = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24924
|
||||
Bc2Unorm = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24925
|
||||
Bc3Unorm = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24926
|
||||
Bc1UnormSrgb = Bc1 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4924
|
||||
Bc2UnormSrgb = Bc2 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4925
|
||||
Bc3UnormSrgb = Bc3 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4926
|
||||
Bc4Unorm = Bc4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24927
|
||||
Bc4Snorm = Bc4 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a7
|
||||
Bc5Unorm = Bc5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24928
|
||||
Bc5Snorm = Bc5 | RSnorm | GSnorm | BSnorm | ASnorm, // 0x124a8
|
||||
Bc7UUnorm = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24917
|
||||
Bc7UUnormSrgb = Bc7U | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4917
|
||||
Bc6HSf16Float = Bc6HSf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff90
|
||||
Bc6HUf16Float = Bc6HUf16 | RFloat | GFloat | BFloat | AFloat, // 0x7ff91
|
||||
Etc2RgbUnorm = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24906
|
||||
Etc2RgbPtaUnorm = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490a
|
||||
Etc2RgbaUnorm = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm, // 0x2490b
|
||||
Etc2RgbUnormSrgb = Etc2Rgb | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4906
|
||||
Etc2RgbPtaUnormSrgb = Etc2RgbPta | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490a
|
||||
Etc2RgbaUnormSrgb = Etc2Rgba | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa490b
|
||||
Astc2D4x4Unorm = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24940
|
||||
Astc2D5x4Unorm = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24950
|
||||
Astc2D5x5Unorm = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24941
|
||||
Astc2D6x5Unorm = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24951
|
||||
Astc2D6x6Unorm = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24942
|
||||
Astc2D8x5Unorm = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24955
|
||||
Astc2D8x6Unorm = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24952
|
||||
Astc2D8x8Unorm = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24944
|
||||
Astc2D10x5Unorm = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24956
|
||||
Astc2D10x6Unorm = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24957
|
||||
Astc2D10x8Unorm = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24953
|
||||
Astc2D10x10Unorm = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24945
|
||||
Astc2D12x10Unorm = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24954
|
||||
Astc2D12x12Unorm = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24946
|
||||
Astc2D4x4UnormSrgb = Astc2D4x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4940
|
||||
Astc2D5x4UnormSrgb = Astc2D5x4 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4950
|
||||
Astc2D5x5UnormSrgb = Astc2D5x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4941
|
||||
Astc2D6x5UnormSrgb = Astc2D6x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4951
|
||||
Astc2D6x6UnormSrgb = Astc2D6x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4942
|
||||
Astc2D8x5UnormSrgb = Astc2D8x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4955
|
||||
Astc2D8x6UnormSrgb = Astc2D8x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4952
|
||||
Astc2D8x8UnormSrgb = Astc2D8x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4944
|
||||
Astc2D10x5UnormSrgb = Astc2D10x5 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4956
|
||||
Astc2D10x6UnormSrgb = Astc2D10x6 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4957
|
||||
Astc2D10x8UnormSrgb = Astc2D10x8 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4953
|
||||
Astc2D10x10UnormSrgb = Astc2D10x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4945
|
||||
Astc2D12x10UnormSrgb = Astc2D12x10 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4954
|
||||
Astc2D12x12UnormSrgb = Astc2D12x12 | RUnorm | GUnorm | BUnorm | AUnorm | Srgb, // 0xa4946
|
||||
A5B5G5R1Unorm = A5B5G5R1 | RUnorm | GUnorm | BUnorm | AUnorm, // 0x24913
|
||||
}
|
||||
|
||||
private enum VertexAttributeFormat : uint
|
||||
{
|
||||
// Width
|
||||
R32G32B32A32 = 0x01,
|
||||
R32G32B32 = 0x02,
|
||||
R16G16B16A16 = 0x03,
|
||||
R32G32 = 0x04,
|
||||
R16G16B16 = 0x05,
|
||||
A8B8G8R8 = 0x2f,
|
||||
R8G8B8A8 = 0x0a,
|
||||
X8B8G8R8 = 0x33,
|
||||
A2B10G10R10 = 0x30,
|
||||
B10G11R11 = 0x31,
|
||||
R16G16 = 0x0f,
|
||||
R32 = 0x12,
|
||||
R8G8B8 = 0x13,
|
||||
G8R8 = 0x32,
|
||||
R8G8 = 0x18,
|
||||
R16 = 0x1b,
|
||||
R8 = 0x1d,
|
||||
A8 = 0x34,
|
||||
|
||||
// Type
|
||||
Snorm = 0x01,
|
||||
Unorm = 0x02,
|
||||
Sint = 0x03,
|
||||
Uint = 0x04,
|
||||
Uscaled = 0x05,
|
||||
Sscaled = 0x06,
|
||||
Float = 0x07,
|
||||
|
||||
// Combinations
|
||||
R8Unorm = (R8 << 21) | (Unorm << 27), // 0x13a00000
|
||||
R8Snorm = (R8 << 21) | (Snorm << 27), // 0x0ba00000
|
||||
R8Uint = (R8 << 21) | (Uint << 27), // 0x23a00000
|
||||
R8Sint = (R8 << 21) | (Sint << 27), // 0x1ba00000
|
||||
R16Float = (R16 << 21) | (Float << 27), // 0x3b600000
|
||||
R16Unorm = (R16 << 21) | (Unorm << 27), // 0x13600000
|
||||
R16Snorm = (R16 << 21) | (Snorm << 27), // 0x0b600000
|
||||
R16Uint = (R16 << 21) | (Uint << 27), // 0x23600000
|
||||
R16Sint = (R16 << 21) | (Sint << 27), // 0x1b600000
|
||||
R32Float = (R32 << 21) | (Float << 27), // 0x3a400000
|
||||
R32Uint = (R32 << 21) | (Uint << 27), // 0x22400000
|
||||
R32Sint = (R32 << 21) | (Sint << 27), // 0x1a400000
|
||||
R8G8Unorm = (R8G8 << 21) | (Unorm << 27), // 0x13000000
|
||||
R8G8Snorm = (R8G8 << 21) | (Snorm << 27), // 0x0b000000
|
||||
R8G8Uint = (R8G8 << 21) | (Uint << 27), // 0x23000000
|
||||
R8G8Sint = (R8G8 << 21) | (Sint << 27), // 0x1b000000
|
||||
R16G16Float = (R16G16 << 21) | (Float << 27), // 0x39e00000
|
||||
R16G16Unorm = (R16G16 << 21) | (Unorm << 27), // 0x11e00000
|
||||
R16G16Snorm = (R16G16 << 21) | (Snorm << 27), // 0x09e00000
|
||||
R16G16Uint = (R16G16 << 21) | (Uint << 27), // 0x21e00000
|
||||
R16G16Sint = (R16G16 << 21) | (Sint << 27), // 0x19e00000
|
||||
R32G32Float = (R32G32 << 21) | (Float << 27), // 0x38800000
|
||||
R32G32Uint = (R32G32 << 21) | (Uint << 27), // 0x20800000
|
||||
R32G32Sint = (R32G32 << 21) | (Sint << 27), // 0x18800000
|
||||
R8G8B8Unorm = (R8G8B8 << 21) | (Unorm << 27), // 0x12600000
|
||||
R8G8B8Snorm = (R8G8B8 << 21) | (Snorm << 27), // 0x0a600000
|
||||
R8G8B8Uint = (R8G8B8 << 21) | (Uint << 27), // 0x22600000
|
||||
R8G8B8Sint = (R8G8B8 << 21) | (Sint << 27), // 0x1a600000
|
||||
R16G16B16Float = (R16G16B16 << 21) | (Float << 27), // 0x38a00000
|
||||
R16G16B16Unorm = (R16G16B16 << 21) | (Unorm << 27), // 0x10a00000
|
||||
R16G16B16Snorm = (R16G16B16 << 21) | (Snorm << 27), // 0x08a00000
|
||||
R16G16B16Uint = (R16G16B16 << 21) | (Uint << 27), // 0x20a00000
|
||||
R16G16B16Sint = (R16G16B16 << 21) | (Sint << 27), // 0x18a00000
|
||||
R32G32B32Float = (R32G32B32 << 21) | (Float << 27), // 0x38400000
|
||||
R32G32B32Uint = (R32G32B32 << 21) | (Uint << 27), // 0x20400000
|
||||
R32G32B32Sint = (R32G32B32 << 21) | (Sint << 27), // 0x18400000
|
||||
R8G8B8A8Unorm = (R8G8B8A8 << 21) | (Unorm << 27), // 0x11400000
|
||||
R8G8B8A8Snorm = (R8G8B8A8 << 21) | (Snorm << 27), // 0x09400000
|
||||
R8G8B8A8Uint = (R8G8B8A8 << 21) | (Uint << 27), // 0x21400000
|
||||
R8G8B8A8Sint = (R8G8B8A8 << 21) | (Sint << 27), // 0x19400000
|
||||
R16G16B16A16Float = (R16G16B16A16 << 21) | (Float << 27), // 0x38600000
|
||||
R16G16B16A16Unorm = (R16G16B16A16 << 21) | (Unorm << 27), // 0x10600000
|
||||
R16G16B16A16Snorm = (R16G16B16A16 << 21) | (Snorm << 27), // 0x08600000
|
||||
R16G16B16A16Uint = (R16G16B16A16 << 21) | (Uint << 27), // 0x20600000
|
||||
R16G16B16A16Sint = (R16G16B16A16 << 21) | (Sint << 27), // 0x18600000
|
||||
R32G32B32A32Float = (R32G32B32A32 << 21) | (Float << 27), // 0x38200000
|
||||
R32G32B32A32Uint = (R32G32B32A32 << 21) | (Uint << 27), // 0x20200000
|
||||
R32G32B32A32Sint = (R32G32B32A32 << 21) | (Sint << 27), // 0x18200000
|
||||
A2B10G10R10Unorm = (A2B10G10R10 << 21) | (Unorm << 27), // 0x16000000
|
||||
A2B10G10R10Uint = (A2B10G10R10 << 21) | (Uint << 27), // 0x26000000
|
||||
B10G11R11Float = (B10G11R11 << 21) | (Float << 27), // 0x3e200000
|
||||
R8Uscaled = (R8 << 21) | (Uscaled << 27), // 0x2ba00000
|
||||
R8Sscaled = (R8 << 21) | (Sscaled << 27), // 0x33a00000
|
||||
R16Uscaled = (R16 << 21) | (Uscaled << 27), // 0x2b600000
|
||||
R16Sscaled = (R16 << 21) | (Sscaled << 27), // 0x33600000
|
||||
R32Uscaled = (R32 << 21) | (Uscaled << 27), // 0x2a400000
|
||||
R32Sscaled = (R32 << 21) | (Sscaled << 27), // 0x32400000
|
||||
R8G8Uscaled = (R8G8 << 21) | (Uscaled << 27), // 0x2b000000
|
||||
R8G8Sscaled = (R8G8 << 21) | (Sscaled << 27), // 0x33000000
|
||||
R16G16Uscaled = (R16G16 << 21) | (Uscaled << 27), // 0x29e00000
|
||||
R16G16Sscaled = (R16G16 << 21) | (Sscaled << 27), // 0x31e00000
|
||||
R32G32Uscaled = (R32G32 << 21) | (Uscaled << 27), // 0x28800000
|
||||
R32G32Sscaled = (R32G32 << 21) | (Sscaled << 27), // 0x30800000
|
||||
R8G8B8Uscaled = (R8G8B8 << 21) | (Uscaled << 27), // 0x2a600000
|
||||
R8G8B8Sscaled = (R8G8B8 << 21) | (Sscaled << 27), // 0x32600000
|
||||
R16G16B16Uscaled = (R16G16B16 << 21) | (Uscaled << 27), // 0x28a00000
|
||||
R16G16B16Sscaled = (R16G16B16 << 21) | (Sscaled << 27), // 0x30a00000
|
||||
R32G32B32Uscaled = (R32G32B32 << 21) | (Uscaled << 27), // 0x28400000
|
||||
R32G32B32Sscaled = (R32G32B32 << 21) | (Sscaled << 27), // 0x30400000
|
||||
R8G8B8A8Uscaled = (R8G8B8A8 << 21) | (Uscaled << 27), // 0x29400000
|
||||
R8G8B8A8Sscaled = (R8G8B8A8 << 21) | (Sscaled << 27), // 0x31400000
|
||||
R16G16B16A16Uscaled = (R16G16B16A16 << 21) | (Uscaled << 27), // 0x28600000
|
||||
R16G16B16A16Sscaled = (R16G16B16A16 << 21) | (Sscaled << 27), // 0x30600000
|
||||
R32G32B32A32Uscaled = (R32G32B32A32 << 21) | (Uscaled << 27), // 0x28200000
|
||||
R32G32B32A32Sscaled = (R32G32B32A32 << 21) | (Sscaled << 27), // 0x30200000
|
||||
A2B10G10R10Snorm = (A2B10G10R10 << 21) | (Snorm << 27), // 0x0e000000
|
||||
A2B10G10R10Sint = (A2B10G10R10 << 21) | (Sint << 27), // 0x1e000000
|
||||
A2B10G10R10Uscaled = (A2B10G10R10 << 21) | (Uscaled << 27), // 0x2e000000
|
||||
A2B10G10R10Sscaled = (A2B10G10R10 << 21) | (Sscaled << 27), // 0x36000000
|
||||
}
|
||||
|
||||
private static readonly Dictionary<TextureFormat, FormatInfo> _textureFormats = new Dictionary<TextureFormat, FormatInfo>()
|
||||
{
|
||||
{ TextureFormat.R8Unorm, new FormatInfo(Format.R8Unorm, 1, 1, 1, 1) },
|
||||
{ TextureFormat.R8Snorm, new FormatInfo(Format.R8Snorm, 1, 1, 1, 1) },
|
||||
{ TextureFormat.R8Uint, new FormatInfo(Format.R8Uint, 1, 1, 1, 1) },
|
||||
{ TextureFormat.R8Sint, new FormatInfo(Format.R8Sint, 1, 1, 1, 1) },
|
||||
{ TextureFormat.R16Float, new FormatInfo(Format.R16Float, 1, 1, 2, 1) },
|
||||
{ TextureFormat.R16Unorm, new FormatInfo(Format.R16Unorm, 1, 1, 2, 1) },
|
||||
{ TextureFormat.R16Snorm, new FormatInfo(Format.R16Snorm, 1, 1, 2, 1) },
|
||||
{ TextureFormat.R16Uint, new FormatInfo(Format.R16Uint, 1, 1, 2, 1) },
|
||||
{ TextureFormat.R16Sint, new FormatInfo(Format.R16Sint, 1, 1, 2, 1) },
|
||||
{ TextureFormat.R32Float, new FormatInfo(Format.R32Float, 1, 1, 4, 1) },
|
||||
{ TextureFormat.R32Uint, new FormatInfo(Format.R32Uint, 1, 1, 4, 1) },
|
||||
{ TextureFormat.R32Sint, new FormatInfo(Format.R32Sint, 1, 1, 4, 1) },
|
||||
{ TextureFormat.G8R8Unorm, new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2) },
|
||||
{ TextureFormat.G8R8Snorm, new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2) },
|
||||
{ TextureFormat.G8R8Uint, new FormatInfo(Format.R8G8Uint, 1, 1, 2, 2) },
|
||||
{ TextureFormat.G8R8Sint, new FormatInfo(Format.R8G8Sint, 1, 1, 2, 2) },
|
||||
{ TextureFormat.R16G16Float, new FormatInfo(Format.R16G16Float, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R16G16Unorm, new FormatInfo(Format.R16G16Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R16G16Snorm, new FormatInfo(Format.R16G16Snorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R16G16Uint, new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R16G16Sint, new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R32G32Float, new FormatInfo(Format.R32G32Float, 1, 1, 8, 2) },
|
||||
{ TextureFormat.R32G32Uint, new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.R32G32Sint, new FormatInfo(Format.R32G32Sint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.R32G32B32Float, new FormatInfo(Format.R32G32B32Float, 1, 1, 12, 3) },
|
||||
{ TextureFormat.R32G32B32Uint, new FormatInfo(Format.R32G32B32Uint, 1, 1, 12, 3) },
|
||||
{ TextureFormat.R32G32B32Sint, new FormatInfo(Format.R32G32B32Sint, 1, 1, 12, 3) },
|
||||
{ TextureFormat.A8B8G8R8Unorm, new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4) },
|
||||
{ TextureFormat.A8B8G8R8Snorm, new FormatInfo(Format.R8G8B8A8Snorm, 1, 1, 4, 4) },
|
||||
{ TextureFormat.A8B8G8R8Uint, new FormatInfo(Format.R8G8B8A8Uint, 1, 1, 4, 4) },
|
||||
{ TextureFormat.A8B8G8R8Sint, new FormatInfo(Format.R8G8B8A8Sint, 1, 1, 4, 4) },
|
||||
{ TextureFormat.R16G16B16A16Float, new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4) },
|
||||
{ TextureFormat.R16G16B16A16Unorm, new FormatInfo(Format.R16G16B16A16Unorm, 1, 1, 8, 4) },
|
||||
{ TextureFormat.R16G16B16A16Snorm, new FormatInfo(Format.R16G16B16A16Snorm, 1, 1, 8, 4) },
|
||||
{ TextureFormat.R16G16B16A16Uint, new FormatInfo(Format.R16G16B16A16Uint, 1, 1, 8, 4) },
|
||||
{ TextureFormat.R16G16B16A16Sint, new FormatInfo(Format.R16G16B16A16Sint, 1, 1, 8, 4) },
|
||||
{ TextureFormat.R32G32B32A32Float, new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4) },
|
||||
{ TextureFormat.R32G32B32A32Uint, new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4) },
|
||||
{ TextureFormat.R32G32B32A32Sint, new FormatInfo(Format.R32G32B32A32Sint, 1, 1, 16, 4) },
|
||||
{ TextureFormat.Z16Unorm, new FormatInfo(Format.D16Unorm, 1, 1, 2, 1) },
|
||||
{ TextureFormat.Zf32RFloatGUintBUintAUint, new FormatInfo(Format.D32Float, 1, 1, 4, 1) },
|
||||
{ TextureFormat.Zf32Float, new FormatInfo(Format.D32Float, 1, 1, 4, 1) },
|
||||
{ TextureFormat.G24R8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUnormAUnorm, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.Z24S8RUintGUnormBUintAUint, new FormatInfo(Format.D24UnormS8Uint, 1, 1, 4, 2) },
|
||||
{ TextureFormat.S8Z24RUnormGUintBUintAUint, new FormatInfo(Format.S8UintD24Unorm, 1, 1, 4, 2) },
|
||||
{ TextureFormat.R32B24G8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.Zf32X24S8RFloatGUintBUnormAUnorm, new FormatInfo(Format.D32FloatS8Uint, 1, 1, 8, 2) },
|
||||
{ TextureFormat.A8B8G8R8UnormSrgb, new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4) },
|
||||
{ TextureFormat.G4R4Unorm, new FormatInfo(Format.R4G4Unorm, 1, 1, 1, 2) },
|
||||
{ TextureFormat.A4B4G4R4Unorm, new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4) },
|
||||
{ TextureFormat.A1B5G5R5Unorm, new FormatInfo(Format.R5G5B5A1Unorm, 1, 1, 2, 4) },
|
||||
{ TextureFormat.B5G6R5Unorm, new FormatInfo(Format.R5G6B5Unorm, 1, 1, 2, 3) },
|
||||
{ TextureFormat.A2B10G10R10Unorm, new FormatInfo(Format.R10G10B10A2Unorm, 1, 1, 4, 4) },
|
||||
{ TextureFormat.A2B10G10R10Uint, new FormatInfo(Format.R10G10B10A2Uint, 1, 1, 4, 4) },
|
||||
{ TextureFormat.Bf10Gf11Rf11Float, new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3) },
|
||||
{ TextureFormat.E5B9G9R9SharedExpFloat, new FormatInfo(Format.R9G9B9E5Float, 1, 1, 4, 4) },
|
||||
{ TextureFormat.Bc1Unorm, new FormatInfo(Format.Bc1RgbaUnorm, 4, 4, 8, 4) },
|
||||
{ TextureFormat.Bc2Unorm, new FormatInfo(Format.Bc2Unorm, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc3Unorm, new FormatInfo(Format.Bc3Unorm, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc1UnormSrgb, new FormatInfo(Format.Bc1RgbaSrgb, 4, 4, 8, 4) },
|
||||
{ TextureFormat.Bc2UnormSrgb, new FormatInfo(Format.Bc2Srgb, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc3UnormSrgb, new FormatInfo(Format.Bc3Srgb, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc4Unorm, new FormatInfo(Format.Bc4Unorm, 4, 4, 8, 1) },
|
||||
{ TextureFormat.Bc4Snorm, new FormatInfo(Format.Bc4Snorm, 4, 4, 8, 1) },
|
||||
{ TextureFormat.Bc5Unorm, new FormatInfo(Format.Bc5Unorm, 4, 4, 16, 2) },
|
||||
{ TextureFormat.Bc5Snorm, new FormatInfo(Format.Bc5Snorm, 4, 4, 16, 2) },
|
||||
{ TextureFormat.Bc7UUnorm, new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc7UUnormSrgb, new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc6HSf16Float, new FormatInfo(Format.Bc6HSfloat, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Bc6HUf16Float, new FormatInfo(Format.Bc6HUfloat, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Etc2RgbUnorm, new FormatInfo(Format.Etc2RgbUnorm, 4, 4, 8, 3) },
|
||||
{ TextureFormat.Etc2RgbPtaUnorm, new FormatInfo(Format.Etc2RgbPtaUnorm, 4, 4, 8, 4) },
|
||||
{ TextureFormat.Etc2RgbaUnorm, new FormatInfo(Format.Etc2RgbaUnorm, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Etc2RgbUnormSrgb, new FormatInfo(Format.Etc2RgbSrgb, 4, 4, 8, 3) },
|
||||
{ TextureFormat.Etc2RgbPtaUnormSrgb, new FormatInfo(Format.Etc2RgbPtaSrgb, 4, 4, 8, 4) },
|
||||
{ TextureFormat.Etc2RgbaUnormSrgb, new FormatInfo(Format.Etc2RgbaSrgb, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Astc2D4x4Unorm, new FormatInfo(Format.Astc4x4Unorm, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Astc2D5x4Unorm, new FormatInfo(Format.Astc5x4Unorm, 5, 4, 16, 4) },
|
||||
{ TextureFormat.Astc2D5x5Unorm, new FormatInfo(Format.Astc5x5Unorm, 5, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D6x5Unorm, new FormatInfo(Format.Astc6x5Unorm, 6, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D6x6Unorm, new FormatInfo(Format.Astc6x6Unorm, 6, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x5Unorm, new FormatInfo(Format.Astc8x5Unorm, 8, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x6Unorm, new FormatInfo(Format.Astc8x6Unorm, 8, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x8Unorm, new FormatInfo(Format.Astc8x8Unorm, 8, 8, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x5Unorm, new FormatInfo(Format.Astc10x5Unorm, 10, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x6Unorm, new FormatInfo(Format.Astc10x6Unorm, 10, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x8Unorm, new FormatInfo(Format.Astc10x8Unorm, 10, 8, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x10Unorm, new FormatInfo(Format.Astc10x10Unorm, 10, 10, 16, 4) },
|
||||
{ TextureFormat.Astc2D12x10Unorm, new FormatInfo(Format.Astc12x10Unorm, 12, 10, 16, 4) },
|
||||
{ TextureFormat.Astc2D12x12Unorm, new FormatInfo(Format.Astc12x12Unorm, 12, 12, 16, 4) },
|
||||
{ TextureFormat.Astc2D4x4UnormSrgb, new FormatInfo(Format.Astc4x4Srgb, 4, 4, 16, 4) },
|
||||
{ TextureFormat.Astc2D5x4UnormSrgb, new FormatInfo(Format.Astc5x4Srgb, 5, 4, 16, 4) },
|
||||
{ TextureFormat.Astc2D5x5UnormSrgb, new FormatInfo(Format.Astc5x5Srgb, 5, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D6x5UnormSrgb, new FormatInfo(Format.Astc6x5Srgb, 6, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D6x6UnormSrgb, new FormatInfo(Format.Astc6x6Srgb, 6, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x5UnormSrgb, new FormatInfo(Format.Astc8x5Srgb, 8, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x6UnormSrgb, new FormatInfo(Format.Astc8x6Srgb, 8, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D8x8UnormSrgb, new FormatInfo(Format.Astc8x8Srgb, 8, 8, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x5UnormSrgb, new FormatInfo(Format.Astc10x5Srgb, 10, 5, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x6UnormSrgb, new FormatInfo(Format.Astc10x6Srgb, 10, 6, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x8UnormSrgb, new FormatInfo(Format.Astc10x8Srgb, 10, 8, 16, 4) },
|
||||
{ TextureFormat.Astc2D10x10UnormSrgb, new FormatInfo(Format.Astc10x10Srgb, 10, 10, 16, 4) },
|
||||
{ TextureFormat.Astc2D12x10UnormSrgb, new FormatInfo(Format.Astc12x10Srgb, 12, 10, 16, 4) },
|
||||
{ TextureFormat.Astc2D12x12UnormSrgb, new FormatInfo(Format.Astc12x12Srgb, 12, 12, 16, 4) },
|
||||
{ TextureFormat.A5B5G5R1Unorm, new FormatInfo(Format.A1B5G5R5Unorm, 1, 1, 2, 4) }
|
||||
};
|
||||
|
||||
private static readonly Dictionary<VertexAttributeFormat, Format> _attribFormats = new Dictionary<VertexAttributeFormat, Format>()
|
||||
{
|
||||
{ VertexAttributeFormat.R8Unorm, Format.R8Unorm },
|
||||
{ VertexAttributeFormat.R8Snorm, Format.R8Snorm },
|
||||
{ VertexAttributeFormat.R8Uint, Format.R8Uint },
|
||||
{ VertexAttributeFormat.R8Sint, Format.R8Sint },
|
||||
{ VertexAttributeFormat.R16Float, Format.R16Float },
|
||||
{ VertexAttributeFormat.R16Unorm, Format.R16Unorm },
|
||||
{ VertexAttributeFormat.R16Snorm, Format.R16Snorm },
|
||||
{ VertexAttributeFormat.R16Uint, Format.R16Uint },
|
||||
{ VertexAttributeFormat.R16Sint, Format.R16Sint },
|
||||
{ VertexAttributeFormat.R32Float, Format.R32Float },
|
||||
{ VertexAttributeFormat.R32Uint, Format.R32Uint },
|
||||
{ VertexAttributeFormat.R32Sint, Format.R32Sint },
|
||||
{ VertexAttributeFormat.R8G8Unorm, Format.R8G8Unorm },
|
||||
{ VertexAttributeFormat.R8G8Snorm, Format.R8G8Snorm },
|
||||
{ VertexAttributeFormat.R8G8Uint, Format.R8G8Uint },
|
||||
{ VertexAttributeFormat.R8G8Sint, Format.R8G8Sint },
|
||||
{ VertexAttributeFormat.R16G16Float, Format.R16G16Float },
|
||||
{ VertexAttributeFormat.R16G16Unorm, Format.R16G16Unorm },
|
||||
{ VertexAttributeFormat.R16G16Snorm, Format.R16G16Snorm },
|
||||
{ VertexAttributeFormat.R16G16Uint, Format.R16G16Uint },
|
||||
{ VertexAttributeFormat.R16G16Sint, Format.R16G16Sint },
|
||||
{ VertexAttributeFormat.R32G32Float, Format.R32G32Float },
|
||||
{ VertexAttributeFormat.R32G32Uint, Format.R32G32Uint },
|
||||
{ VertexAttributeFormat.R32G32Sint, Format.R32G32Sint },
|
||||
{ VertexAttributeFormat.R8G8B8Unorm, Format.R8G8B8Unorm },
|
||||
{ VertexAttributeFormat.R8G8B8Snorm, Format.R8G8B8Snorm },
|
||||
{ VertexAttributeFormat.R8G8B8Uint, Format.R8G8B8Uint },
|
||||
{ VertexAttributeFormat.R8G8B8Sint, Format.R8G8B8Sint },
|
||||
{ VertexAttributeFormat.R16G16B16Float, Format.R16G16B16Float },
|
||||
{ VertexAttributeFormat.R16G16B16Unorm, Format.R16G16B16Unorm },
|
||||
{ VertexAttributeFormat.R16G16B16Snorm, Format.R16G16B16Snorm },
|
||||
{ VertexAttributeFormat.R16G16B16Uint, Format.R16G16B16Uint },
|
||||
{ VertexAttributeFormat.R16G16B16Sint, Format.R16G16B16Sint },
|
||||
{ VertexAttributeFormat.R32G32B32Float, Format.R32G32B32Float },
|
||||
{ VertexAttributeFormat.R32G32B32Uint, Format.R32G32B32Uint },
|
||||
{ VertexAttributeFormat.R32G32B32Sint, Format.R32G32B32Sint },
|
||||
{ VertexAttributeFormat.R8G8B8A8Unorm, Format.R8G8B8A8Unorm },
|
||||
{ VertexAttributeFormat.R8G8B8A8Snorm, Format.R8G8B8A8Snorm },
|
||||
{ VertexAttributeFormat.R8G8B8A8Uint, Format.R8G8B8A8Uint },
|
||||
{ VertexAttributeFormat.R8G8B8A8Sint, Format.R8G8B8A8Sint },
|
||||
{ VertexAttributeFormat.R16G16B16A16Float, Format.R16G16B16A16Float },
|
||||
{ VertexAttributeFormat.R16G16B16A16Unorm, Format.R16G16B16A16Unorm },
|
||||
{ VertexAttributeFormat.R16G16B16A16Snorm, Format.R16G16B16A16Snorm },
|
||||
{ VertexAttributeFormat.R16G16B16A16Uint, Format.R16G16B16A16Uint },
|
||||
{ VertexAttributeFormat.R16G16B16A16Sint, Format.R16G16B16A16Sint },
|
||||
{ VertexAttributeFormat.R32G32B32A32Float, Format.R32G32B32A32Float },
|
||||
{ VertexAttributeFormat.R32G32B32A32Uint, Format.R32G32B32A32Uint },
|
||||
{ VertexAttributeFormat.R32G32B32A32Sint, Format.R32G32B32A32Sint },
|
||||
{ VertexAttributeFormat.A2B10G10R10Unorm, Format.R10G10B10A2Unorm },
|
||||
{ VertexAttributeFormat.A2B10G10R10Uint, Format.R10G10B10A2Uint },
|
||||
{ VertexAttributeFormat.B10G11R11Float, Format.R11G11B10Float },
|
||||
{ VertexAttributeFormat.R8Uscaled, Format.R8Uscaled },
|
||||
{ VertexAttributeFormat.R8Sscaled, Format.R8Sscaled },
|
||||
{ VertexAttributeFormat.R16Uscaled, Format.R16Uscaled },
|
||||
{ VertexAttributeFormat.R16Sscaled, Format.R16Sscaled },
|
||||
{ VertexAttributeFormat.R32Uscaled, Format.R32Uscaled },
|
||||
{ VertexAttributeFormat.R32Sscaled, Format.R32Sscaled },
|
||||
{ VertexAttributeFormat.R8G8Uscaled, Format.R8G8Uscaled },
|
||||
{ VertexAttributeFormat.R8G8Sscaled, Format.R8G8Sscaled },
|
||||
{ VertexAttributeFormat.R16G16Uscaled, Format.R16G16Uscaled },
|
||||
{ VertexAttributeFormat.R16G16Sscaled, Format.R16G16Sscaled },
|
||||
{ VertexAttributeFormat.R32G32Uscaled, Format.R32G32Uscaled },
|
||||
{ VertexAttributeFormat.R32G32Sscaled, Format.R32G32Sscaled },
|
||||
{ VertexAttributeFormat.R8G8B8Uscaled, Format.R8G8B8Uscaled },
|
||||
{ VertexAttributeFormat.R8G8B8Sscaled, Format.R8G8B8Sscaled },
|
||||
{ VertexAttributeFormat.R16G16B16Uscaled, Format.R16G16B16Uscaled },
|
||||
{ VertexAttributeFormat.R16G16B16Sscaled, Format.R16G16B16Sscaled },
|
||||
{ VertexAttributeFormat.R32G32B32Uscaled, Format.R32G32B32Uscaled },
|
||||
{ VertexAttributeFormat.R32G32B32Sscaled, Format.R32G32B32Sscaled },
|
||||
{ VertexAttributeFormat.R8G8B8A8Uscaled, Format.R8G8B8A8Uscaled },
|
||||
{ VertexAttributeFormat.R8G8B8A8Sscaled, Format.R8G8B8A8Sscaled },
|
||||
{ VertexAttributeFormat.R16G16B16A16Uscaled, Format.R16G16B16A16Uscaled },
|
||||
{ VertexAttributeFormat.R16G16B16A16Sscaled, Format.R16G16B16A16Sscaled },
|
||||
{ VertexAttributeFormat.R32G32B32A32Uscaled, Format.R32G32B32A32Uscaled },
|
||||
{ VertexAttributeFormat.R32G32B32A32Sscaled, Format.R32G32B32A32Sscaled },
|
||||
{ VertexAttributeFormat.A2B10G10R10Snorm, Format.R10G10B10A2Snorm },
|
||||
{ VertexAttributeFormat.A2B10G10R10Sint, Format.R10G10B10A2Sint },
|
||||
{ VertexAttributeFormat.A2B10G10R10Uscaled, Format.R10G10B10A2Uscaled },
|
||||
{ VertexAttributeFormat.A2B10G10R10Sscaled, Format.R10G10B10A2Sscaled }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Try getting the texture format from an encoded format integer from the Maxwell texture descriptor.
|
||||
/// </summary>
|
||||
/// <param name="encoded">The encoded format integer from the texture descriptor</param>
|
||||
/// <param name="isSrgb">Indicates if the format is a sRGB format</param>
|
||||
/// <param name="format">The output texture format</param>
|
||||
/// <returns>True if the format is valid, false otherwise</returns>
|
||||
public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
|
||||
{
|
||||
encoded |= (isSrgb ? 1u << 19 : 0u);
|
||||
|
||||
return _textureFormats.TryGetValue((TextureFormat)encoded, out format);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try getting the vertex attribute format from an encoded format integer from Maxwell attribute registers.
|
||||
/// </summary>
|
||||
/// <param name="encoded">The encoded format integer from the attribute registers</param>
|
||||
/// <param name="format">The output vertex attribute format</param>
|
||||
/// <returns>True if the format is valid, false otherwise</returns>
|
||||
public static bool TryGetAttribFormat(uint encoded, out Format format)
|
||||
{
|
||||
return _attribFormats.TryGetValue((VertexAttributeFormat)encoded, out format);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs
Normal file
10
src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
interface ITextureDescriptor
|
||||
{
|
||||
public uint UnpackFormat();
|
||||
public TextureTarget UnpackTextureTarget();
|
||||
public bool UnpackSrgb();
|
||||
public bool UnpackTextureCoordNormalized();
|
||||
}
|
||||
}
|
||||
222
src/Ryujinx.Graphics.Gpu/Image/Pool.cs
Normal file
222
src/Ryujinx.Graphics.Gpu/Image/Pool.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a pool of GPU resources, such as samplers or textures.
|
||||
/// </summary>
|
||||
/// <typeparam name="T1">Type of the GPU resource</typeparam>
|
||||
/// <typeparam name="T2">Type of the descriptor</typeparam>
|
||||
abstract class Pool<T1, T2> : IDisposable where T2 : unmanaged
|
||||
{
|
||||
protected const int DescriptorSize = 0x20;
|
||||
|
||||
protected GpuContext Context;
|
||||
protected PhysicalMemory PhysicalMemory;
|
||||
protected int SequenceNumber;
|
||||
protected int ModifiedSequenceNumber;
|
||||
|
||||
protected T1[] Items;
|
||||
protected T2[] DescriptorCache;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum ID value of resources on the pool (inclusive).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The maximum amount of resources on the pool is equal to this value plus one.
|
||||
/// </remarks>
|
||||
public int MaximumId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The address of the pool in guest memory.
|
||||
/// </summary>
|
||||
public ulong Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The size of the pool in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
|
||||
private readonly CpuMultiRegionHandle _memoryTracking;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private int _modifiedSequenceOffset;
|
||||
private bool _modified;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the GPU resource pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the pool belongs to</param>
|
||||
/// <param name="physicalMemory">Physical memory where the resource descriptors are mapped</param>
|
||||
/// <param name="address">Address of the pool in physical memory</param>
|
||||
/// <param name="maximumId">Maximum index of an item on the pool (inclusive)</param>
|
||||
public Pool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId)
|
||||
{
|
||||
Context = context;
|
||||
PhysicalMemory = physicalMemory;
|
||||
MaximumId = maximumId;
|
||||
|
||||
int count = maximumId + 1;
|
||||
|
||||
ulong size = (ulong)(uint)count * DescriptorSize;
|
||||
|
||||
Items = new T1[count];
|
||||
DescriptorCache = new T2[count];
|
||||
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
||||
_memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool);
|
||||
_memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
|
||||
_modifiedDelegate = RegionModified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the descriptor for a given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>The descriptor</returns>
|
||||
public T2 GetDescriptor(int id)
|
||||
{
|
||||
return PhysicalMemory.Read<T2>(Address + (ulong)id * DescriptorSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the descriptor for a given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>A reference to the descriptor</returns>
|
||||
public ref readonly T2 GetDescriptorRef(int id)
|
||||
{
|
||||
return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the descriptor for a given address.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the descriptor</param>
|
||||
/// <returns>A reference to the descriptor</returns>
|
||||
public ref readonly T2 GetDescriptorRefAddress(ulong address)
|
||||
{
|
||||
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(address, DescriptorSize))[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPU resource with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the resource. This is effectively a zero-based index</param>
|
||||
/// <returns>The GPU resource with the given ID</returns>
|
||||
public abstract T1 Get(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given ID is valid and inside the range of the pool.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>True if the specified ID is valid, false otherwise</returns>
|
||||
public bool IsValidId(int id)
|
||||
{
|
||||
return (uint)id <= MaximumId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes host memory with guest memory.
|
||||
/// This causes invalidation of pool entries,
|
||||
/// if a modification of entries by the CPU is detected.
|
||||
/// </summary>
|
||||
public void SynchronizeMemory()
|
||||
{
|
||||
_modified = false;
|
||||
_memoryTracking.QueryModified(_modifiedDelegate);
|
||||
|
||||
if (_modified)
|
||||
{
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that a region of the pool was modified, and must be loaded from memory.
|
||||
/// </summary>
|
||||
/// <param name="mAddress">Start address of the modified region</param>
|
||||
/// <param name="mSize">Size of the modified region</param>
|
||||
private void RegionModified(ulong mAddress, ulong mSize)
|
||||
{
|
||||
_modified = true;
|
||||
|
||||
if (mAddress < Address)
|
||||
{
|
||||
mAddress = Address;
|
||||
}
|
||||
|
||||
ulong maxSize = Address + Size - mAddress;
|
||||
|
||||
if (mSize > maxSize)
|
||||
{
|
||||
mSize = maxSize;
|
||||
}
|
||||
|
||||
InvalidateRangeImpl(mAddress, mSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the modified sequence number using the current sequence number and offset,
|
||||
/// indicating that it has been modified.
|
||||
/// </summary>
|
||||
protected void UpdateModifiedSequence()
|
||||
{
|
||||
ModifiedSequenceNumber = SequenceNumber + _modifiedSequenceOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action to be performed when a precise memory access occurs to this resource.
|
||||
/// Makes sure that the dirty flags are checked.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the memory action</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
/// <param name="write">True if the access was a write, false otherwise</param>
|
||||
private bool PreciseAction(ulong address, ulong size, bool write)
|
||||
{
|
||||
if (write && Context.SequenceNumber == SequenceNumber)
|
||||
{
|
||||
if (ModifiedSequenceNumber == SequenceNumber + _modifiedSequenceOffset)
|
||||
{
|
||||
// The modified sequence number is offset when PreciseActions occur so that
|
||||
// users checking it will see an increment and know the pool has changed since
|
||||
// their last look, even though the main SequenceNumber has not been changed.
|
||||
|
||||
_modifiedSequenceOffset++;
|
||||
}
|
||||
|
||||
// Force the pool to be checked again the next time it is used.
|
||||
SequenceNumber--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
|
||||
|
||||
protected abstract void Delete(T1 item);
|
||||
|
||||
/// <summary>
|
||||
/// Performs the disposal of all resources stored on the pool.
|
||||
/// It's an error to try using the pool after disposal.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (Items != null)
|
||||
{
|
||||
for (int index = 0; index < Items.Length; index++)
|
||||
{
|
||||
Delete(Items[index]);
|
||||
}
|
||||
|
||||
Items = null;
|
||||
}
|
||||
_memoryTracking.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs
Normal file
129
src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Resource pool interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Resource pool type</typeparam>
|
||||
interface IPool<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Start address of the pool in memory.
|
||||
/// </summary>
|
||||
ulong Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Linked list node used on the texture pool cache.
|
||||
/// </summary>
|
||||
LinkedListNode<T> CacheNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp set on the last use of the pool by the cache.
|
||||
/// </summary>
|
||||
ulong CacheTimestamp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pool cache.
|
||||
/// This can keep multiple pools, and return the current one as needed.
|
||||
/// </summary>
|
||||
abstract class PoolCache<T> : IDisposable where T : IPool<T>, IDisposable
|
||||
{
|
||||
private const int MaxCapacity = 2;
|
||||
private const ulong MinDeltaForRemoval = 20000;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly LinkedList<T> _pools;
|
||||
private ulong _currentTimestamp;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
public PoolCache(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
_pools = new LinkedList<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the internal timestamp of the cache that is used to decide when old resources will be deleted.
|
||||
/// </summary>
|
||||
public void Tick()
|
||||
{
|
||||
_currentTimestamp++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a cache texture pool, or creates a new one if not found.
|
||||
/// </summary>
|
||||
/// <param name="channel">GPU channel that the texture pool cache belongs to</param>
|
||||
/// <param name="address">Start address of the texture pool</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <returns>The found or newly created texture pool</returns>
|
||||
public T FindOrCreate(GpuChannel channel, ulong address, int maximumId)
|
||||
{
|
||||
// Remove old entries from the cache, if possible.
|
||||
while (_pools.Count > MaxCapacity && (_currentTimestamp - _pools.First.Value.CacheTimestamp) >= MinDeltaForRemoval)
|
||||
{
|
||||
T oldestPool = _pools.First.Value;
|
||||
|
||||
_pools.RemoveFirst();
|
||||
oldestPool.Dispose();
|
||||
oldestPool.CacheNode = null;
|
||||
}
|
||||
|
||||
T pool;
|
||||
|
||||
// Try to find the pool on the cache.
|
||||
for (LinkedListNode<T> node = _pools.First; node != null; node = node.Next)
|
||||
{
|
||||
pool = node.Value;
|
||||
|
||||
if (pool.Address == address)
|
||||
{
|
||||
if (pool.CacheNode != _pools.Last)
|
||||
{
|
||||
_pools.Remove(pool.CacheNode);
|
||||
|
||||
pool.CacheNode = _pools.AddLast(pool);
|
||||
}
|
||||
|
||||
pool.CacheTimestamp = _currentTimestamp;
|
||||
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, create a new one.
|
||||
pool = CreatePool(_context, channel, address, maximumId);
|
||||
|
||||
pool.CacheNode = _pools.AddLast(pool);
|
||||
pool.CacheTimestamp = _currentTimestamp;
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the pool belongs to</param>
|
||||
/// <param name="address">Address of the pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum ID of the pool (equal to maximum minus one)</param>
|
||||
protected abstract T CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (T pool in _pools)
|
||||
{
|
||||
pool.Dispose();
|
||||
pool.CacheNode = null;
|
||||
}
|
||||
|
||||
_pools.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
Normal file
15
src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a filter used with texture minification linear filtering.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is only supported on NVIDIA GPUs.
|
||||
/// </remarks>
|
||||
enum ReductionFilter
|
||||
{
|
||||
Average,
|
||||
Minimum,
|
||||
Maximum
|
||||
}
|
||||
}
|
||||
115
src/Ryujinx.Graphics.Gpu/Image/Sampler.cs
Normal file
115
src/Ryujinx.Graphics.Gpu/Image/Sampler.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Cached sampler entry for sampler pools.
|
||||
/// </summary>
|
||||
class Sampler : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the sampler is disposed, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host sampler object.
|
||||
/// </summary>
|
||||
private readonly ISampler _hostSampler;
|
||||
|
||||
/// <summary>
|
||||
/// Host sampler object, with anisotropy forced.
|
||||
/// </summary>
|
||||
private readonly ISampler _anisoSampler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the cached sampler.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context the sampler belongs to</param>
|
||||
/// <param name="descriptor">The Maxwell sampler descriptor</param>
|
||||
public Sampler(GpuContext context, SamplerDescriptor descriptor)
|
||||
{
|
||||
MinFilter minFilter = descriptor.UnpackMinFilter();
|
||||
MagFilter magFilter = descriptor.UnpackMagFilter();
|
||||
|
||||
bool seamlessCubemap = descriptor.UnpackSeamlessCubemap();
|
||||
|
||||
AddressMode addressU = descriptor.UnpackAddressU();
|
||||
AddressMode addressV = descriptor.UnpackAddressV();
|
||||
AddressMode addressP = descriptor.UnpackAddressP();
|
||||
|
||||
CompareMode compareMode = descriptor.UnpackCompareMode();
|
||||
CompareOp compareOp = descriptor.UnpackCompareOp();
|
||||
|
||||
ColorF color = new ColorF(
|
||||
descriptor.BorderColorR,
|
||||
descriptor.BorderColorG,
|
||||
descriptor.BorderColorB,
|
||||
descriptor.BorderColorA);
|
||||
|
||||
float minLod = descriptor.UnpackMinLod();
|
||||
float maxLod = descriptor.UnpackMaxLod();
|
||||
float mipLodBias = descriptor.UnpackMipLodBias();
|
||||
|
||||
float maxRequestedAnisotropy = descriptor.UnpackMaxAnisotropy();
|
||||
float maxSupportedAnisotropy = context.Capabilities.MaximumSupportedAnisotropy;
|
||||
|
||||
_hostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
|
||||
minFilter,
|
||||
magFilter,
|
||||
seamlessCubemap,
|
||||
addressU,
|
||||
addressV,
|
||||
addressP,
|
||||
compareMode,
|
||||
compareOp,
|
||||
color,
|
||||
minLod,
|
||||
maxLod,
|
||||
mipLodBias,
|
||||
Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy)));
|
||||
|
||||
if (GraphicsConfig.MaxAnisotropy >= 0 && GraphicsConfig.MaxAnisotropy <= 16 && (minFilter == MinFilter.LinearMipmapNearest || minFilter == MinFilter.LinearMipmapLinear))
|
||||
{
|
||||
maxRequestedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||
|
||||
_anisoSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
|
||||
minFilter,
|
||||
magFilter,
|
||||
seamlessCubemap,
|
||||
addressU,
|
||||
addressV,
|
||||
addressP,
|
||||
compareMode,
|
||||
compareOp,
|
||||
color,
|
||||
minLod,
|
||||
maxLod,
|
||||
mipLodBias,
|
||||
Math.Min(maxRequestedAnisotropy, maxSupportedAnisotropy)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a host sampler for the given texture.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to be sampled</param>
|
||||
/// <returns>A host sampler</returns>
|
||||
public ISampler GetHostSampler(Texture texture)
|
||||
{
|
||||
return _anisoSampler != null && texture?.CanForceAnisotropy == true ? _anisoSampler : _hostSampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host sampler object.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
_hostSampler.Dispose();
|
||||
_anisoSampler?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
260
src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
Normal file
260
src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Maxwell sampler descriptor structure.
|
||||
/// This structure defines the sampler descriptor as it is packed on the GPU sampler pool region.
|
||||
/// </summary>
|
||||
struct SamplerDescriptor
|
||||
{
|
||||
private static readonly float[] _f5ToF32ConversionLut = new float[]
|
||||
{
|
||||
0.0f,
|
||||
0.055555556f,
|
||||
0.1f,
|
||||
0.13636364f,
|
||||
0.16666667f,
|
||||
0.1923077f,
|
||||
0.21428572f,
|
||||
0.23333333f,
|
||||
0.25f,
|
||||
0.2777778f,
|
||||
0.3f,
|
||||
0.3181818f,
|
||||
0.33333334f,
|
||||
0.34615386f,
|
||||
0.35714287f,
|
||||
0.36666667f,
|
||||
0.375f,
|
||||
0.3888889f,
|
||||
0.4f,
|
||||
0.4090909f,
|
||||
0.41666666f,
|
||||
0.42307693f,
|
||||
0.42857143f,
|
||||
0.43333334f,
|
||||
0.4375f,
|
||||
0.44444445f,
|
||||
0.45f,
|
||||
0.45454547f,
|
||||
0.45833334f,
|
||||
0.46153846f,
|
||||
0.4642857f,
|
||||
0.46666667f
|
||||
};
|
||||
|
||||
private static readonly float[] _maxAnisotropyLut = new float[]
|
||||
{
|
||||
1, 2, 4, 6, 8, 10, 12, 16
|
||||
};
|
||||
|
||||
private const float Frac8ToF32 = 1.0f / 256.0f;
|
||||
|
||||
#pragma warning disable CS0649
|
||||
public uint Word0;
|
||||
public uint Word1;
|
||||
public uint Word2;
|
||||
public uint Word3;
|
||||
public float BorderColorR;
|
||||
public float BorderColorG;
|
||||
public float BorderColorB;
|
||||
public float BorderColorA;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture wrap mode along the X axis.
|
||||
/// </summary>
|
||||
/// <returns>The texture wrap mode enum</returns>
|
||||
public AddressMode UnpackAddressU()
|
||||
{
|
||||
return (AddressMode)(Word0 & 7);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Unpacks the texture wrap mode along the Y axis.
|
||||
/// </summary>
|
||||
/// <returns>The texture wrap mode enum</returns>
|
||||
public AddressMode UnpackAddressV()
|
||||
{
|
||||
return (AddressMode)((Word0 >> 3) & 7);
|
||||
}
|
||||
|
||||
// <summary>
|
||||
/// Unpacks the texture wrap mode along the Z axis.
|
||||
/// </summary>
|
||||
/// <returns>The texture wrap mode enum</returns>
|
||||
public AddressMode UnpackAddressP()
|
||||
{
|
||||
return (AddressMode)((Word0 >> 6) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the compare mode used for depth comparison on the shader, for
|
||||
/// depth buffer texture.
|
||||
/// This is only relevant for shaders with shadow samplers.
|
||||
/// </summary>
|
||||
/// <returns>The depth comparison mode enum</returns>
|
||||
public CompareMode UnpackCompareMode()
|
||||
{
|
||||
return (CompareMode)((Word0 >> 9) & 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the compare operation used for depth comparison on the shader, for
|
||||
/// depth buffer texture.
|
||||
/// This is only relevant for shaders with shadow samplers.
|
||||
/// </summary>
|
||||
/// <returns>The depth comparison operation enum</returns>
|
||||
public CompareOp UnpackCompareOp()
|
||||
{
|
||||
return (CompareOp)(((Word0 >> 10) & 7) + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks and converts the maximum anisotropy value used for texture anisotropic filtering.
|
||||
/// </summary>
|
||||
/// <returns>The maximum anisotropy</returns>
|
||||
public float UnpackMaxAnisotropy()
|
||||
{
|
||||
return _maxAnisotropyLut[(Word0 >> 20) & 7];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture magnification filter.
|
||||
/// This defines the filtering used when the texture covers an area on the screen
|
||||
/// that is larger than the texture size.
|
||||
/// </summary>
|
||||
/// <returns>The magnification filter</returns>
|
||||
public MagFilter UnpackMagFilter()
|
||||
{
|
||||
return (MagFilter)(Word1 & 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture minification filter.
|
||||
/// This defines the filtering used when the texture covers an area on the screen
|
||||
/// that is smaller than the texture size.
|
||||
/// </summary>
|
||||
/// <returns>The minification filter</returns>
|
||||
public MinFilter UnpackMinFilter()
|
||||
{
|
||||
SamplerMinFilter minFilter = (SamplerMinFilter)((Word1 >> 4) & 3);
|
||||
SamplerMipFilter mipFilter = (SamplerMipFilter)((Word1 >> 6) & 3);
|
||||
|
||||
return ConvertFilter(minFilter, mipFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts two minification and filter enum, to a single minification enum,
|
||||
/// including mipmap filtering information, as expected from the host API.
|
||||
/// </summary>
|
||||
/// <param name="minFilter">The minification filter</param>
|
||||
/// <param name="mipFilter">The mipmap level filter</param>
|
||||
/// <returns>The combined, host API compatible filter enum</returns>
|
||||
private static MinFilter ConvertFilter(SamplerMinFilter minFilter, SamplerMipFilter mipFilter)
|
||||
{
|
||||
switch (mipFilter)
|
||||
{
|
||||
case SamplerMipFilter.None:
|
||||
switch (minFilter)
|
||||
{
|
||||
case SamplerMinFilter.Nearest: return MinFilter.Nearest;
|
||||
case SamplerMinFilter.Linear: return MinFilter.Linear;
|
||||
}
|
||||
break;
|
||||
|
||||
case SamplerMipFilter.Nearest:
|
||||
switch (minFilter)
|
||||
{
|
||||
case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapNearest;
|
||||
case SamplerMinFilter.Linear: return MinFilter.LinearMipmapNearest;
|
||||
}
|
||||
break;
|
||||
|
||||
case SamplerMipFilter.Linear:
|
||||
switch (minFilter)
|
||||
{
|
||||
case SamplerMinFilter.Nearest: return MinFilter.NearestMipmapLinear;
|
||||
case SamplerMinFilter.Linear: return MinFilter.LinearMipmapLinear;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return MinFilter.Nearest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the seamless cubemap flag.
|
||||
/// </summary>
|
||||
/// <returns>The seamless cubemap flag</returns>
|
||||
public bool UnpackSeamlessCubemap()
|
||||
{
|
||||
return (Word1 & (1 << 9)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the reduction filter, used with texture minification linear filtering.
|
||||
/// This describes how the final value will be computed from neighbouring pixels.
|
||||
/// </summary>
|
||||
/// <returns>The reduction filter</returns>
|
||||
public ReductionFilter UnpackReductionFilter()
|
||||
{
|
||||
return (ReductionFilter)((Word1 >> 10) & 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the level-of-detail bias value.
|
||||
/// This is a bias added to the level-of-detail value as computed by the GPU, used to select
|
||||
/// which mipmap level to use from a given texture.
|
||||
/// </summary>
|
||||
/// <returns>The level-of-detail bias value</returns>
|
||||
public float UnpackMipLodBias()
|
||||
{
|
||||
int fixedValue = (int)(Word1 >> 12) & 0x1fff;
|
||||
|
||||
fixedValue = (fixedValue << 19) >> 19;
|
||||
|
||||
return fixedValue * Frac8ToF32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the level-of-detail snap value.
|
||||
/// </summary>
|
||||
/// <returns>The level-of-detail snap value</returns>
|
||||
public float UnpackLodSnap()
|
||||
{
|
||||
return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the minimum level-of-detail value.
|
||||
/// </summary>
|
||||
/// <returns>The minimum level-of-detail value</returns>
|
||||
public float UnpackMinLod()
|
||||
{
|
||||
return (Word2 & 0xfff) * Frac8ToF32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the maximum level-of-detail value.
|
||||
/// </summary>
|
||||
/// <returns>The maximum level-of-detail value</returns>
|
||||
public float UnpackMaxLod()
|
||||
{
|
||||
return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two descriptors are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The descriptor to compare against</param>
|
||||
/// <returns>True if they are equal, false otherwise</returns>
|
||||
public bool Equals(ref SamplerDescriptor other)
|
||||
{
|
||||
return Unsafe.As<SamplerDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<SamplerDescriptor, Vector256<byte>>(ref other));
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs
Normal file
11
src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampler texture minification filter.
|
||||
/// </summary>
|
||||
enum SamplerMinFilter
|
||||
{
|
||||
Nearest = 1,
|
||||
Linear
|
||||
}
|
||||
}
|
||||
12
src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs
Normal file
12
src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampler texture mipmap level filter.
|
||||
/// </summary>
|
||||
enum SamplerMipFilter
|
||||
{
|
||||
None = 1,
|
||||
Nearest,
|
||||
Linear
|
||||
}
|
||||
}
|
||||
162
src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
Normal file
162
src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampler pool.
|
||||
/// </summary>
|
||||
class SamplerPool : Pool<Sampler, SamplerDescriptor>, IPool<SamplerPool>
|
||||
{
|
||||
private float _forcedAnisotropy;
|
||||
|
||||
/// <summary>
|
||||
/// Linked list node used on the sampler pool cache.
|
||||
/// </summary>
|
||||
public LinkedListNode<SamplerPool> CacheNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp used by the sampler pool cache, updated on every use of this sampler pool.
|
||||
/// </summary>
|
||||
public ulong CacheTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the sampler pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the sampler pool belongs to</param>
|
||||
/// <param name="physicalMemory">Physical memory where the sampler descriptors are mapped</param>
|
||||
/// <param name="address">Address of the sampler pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
|
||||
public SamplerPool(GpuContext context, PhysicalMemory physicalMemory, ulong address, int maximumId) : base(context, physicalMemory, address, maximumId)
|
||||
{
|
||||
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sampler with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the sampler. This is effectively a zero-based index</param>
|
||||
/// <returns>The sampler with the given ID</returns>
|
||||
public override Sampler Get(int id)
|
||||
{
|
||||
if ((uint)id >= Items.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
|
||||
{
|
||||
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
{
|
||||
if (Items[i] != null)
|
||||
{
|
||||
Items[i].Dispose();
|
||||
|
||||
Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
Sampler sampler = Items[id];
|
||||
|
||||
if (sampler == null)
|
||||
{
|
||||
SamplerDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
sampler = new Sampler(Context, descriptor);
|
||||
|
||||
Items[id] = sampler;
|
||||
|
||||
DescriptorCache[id] = descriptor;
|
||||
}
|
||||
|
||||
return sampler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||
/// </summary>
|
||||
/// <returns>A number that increments each time a modification is detected</returns>
|
||||
public int CheckModified()
|
||||
{
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
if (_forcedAnisotropy != GraphicsConfig.MaxAnisotropy)
|
||||
{
|
||||
_forcedAnisotropy = GraphicsConfig.MaxAnisotropy;
|
||||
|
||||
for (int i = 0; i < Items.Length; i++)
|
||||
{
|
||||
if (Items[i] != null)
|
||||
{
|
||||
Items[i].Dispose();
|
||||
|
||||
Items[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModifiedSequence();
|
||||
}
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the sampler pool range invalidation.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range of the sampler pool</param>
|
||||
/// <param name="size">Size of the range being invalidated</param>
|
||||
protected override void InvalidateRangeImpl(ulong address, ulong size)
|
||||
{
|
||||
ulong endAddress = address + size;
|
||||
|
||||
for (; address < endAddress; address += DescriptorSize)
|
||||
{
|
||||
int id = (int)((address - Address) / DescriptorSize);
|
||||
|
||||
Sampler sampler = Items[id];
|
||||
|
||||
if (sampler != null)
|
||||
{
|
||||
SamplerDescriptor descriptor = GetDescriptor(id);
|
||||
|
||||
// If the descriptors are the same, the sampler is still valid.
|
||||
if (descriptor.Equals(ref DescriptorCache[id]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sampler.Dispose();
|
||||
|
||||
Items[id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a given sampler pool entry.
|
||||
/// The host memory used by the sampler is released by the driver.
|
||||
/// </summary>
|
||||
/// <param name="item">The entry to be deleted</param>
|
||||
protected override void Delete(Sampler item)
|
||||
{
|
||||
item?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs
Normal file
30
src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampler pool cache.
|
||||
/// This can keep multiple sampler pools, and return the current one as needed.
|
||||
/// It is useful for applications that uses multiple sampler pools.
|
||||
/// </summary>
|
||||
class SamplerPoolCache : PoolCache<SamplerPool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
public SamplerPoolCache(GpuContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the sampler pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the sampler pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="address">Address of the sampler pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum sampler ID of the sampler pool (equal to maximum samplers minus one)</param>
|
||||
protected override SamplerPool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
{
|
||||
return new SamplerPool(context, channel.MemoryManager.Physical, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
1705
src/Ryujinx.Graphics.Gpu/Image/Texture.cs
Normal file
1705
src/Ryujinx.Graphics.Gpu/Image/Texture.cs
Normal file
File diff suppressed because it is too large
Load Diff
73
src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
Normal file
73
src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture binding information.
|
||||
/// This is used for textures that needs to be accessed from shaders.
|
||||
/// </summary>
|
||||
readonly struct TextureBindingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader sampler target type.
|
||||
/// </summary>
|
||||
public Target Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// For images, indicates the format specified on the shader.
|
||||
/// </summary>
|
||||
public Format Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Shader texture host binding point.
|
||||
/// </summary>
|
||||
public int Binding { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constant buffer slot with the texture handle.
|
||||
/// </summary>
|
||||
public int CbufSlot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Index of the texture handle on the constant buffer at slot <see cref="CbufSlot"/>.
|
||||
/// </summary>
|
||||
public int Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Flags from the texture descriptor that indicate how the texture is used.
|
||||
/// </summary>
|
||||
public TextureUsageFlags Flags { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the texture binding information structure.
|
||||
/// </summary>
|
||||
/// <param name="target">The shader sampler target type</param>
|
||||
/// <param name="format">Format of the image as declared on the shader</param>
|
||||
/// <param name="binding">The shader texture binding point</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
||||
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
|
||||
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
||||
public TextureBindingInfo(Target target, Format format, int binding, int cbufSlot, int handle, TextureUsageFlags flags)
|
||||
{
|
||||
Target = target;
|
||||
Format = format;
|
||||
Binding = binding;
|
||||
CbufSlot = cbufSlot;
|
||||
Handle = handle;
|
||||
Flags = flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the texture binding information structure.
|
||||
/// </summary>
|
||||
/// <param name="target">The shader sampler target type</param>
|
||||
/// <param name="binding">The shader texture binding point</param>
|
||||
/// <param name="cbufSlot">Constant buffer slot where the texture handle is located</param>
|
||||
/// <param name="handle">The shader texture handle (read index into the texture constant buffer)</param>
|
||||
/// <param name="flags">The texture's usage flags, indicating how it is used in the shader</param>
|
||||
public TextureBindingInfo(Target target, int binding, int cbufSlot, int handle, TextureUsageFlags flags) : this(target, (Format)0, binding, cbufSlot, handle, flags)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
882
src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
Normal file
882
src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs
Normal file
@@ -0,0 +1,882 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture bindings manager.
|
||||
/// </summary>
|
||||
class TextureBindingsManager
|
||||
{
|
||||
private const int InitialTextureStateSize = 32;
|
||||
private const int InitialImageStateSize = 8;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
private readonly bool _isCompute;
|
||||
|
||||
private ulong _texturePoolGpuVa;
|
||||
private int _texturePoolMaximumId;
|
||||
private TexturePool _texturePool;
|
||||
private ulong _samplerPoolGpuVa;
|
||||
private int _samplerPoolMaximumId;
|
||||
private SamplerIndex _samplerIndex;
|
||||
private SamplerPool _samplerPool;
|
||||
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
private readonly SamplerPoolCache _samplerPoolCache;
|
||||
|
||||
private TexturePool _cachedTexturePool;
|
||||
private SamplerPool _cachedSamplerPool;
|
||||
|
||||
private TextureBindingInfo[][] _textureBindings;
|
||||
private TextureBindingInfo[][] _imageBindings;
|
||||
|
||||
private struct TextureState
|
||||
{
|
||||
public ITexture Texture;
|
||||
public ISampler Sampler;
|
||||
|
||||
public int TextureHandle;
|
||||
public int SamplerHandle;
|
||||
public Format ImageFormat;
|
||||
public int InvalidatedSequence;
|
||||
public Texture CachedTexture;
|
||||
public Sampler CachedSampler;
|
||||
}
|
||||
|
||||
private TextureState[] _textureState;
|
||||
private TextureState[] _imageState;
|
||||
|
||||
private int _texturePoolSequence;
|
||||
private int _samplerPoolSequence;
|
||||
|
||||
private int _textureBufferIndex;
|
||||
|
||||
private readonly float[] _scales;
|
||||
private bool _scaleChanged;
|
||||
private int _lastFragmentTotal;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture bindings manager.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context that the texture bindings manager belongs to</param>
|
||||
/// <param name="channel">The GPU channel that the texture bindings manager belongs to</param>
|
||||
/// <param name="texturePoolCache">Texture pools cache used to get texture pools from</param>
|
||||
/// <param name="samplerPoolCache">Sampler pools cache used to get sampler pools from</param>
|
||||
/// <param name="scales">Array where the scales for the currently bound textures are stored</param>
|
||||
/// <param name="isCompute">True if the bindings manager is used for the compute engine</param>
|
||||
public TextureBindingsManager(
|
||||
GpuContext context,
|
||||
GpuChannel channel,
|
||||
TexturePoolCache texturePoolCache,
|
||||
SamplerPoolCache samplerPoolCache,
|
||||
float[] scales,
|
||||
bool isCompute)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
_texturePoolCache = texturePoolCache;
|
||||
_samplerPoolCache = samplerPoolCache;
|
||||
|
||||
_scales = scales;
|
||||
_isCompute = isCompute;
|
||||
|
||||
int stages = isCompute ? 1 : Constants.ShaderStages;
|
||||
|
||||
_textureBindings = new TextureBindingInfo[stages][];
|
||||
_imageBindings = new TextureBindingInfo[stages][];
|
||||
|
||||
_textureState = new TextureState[InitialTextureStateSize];
|
||||
_imageState = new TextureState[InitialImageStateSize];
|
||||
|
||||
for (int stage = 0; stage < stages; stage++)
|
||||
{
|
||||
_textureBindings[stage] = new TextureBindingInfo[InitialTextureStateSize];
|
||||
_imageBindings[stage] = new TextureBindingInfo[InitialImageStateSize];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture and image bindings.
|
||||
/// </summary>
|
||||
/// <param name="bindings">Bindings for the active shader</param>
|
||||
public void SetBindings(CachedShaderBindings bindings)
|
||||
{
|
||||
_textureBindings = bindings.TextureBindings;
|
||||
_imageBindings = bindings.ImageBindings;
|
||||
|
||||
SetMaxBindings(bindings.MaxTextureBinding, bindings.MaxImageBinding);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the max binding indexes for textures and images.
|
||||
/// </summary>
|
||||
/// <param name="maxTextureBinding">The maximum texture binding</param>
|
||||
/// <param name="maxImageBinding">The maximum image binding</param>
|
||||
public void SetMaxBindings(int maxTextureBinding, int maxImageBinding)
|
||||
{
|
||||
if (maxTextureBinding >= _textureState.Length)
|
||||
{
|
||||
Array.Resize(ref _textureState, maxTextureBinding + 1);
|
||||
}
|
||||
|
||||
if (maxImageBinding >= _imageState.Length)
|
||||
{
|
||||
Array.Resize(ref _imageState, maxImageBinding + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the textures constant buffer index.
|
||||
/// The constant buffer specified holds the texture handles.
|
||||
/// </summary>
|
||||
/// <param name="index">Constant buffer index</param>
|
||||
public void SetTextureBufferIndex(int index)
|
||||
{
|
||||
_textureBufferIndex = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current texture sampler pool to be used.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
|
||||
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
|
||||
/// <param name="samplerIndex">Type of the sampler pool indexing used for bound samplers</param>
|
||||
public void SetSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
|
||||
{
|
||||
_samplerPoolGpuVa = gpuVa;
|
||||
_samplerPoolMaximumId = maximumId;
|
||||
_samplerIndex = samplerIndex;
|
||||
_samplerPool = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current texture pool to be used.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">Start GPU virtual address of the pool</param>
|
||||
/// <param name="maximumId">Maximum ID of the pool (total count minus one)</param>
|
||||
public void SetTexturePool(ulong gpuVa, int maximumId)
|
||||
{
|
||||
_texturePoolGpuVa = gpuVa;
|
||||
_texturePoolMaximumId = maximumId;
|
||||
_texturePool = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID.
|
||||
/// </summary>
|
||||
/// <param name="textureId">ID of the texture</param>
|
||||
/// <param name="samplerId">ID of the sampler</param>
|
||||
public (Texture, Sampler) GetTextureAndSampler(int textureId, int samplerId)
|
||||
{
|
||||
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
|
||||
|
||||
return (texturePool.Get(textureId), samplerPool.Get(samplerId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the texture scale for a given texture or image.
|
||||
/// </summary>
|
||||
/// <param name="texture">Start GPU virtual address of the pool</param>
|
||||
/// <param name="usageFlags">The related texture usage flags</param>
|
||||
/// <param name="index">The texture/image binding index</param>
|
||||
/// <param name="stage">The active shader stage</param>
|
||||
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
|
||||
private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
|
||||
{
|
||||
float result = 1f;
|
||||
bool changed = false;
|
||||
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||
{
|
||||
if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
{
|
||||
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
|
||||
texture.BlacklistScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
float scale = texture.ScaleFactor;
|
||||
|
||||
if (scale != 1)
|
||||
{
|
||||
Texture activeTarget = _channel.TextureManager.GetAnyRenderTarget();
|
||||
|
||||
if (activeTarget != null && (activeTarget.Info.Width / (float)texture.Info.Width) == (activeTarget.Info.Height / (float)texture.Info.Height))
|
||||
{
|
||||
// If the texture's size is a multiple of the sampler size, enable interpolation using gl_FragCoord. (helps "invent" new integer values between scaled pixels)
|
||||
result = -scale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = scale;
|
||||
break;
|
||||
|
||||
case ShaderStage.Vertex:
|
||||
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
||||
index += _textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length;
|
||||
|
||||
result = texture.ScaleFactor;
|
||||
break;
|
||||
|
||||
case ShaderStage.Compute:
|
||||
result = texture.ScaleFactor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result != _scales[index])
|
||||
{
|
||||
_scaleChanged = true;
|
||||
|
||||
_scales[index] = result;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the vertex stage requires a scale value.
|
||||
/// </summary>
|
||||
private bool VertexRequiresScale()
|
||||
{
|
||||
for (int i = 0; i < _textureBindings[0].Length; i++)
|
||||
{
|
||||
if ((_textureBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _imageBindings[0].Length; i++)
|
||||
{
|
||||
if ((_imageBindings[0][i].Flags & TextureUsageFlags.NeedsScaleValue) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads texture and image scales to the backend when they are used.
|
||||
/// </summary>
|
||||
private void CommitRenderScale()
|
||||
{
|
||||
// Stage 0 total: Compute or Vertex.
|
||||
int total = _textureBindings[0].Length + _imageBindings[0].Length;
|
||||
|
||||
int fragmentIndex = (int)ShaderStage.Fragment - 1;
|
||||
int fragmentTotal = _isCompute ? 0 : (_textureBindings[fragmentIndex].Length + _imageBindings[fragmentIndex].Length);
|
||||
|
||||
if (total != 0 && fragmentTotal != _lastFragmentTotal && VertexRequiresScale())
|
||||
{
|
||||
// Must update scales in the support buffer if:
|
||||
// - Vertex stage has bindings that require scale.
|
||||
// - Fragment stage binding count has been updated since last render scale update.
|
||||
|
||||
_scaleChanged = true;
|
||||
}
|
||||
|
||||
if (_scaleChanged)
|
||||
{
|
||||
if (!_isCompute)
|
||||
{
|
||||
total += fragmentTotal; // Add the fragment bindings to the total.
|
||||
}
|
||||
|
||||
_lastFragmentTotal = fragmentTotal;
|
||||
|
||||
_context.Renderer.Pipeline.UpdateRenderScale(_scales, total, fragmentTotal);
|
||||
|
||||
_scaleChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </summary>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||
public bool CommitBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
(TexturePool texturePool, SamplerPool samplerPool) = GetPools();
|
||||
|
||||
// Check if the texture pool has been modified since bindings were last committed.
|
||||
// If it wasn't, then it's possible to avoid looking up textures again when the handle remains the same.
|
||||
if (_cachedTexturePool != texturePool || _cachedSamplerPool != samplerPool)
|
||||
{
|
||||
Rebind();
|
||||
|
||||
_cachedTexturePool = texturePool;
|
||||
_cachedSamplerPool = samplerPool;
|
||||
}
|
||||
|
||||
bool poolModified = false;
|
||||
|
||||
if (texturePool != null)
|
||||
{
|
||||
int texturePoolSequence = texturePool.CheckModified();
|
||||
|
||||
if (_texturePoolSequence != texturePoolSequence)
|
||||
{
|
||||
poolModified = true;
|
||||
_texturePoolSequence = texturePoolSequence;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerPool != null)
|
||||
{
|
||||
int samplerPoolSequence = samplerPool.CheckModified();
|
||||
|
||||
if (_samplerPoolSequence != samplerPoolSequence)
|
||||
{
|
||||
poolModified = true;
|
||||
_samplerPoolSequence = samplerPoolSequence;
|
||||
}
|
||||
}
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
if (_isCompute)
|
||||
{
|
||||
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, ShaderStage.Compute, 0, poolModified, specState);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
|
||||
{
|
||||
int stageIndex = (int)stage - 1;
|
||||
|
||||
specStateMatches &= CommitTextureBindings(texturePool, samplerPool, stage, stageIndex, poolModified, specState);
|
||||
specStateMatches &= CommitImageBindings(texturePool, stage, stageIndex, poolModified, specState);
|
||||
}
|
||||
}
|
||||
|
||||
CommitRenderScale();
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch the constant buffers used for a texture to cache.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">Stage index of the constant buffer</param>
|
||||
/// <param name="cachedTextureBufferIndex">The currently cached texture buffer index</param>
|
||||
/// <param name="cachedSamplerBufferIndex">The currently cached sampler buffer index</param>
|
||||
/// <param name="cachedTextureBuffer">The currently cached texture buffer data</param>
|
||||
/// <param name="cachedSamplerBuffer">The currently cached sampler buffer data</param>
|
||||
/// <param name="textureBufferIndex">The new texture buffer index</param>
|
||||
/// <param name="samplerBufferIndex">The new sampler buffer index</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateCachedBuffer(
|
||||
int stageIndex,
|
||||
scoped ref int cachedTextureBufferIndex,
|
||||
scoped ref int cachedSamplerBufferIndex,
|
||||
scoped ref ReadOnlySpan<int> cachedTextureBuffer,
|
||||
scoped ref ReadOnlySpan<int> cachedSamplerBuffer,
|
||||
int textureBufferIndex,
|
||||
int samplerBufferIndex)
|
||||
{
|
||||
if (textureBufferIndex != cachedTextureBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, textureBufferIndex);
|
||||
|
||||
cachedTextureBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedTextureBufferIndex = textureBufferIndex;
|
||||
|
||||
if (samplerBufferIndex == textureBufferIndex)
|
||||
{
|
||||
cachedSamplerBuffer = cachedTextureBuffer;
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerBufferIndex != cachedSamplerBufferIndex)
|
||||
{
|
||||
ref BufferBounds bounds = ref _channel.BufferManager.GetUniformBufferBounds(_isCompute, stageIndex, samplerBufferIndex);
|
||||
|
||||
cachedSamplerBuffer = MemoryMarshal.Cast<byte, int>(_channel.MemoryManager.Physical.GetSpan(bounds.Address, (int)bounds.Size));
|
||||
cachedSamplerBufferIndex = samplerBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the total number of texture bindings used by all shader stages.
|
||||
/// </summary>
|
||||
/// <returns>The total amount of textures used</returns>
|
||||
private int GetTextureBindingsCount()
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _textureBindings.Length; i++)
|
||||
{
|
||||
if (_textureBindings[i] != null)
|
||||
{
|
||||
count += _textureBindings[i].Length;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the texture bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </summary>
|
||||
/// <param name="texturePool">The current texture pool</param>
|
||||
/// <param name="samplerPool">The current sampler pool</param>
|
||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param
|
||||
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialiation state, false otherwise</returns>
|
||||
private bool CommitTextureBindings(
|
||||
TexturePool texturePool,
|
||||
SamplerPool samplerPool,
|
||||
ShaderStage stage,
|
||||
int stageIndex,
|
||||
bool poolModified,
|
||||
ShaderSpecializationState specState)
|
||||
{
|
||||
int textureCount = _textureBindings[stageIndex].Length;
|
||||
if (textureCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (texturePool == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses textures, but texture pool was not set.");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
for (int index = 0; index < textureCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
int samplerId;
|
||||
|
||||
if (_samplerIndex == SamplerIndex.ViaHeaderIndex)
|
||||
{
|
||||
samplerId = textureId;
|
||||
}
|
||||
else
|
||||
{
|
||||
samplerId = TextureHandle.UnpackSamplerId(packedId);
|
||||
}
|
||||
|
||||
ref TextureState state = ref _textureState[bindingInfo.Binding];
|
||||
|
||||
if (!poolModified &&
|
||||
state.TextureHandle == textureId &&
|
||||
state.SamplerHandle == samplerId &&
|
||||
state.CachedTexture != null &&
|
||||
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence &&
|
||||
state.CachedSampler?.IsDisposed != true)
|
||||
{
|
||||
// The texture is already bound.
|
||||
state.CachedTexture.SynchronizeMemory();
|
||||
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, index, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTextureRebind, state.Sampler);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
state.TextureHandle = textureId;
|
||||
state.SamplerHandle = samplerId;
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref texturePool.GetForBinding(textureId, out Texture texture);
|
||||
|
||||
specStateMatches &= specState.MatchesTexture(stage, index, descriptor);
|
||||
|
||||
Sampler sampler = samplerPool?.Get(samplerId);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
ISampler hostSampler = sampler?.GetHostSampler(texture);
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, bindingInfo.Format, false);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool textureOrSamplerChanged = state.Texture != hostTexture || state.Sampler != hostSampler;
|
||||
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(texture, usageFlags, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
textureOrSamplerChanged = true;
|
||||
}
|
||||
|
||||
if (textureOrSamplerChanged)
|
||||
{
|
||||
state.Texture = hostTexture;
|
||||
state.Sampler = hostSampler;
|
||||
|
||||
_context.Renderer.Pipeline.SetTextureAndSampler(stage, bindingInfo.Binding, hostTexture, hostSampler);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.CachedSampler = sampler;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the image bindings are visible to the host GPU.
|
||||
/// Note: this actually performs the binding using the host graphics API.
|
||||
/// </summary>
|
||||
/// <param name="pool">The current texture pool</param>
|
||||
/// <param name="stage">The shader stage using the textures to be bound</param>
|
||||
/// <param name="stageIndex">The stage number of the specified shader stage</param>
|
||||
/// <param name="poolModified">True if either the texture or sampler pool was modified, false otherwise</param>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound images match the current shader specialiation state, false otherwise</returns>
|
||||
private bool CommitImageBindings(TexturePool pool, ShaderStage stage, int stageIndex, bool poolModified, ShaderSpecializationState specState)
|
||||
{
|
||||
int imageCount = _imageBindings[stageIndex].Length;
|
||||
if (imageCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pool == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Shader stage \"{stage}\" uses images, but texture pool was not set.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scales for images appear after the texture ones.
|
||||
int baseScaleIndex = _textureBindings[stageIndex].Length;
|
||||
|
||||
int cachedTextureBufferIndex = -1;
|
||||
int cachedSamplerBufferIndex = -1;
|
||||
ReadOnlySpan<int> cachedTextureBuffer = Span<int>.Empty;
|
||||
ReadOnlySpan<int> cachedSamplerBuffer = Span<int>.Empty;
|
||||
|
||||
bool specStateMatches = true;
|
||||
|
||||
for (int index = 0; index < imageCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
int scaleIndex = baseScaleIndex + index;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
UpdateCachedBuffer(stageIndex, ref cachedTextureBufferIndex, ref cachedSamplerBufferIndex, ref cachedTextureBuffer, ref cachedSamplerBuffer, textureBufferIndex, samplerBufferIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ref TextureState state = ref _imageState[bindingInfo.Binding];
|
||||
|
||||
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
|
||||
if (!poolModified &&
|
||||
state.TextureHandle == textureId &&
|
||||
state.CachedTexture != null &&
|
||||
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
|
||||
{
|
||||
Texture cachedTexture = state.CachedTexture;
|
||||
|
||||
// The texture is already bound.
|
||||
cachedTexture.SynchronizeMemory();
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
cachedTexture?.SignalModified();
|
||||
}
|
||||
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
||||
if (state.ImageFormat != format ||
|
||||
((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage)))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
state.TextureHandle = textureId;
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref pool.GetForBinding(textureId, out Texture texture);
|
||||
|
||||
specStateMatches &= specState.MatchesImage(stage, index, descriptor);
|
||||
|
||||
ITexture hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
if (hostTexture != null && texture.Target == Target.TextureBuffer)
|
||||
{
|
||||
// Ensure that the buffer texture is using the correct buffer as storage.
|
||||
// Buffers are frequently re-created to accomodate larger data, so we need to re-bind
|
||||
// to ensure we're not using a old buffer that was already deleted.
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
if (format == 0 && texture != null)
|
||||
{
|
||||
format = texture.Format;
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetBufferTextureStorage(stage, hostTexture, texture.Range.GetSubRange(0).Address, texture.Size, bindingInfo, format, true);
|
||||
|
||||
// Cache is not used for buffer texture, it must always rebind.
|
||||
state.CachedTexture = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isStore)
|
||||
{
|
||||
texture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 &&
|
||||
UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
state.Texture = hostTexture;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
if (format == 0 && texture != null)
|
||||
{
|
||||
format = texture.Format;
|
||||
}
|
||||
|
||||
state.ImageFormat = format;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
|
||||
}
|
||||
|
||||
state.CachedTexture = texture;
|
||||
state.InvalidatedSequence = texture?.InvalidatedSequence ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
return specStateMatches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor for a given texture handle.
|
||||
/// </summary>
|
||||
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="stageIndex">The stage number where the texture is bound</param>
|
||||
/// <param name="handle">The texture handle</param>
|
||||
/// <param name="cbufSlot">The texture handle's constant buffer slot</param>
|
||||
/// <returns>The texture descriptor for the specified texture</returns>
|
||||
public TextureDescriptor GetTextureDescriptor(
|
||||
ulong poolGpuVa,
|
||||
int bufferIndex,
|
||||
int maximumId,
|
||||
int stageIndex,
|
||||
int handle,
|
||||
int cbufSlot)
|
||||
{
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(cbufSlot, bufferIndex);
|
||||
|
||||
int packedId = ReadPackedId(stageIndex, handle, textureBufferIndex, samplerBufferIndex);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||
|
||||
TextureDescriptor descriptor;
|
||||
|
||||
if (texturePool.IsValidId(textureId))
|
||||
{
|
||||
descriptor = texturePool.GetDescriptor(textureId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ID is not valid, we just return a default descriptor with the most common state.
|
||||
// Since this is used for shader specialization, doing so might avoid the need for recompilations.
|
||||
descriptor = new TextureDescriptor();
|
||||
descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23;
|
||||
descriptor.Word5 |= 1u << 31; // Coords normalized.
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a packed texture and sampler ID (basically, the real texture handle)
|
||||
/// from the texture constant buffer.
|
||||
/// </summary>
|
||||
/// <param name="stageIndex">The number of the shader stage where the texture is bound</param>
|
||||
/// <param name="wordOffset">A word offset of the handle on the buffer (the "fake" shader handle)</param>
|
||||
/// <param name="textureBufferIndex">Index of the constant buffer holding the texture handles</param>
|
||||
/// <param name="samplerBufferIndex">Index of the constant buffer holding the sampler handles</param>
|
||||
/// <returns>The packed texture and sampler ID (the real texture handle)</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int ReadPackedId(int stageIndex, int wordOffset, int textureBufferIndex, int samplerBufferIndex)
|
||||
{
|
||||
(int textureWordOffset, int samplerWordOffset, TextureHandleType handleType) = TextureHandle.UnpackOffsets(wordOffset);
|
||||
|
||||
ulong textureBufferAddress = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(textureBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, textureBufferIndex);
|
||||
|
||||
int handle = textureBufferAddress != 0
|
||||
? _channel.MemoryManager.Physical.Read<int>(textureBufferAddress + (uint)textureWordOffset * 4)
|
||||
: 0;
|
||||
|
||||
// The "wordOffset" (which is really the immediate value used on texture instructions on the shader)
|
||||
// is a 13-bit value. However, in order to also support separate samplers and textures (which uses
|
||||
// bindless textures on the shader), we extend it with another value on the higher 16 bits with
|
||||
// another offset for the sampler.
|
||||
// The shader translator has code to detect separate texture and sampler uses with a bindless texture,
|
||||
// turn that into a regular texture access and produce those special handles with values on the higher 16 bits.
|
||||
if (handleType != TextureHandleType.CombinedSampler)
|
||||
{
|
||||
int samplerHandle;
|
||||
|
||||
if (handleType != TextureHandleType.SeparateConstantSamplerHandle)
|
||||
{
|
||||
ulong samplerBufferAddress = _isCompute
|
||||
? _channel.BufferManager.GetComputeUniformBufferAddress(samplerBufferIndex)
|
||||
: _channel.BufferManager.GetGraphicsUniformBufferAddress(stageIndex, samplerBufferIndex);
|
||||
|
||||
samplerHandle = samplerBufferAddress != 0
|
||||
? _channel.MemoryManager.Physical.Read<int>(samplerBufferAddress + (uint)samplerWordOffset * 4)
|
||||
: 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
samplerHandle = samplerWordOffset;
|
||||
}
|
||||
|
||||
if (handleType == TextureHandleType.SeparateSamplerId ||
|
||||
handleType == TextureHandleType.SeparateConstantSamplerHandle)
|
||||
{
|
||||
samplerHandle <<= 20;
|
||||
}
|
||||
|
||||
handle |= samplerHandle;
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture and sampler pool for the GPU virtual address that are currently set.
|
||||
/// </summary>
|
||||
/// <returns>The texture and sampler pools</returns>
|
||||
private (TexturePool, SamplerPool) GetPools()
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
TexturePool texturePool = _texturePool;
|
||||
SamplerPool samplerPool = _samplerPool;
|
||||
|
||||
if (texturePool == null)
|
||||
{
|
||||
ulong poolAddress = memoryManager.Translate(_texturePoolGpuVa);
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, _texturePoolMaximumId);
|
||||
_texturePool = texturePool;
|
||||
}
|
||||
}
|
||||
|
||||
if (samplerPool == null)
|
||||
{
|
||||
ulong poolAddress = memoryManager.Translate(_samplerPoolGpuVa);
|
||||
|
||||
if (poolAddress != MemoryManager.PteUnmapped)
|
||||
{
|
||||
samplerPool = _samplerPoolCache.FindOrCreate(_channel, poolAddress, _samplerPoolMaximumId);
|
||||
_samplerPool = samplerPool;
|
||||
}
|
||||
}
|
||||
|
||||
return (texturePool, samplerPool);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This should be called if the memory mappings change, to ensure the correct pools are being used.
|
||||
/// </remarks>
|
||||
public void ReloadPools()
|
||||
{
|
||||
_samplerPool = null;
|
||||
_texturePool = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
public void Rebind()
|
||||
{
|
||||
Array.Clear(_textureState);
|
||||
Array.Clear(_imageState);
|
||||
}
|
||||
}
|
||||
}
|
||||
1180
src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
Normal file
1180
src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
Normal file
File diff suppressed because it is too large
Load Diff
911
src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
Normal file
911
src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
Normal file
@@ -0,0 +1,911 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture format compatibility checks.
|
||||
/// </summary>
|
||||
static class TextureCompatibility
|
||||
{
|
||||
private enum FormatClass
|
||||
{
|
||||
Unclassified,
|
||||
Bc1Rgba,
|
||||
Bc2,
|
||||
Bc3,
|
||||
Bc4,
|
||||
Bc5,
|
||||
Bc6,
|
||||
Bc7,
|
||||
Etc2Rgb,
|
||||
Etc2Rgba,
|
||||
Astc4x4,
|
||||
Astc5x4,
|
||||
Astc5x5,
|
||||
Astc6x5,
|
||||
Astc6x6,
|
||||
Astc8x5,
|
||||
Astc8x6,
|
||||
Astc8x8,
|
||||
Astc10x5,
|
||||
Astc10x6,
|
||||
Astc10x8,
|
||||
Astc10x10,
|
||||
Astc12x10,
|
||||
Astc12x12
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a format is host incompatible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Host incompatible formats can't be used directly, the texture data needs to be converted
|
||||
/// to a compatible format first.
|
||||
/// </remarks>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>True if the format is incompatible, false otherwise</returns>
|
||||
public static bool IsFormatHostIncompatible(TextureInfo info, Capabilities caps)
|
||||
{
|
||||
Format originalFormat = info.FormatInfo.Format;
|
||||
return ToHostCompatibleFormat(info, caps).Format != originalFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a incompatible format to a host compatible format, or return the format directly
|
||||
/// if it is already host compatible.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to convert a incompatible compressed format to the decompressor
|
||||
/// output format.
|
||||
/// </remarks>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>A host compatible format</returns>
|
||||
public static FormatInfo ToHostCompatibleFormat(TextureInfo info, Capabilities caps)
|
||||
{
|
||||
// The host API does not support those compressed formats.
|
||||
// We assume software decompression will be done for those textures,
|
||||
// and so we adjust the format here to match the decompressor output.
|
||||
|
||||
if (!caps.SupportsAstcCompression)
|
||||
{
|
||||
if (info.FormatInfo.Format.IsAstcUnorm())
|
||||
{
|
||||
return GraphicsConfig.EnableTextureRecompression
|
||||
? new FormatInfo(Format.Bc7Unorm, 4, 4, 16, 4)
|
||||
: new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
else if (info.FormatInfo.Format.IsAstcSrgb())
|
||||
{
|
||||
return GraphicsConfig.EnableTextureRecompression
|
||||
? new FormatInfo(Format.Bc7Srgb, 4, 4, 16, 4)
|
||||
: new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (!HostSupportsBcFormat(info.FormatInfo.Format, info.Target, caps))
|
||||
{
|
||||
switch (info.FormatInfo.Format)
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc7Srgb:
|
||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
case Format.Bc1RgbaUnorm:
|
||||
case Format.Bc2Unorm:
|
||||
case Format.Bc3Unorm:
|
||||
case Format.Bc7Unorm:
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
case Format.Bc4Unorm:
|
||||
return new FormatInfo(Format.R8Unorm, 1, 1, 1, 1);
|
||||
case Format.Bc4Snorm:
|
||||
return new FormatInfo(Format.R8Snorm, 1, 1, 1, 1);
|
||||
case Format.Bc5Unorm:
|
||||
return new FormatInfo(Format.R8G8Unorm, 1, 1, 2, 2);
|
||||
case Format.Bc5Snorm:
|
||||
return new FormatInfo(Format.R8G8Snorm, 1, 1, 2, 2);
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
return new FormatInfo(Format.R16G16B16A16Float, 1, 1, 8, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (!caps.SupportsEtc2Compression)
|
||||
{
|
||||
switch (info.FormatInfo.Format)
|
||||
{
|
||||
case Format.Etc2RgbaSrgb:
|
||||
case Format.Etc2RgbPtaSrgb:
|
||||
case Format.Etc2RgbSrgb:
|
||||
return new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4, 4);
|
||||
case Format.Etc2RgbaUnorm:
|
||||
case Format.Etc2RgbPtaUnorm:
|
||||
case Format.Etc2RgbUnorm:
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (!caps.SupportsR4G4Format && info.FormatInfo.Format == Format.R4G4Unorm)
|
||||
{
|
||||
if (caps.SupportsR4G4B4A4Format)
|
||||
{
|
||||
return new FormatInfo(Format.R4G4B4A4Unorm, 1, 1, 2, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if (info.FormatInfo.Format == Format.R4G4B4A4Unorm)
|
||||
{
|
||||
if (!caps.SupportsR4G4B4A4Format)
|
||||
{
|
||||
return new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
}
|
||||
else if (!caps.Supports5BitComponentFormat && info.FormatInfo.Format.Is16BitPacked())
|
||||
{
|
||||
return new FormatInfo(info.FormatInfo.Format.IsBgr() ? Format.B8G8R8A8Unorm : Format.R8G8B8A8Unorm, 1, 1, 4, 4);
|
||||
}
|
||||
|
||||
return info.FormatInfo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the host API supports a given texture compression format of the BC family.
|
||||
/// </summary>
|
||||
/// <param name="format">BC format to be checked</param>
|
||||
/// <param name="target">Target usage of the texture</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture host supports the format with the given target usage, false otherwise</returns>
|
||||
public static bool HostSupportsBcFormat(Format format, Target target, Capabilities caps)
|
||||
{
|
||||
bool not3DOr3DCompressionSupported = target != Target.Texture3D || caps.Supports3DTextureCompression;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
return caps.SupportsBc123Compression && not3DOr3DCompressionSupported;
|
||||
case Format.Bc4Unorm:
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
case Format.Bc5Snorm:
|
||||
return caps.SupportsBc45Compression && not3DOr3DCompressionSupported;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
return caps.SupportsBc67Compression && not3DOr3DCompressionSupported;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a texture can flush its data back to guest memory.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information</param>
|
||||
/// <param name="caps">Host GPU Capabilities</param>
|
||||
/// <returns>True if the texture can flush, false otherwise</returns>
|
||||
public static bool CanTextureFlush(TextureInfo info, Capabilities caps)
|
||||
{
|
||||
if (IsFormatHostIncompatible(info, caps))
|
||||
{
|
||||
return false; // Flushing this format is not supported, as it may have been converted to another host format.
|
||||
}
|
||||
|
||||
if (info.Target == Target.Texture2DMultisample ||
|
||||
info.Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return false; // Flushing multisample textures is not supported, the host does not allow getting their data.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture format matches with the specified texture information.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <param name="forSampler">Indicates that the texture will be used for shader sampling</param>
|
||||
/// <param name="forCopy">Indicates that the texture will be used as copy source or target</param>
|
||||
/// <returns>A value indicating how well the formats match</returns>
|
||||
public static TextureMatchQuality FormatMatches(TextureInfo lhs, TextureInfo rhs, bool forSampler, bool forCopy)
|
||||
{
|
||||
// D32F and R32F texture have the same representation internally,
|
||||
// however the R32F format is used to sample from depth textures.
|
||||
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || forCopy))
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
||||
if (forCopy)
|
||||
{
|
||||
// The 2D engine does not support depth-stencil formats, so it will instead
|
||||
// use equivalent color formats. We must also consider them as compatible.
|
||||
if (lhs.FormatInfo.Format == Format.S8Uint && rhs.FormatInfo.Format == Format.R8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
||||
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
||||
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
}
|
||||
|
||||
return lhs.FormatInfo.Format == rhs.FormatInfo.Format ? TextureMatchQuality.Perfect : TextureMatchQuality.NoMatch;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture layout specified matches with this texture layout.
|
||||
/// The layout information is composed of the Stride for linear textures, or GOB block size
|
||||
/// for block linear textures.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <returns>True if the layout matches, false otherwise</returns>
|
||||
public static bool LayoutMatches(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
if (lhs.IsLinear != rhs.IsLinear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For linear textures, gob block sizes are ignored.
|
||||
// For block linear textures, the stride is ignored.
|
||||
if (rhs.IsLinear)
|
||||
{
|
||||
return lhs.Stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return lhs.GobBlocksInY == rhs.GobBlocksInY &&
|
||||
lhs.GobBlocksInZ == rhs.GobBlocksInZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtain the minimum compatibility level of two provided view compatibility results.
|
||||
/// </summary>
|
||||
/// <param name="first">The first compatibility level</param>
|
||||
/// <param name="second">The second compatibility level</param>
|
||||
/// <returns>The minimum compatibility level of two provided view compatibility results</returns>
|
||||
public static TextureViewCompatibility PropagateViewCompatibility(TextureViewCompatibility first, TextureViewCompatibility second)
|
||||
{
|
||||
if (first == TextureViewCompatibility.Incompatible || second == TextureViewCompatibility.Incompatible)
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
else if (first == TextureViewCompatibility.LayoutIncompatible || second == TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
else if (first == TextureViewCompatibility.CopyOnly || second == TextureViewCompatibility.CopyOnly)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.Full;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the sizes of two texture levels are copy compatible.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to match against</param>
|
||||
/// <param name="lhsLevel">Mipmap level of the texture view in relation to this texture</param>
|
||||
/// <param name="rhsLevel">Mipmap level of the texture view in relation to the second texture</param>
|
||||
/// <returns>True if both levels are view compatible</returns>
|
||||
public static bool CopySizeMatches(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
|
||||
{
|
||||
Size size = GetAlignedSize(lhs, lhsLevel);
|
||||
|
||||
Size otherSize = GetAlignedSize(rhs, rhsLevel);
|
||||
|
||||
if (size.Width == otherSize.Width && size.Height == otherSize.Height)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (lhs.IsLinear && rhs.IsLinear)
|
||||
{
|
||||
// Copy between linear textures with matching stride.
|
||||
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> lhsLevel), Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the sizes of two given textures are view compatible.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to match against</param>
|
||||
/// <param name="exact">Indicates if the sizes must be exactly equal</param>
|
||||
/// <param name="level">Mipmap level of the texture view in relation to this texture</param>
|
||||
/// <returns>The view compatibility level of the view sizes</returns>
|
||||
public static TextureViewCompatibility ViewSizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact, int level)
|
||||
{
|
||||
Size lhsAlignedSize = GetAlignedSize(lhs, level);
|
||||
Size rhsAlignedSize = GetAlignedSize(rhs);
|
||||
|
||||
Size lhsSize = GetSizeInBlocks(lhs, level);
|
||||
Size rhsSize = GetSizeInBlocks(rhs);
|
||||
|
||||
bool alignedWidthMatches = lhsAlignedSize.Width == rhsAlignedSize.Width;
|
||||
|
||||
if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo))
|
||||
{
|
||||
alignedWidthMatches = lhsSize.Width * lhs.FormatInfo.BytesPerPixel == rhsSize.Width * rhs.FormatInfo.BytesPerPixel;
|
||||
}
|
||||
|
||||
TextureViewCompatibility result = TextureViewCompatibility.Full;
|
||||
|
||||
// For copies, we can copy a subset of the 3D texture slices,
|
||||
// so the depth may be different in this case.
|
||||
if (rhs.Target == Target.Texture3D && lhsSize.Depth != rhsSize.Depth)
|
||||
{
|
||||
result = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
// Some APIs align the width for copy and render target textures,
|
||||
// so the width may not match in this case for different uses of the same texture.
|
||||
// To account for this, we compare the aligned width here.
|
||||
// We expect height to always match exactly, if the texture is the same.
|
||||
if (alignedWidthMatches && lhsSize.Height == rhsSize.Height)
|
||||
{
|
||||
return (exact && lhsSize.Width != rhsSize.Width) || lhsSize.Width < rhsSize.Width
|
||||
? TextureViewCompatibility.CopyOnly
|
||||
: result;
|
||||
}
|
||||
else if (lhs.IsLinear && rhs.IsLinear && lhsSize.Height == rhsSize.Height)
|
||||
{
|
||||
// Copy between linear textures with matching stride.
|
||||
int stride = BitUtils.AlignUp(Math.Max(1, lhs.Stride >> level), Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the potential child texture fits within the level and layer bounds of the parent.
|
||||
/// </summary>
|
||||
/// <param name="parent">Texture information for the parent</param>
|
||||
/// <param name="child">Texture information for the child</param>
|
||||
/// <param name="layer">Base layer of the child texture</param>
|
||||
/// <param name="level">Base level of the child texture</param>
|
||||
/// <returns>Full compatiblity if the child's layer and level count fit within the parent, incompatible otherwise</returns>
|
||||
public static TextureViewCompatibility ViewSubImagesInBounds(TextureInfo parent, TextureInfo child, int layer, int level)
|
||||
{
|
||||
if (level + child.Levels <= parent.Levels &&
|
||||
layer + child.GetSlices() <= parent.GetSlices())
|
||||
{
|
||||
return TextureViewCompatibility.Full;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.LayoutIncompatible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture sizes of the supplied texture informations match.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <param name="exact">Indicates if the size must be exactly equal between the textures, or if <paramref name="rhs"/> is allowed to be larger</param>
|
||||
/// <returns>True if the sizes matches, false otherwise</returns>
|
||||
public static bool SizeMatches(TextureInfo lhs, TextureInfo rhs, bool exact)
|
||||
{
|
||||
if (lhs.GetLayers() != rhs.GetLayers())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Size lhsSize = GetSizeInBlocks(lhs);
|
||||
Size rhsSize = GetSizeInBlocks(rhs);
|
||||
|
||||
if (exact || lhs.IsLinear || rhs.IsLinear)
|
||||
{
|
||||
return lhsSize.Width == rhsSize.Width &&
|
||||
lhsSize.Height == rhsSize.Height &&
|
||||
lhsSize.Depth == rhsSize.Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
Size lhsAlignedSize = GetAlignedSize(lhs);
|
||||
Size rhsAlignedSize = GetAlignedSize(rhs);
|
||||
|
||||
return lhsAlignedSize.Width == rhsAlignedSize.Width &&
|
||||
lhsSize.Width >= rhsSize.Width &&
|
||||
lhsSize.Height == rhsSize.Height &&
|
||||
lhsSize.Depth == rhsSize.Depth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned sizes for the given dimensions, using the specified texture information.
|
||||
/// The alignment depends on the texture layout and format bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information to calculate the aligned size from</param>
|
||||
/// <param name="width">The width to be aligned</param>
|
||||
/// <param name="height">The height to be aligned</param>
|
||||
/// <param name="depth">The depth to be aligned</param>
|
||||
/// <returns>The aligned texture size</returns>
|
||||
private static Size GetAlignedSize(TextureInfo info, int width, int height, int depth)
|
||||
{
|
||||
if (info.IsLinear)
|
||||
{
|
||||
return SizeCalculator.GetLinearAlignedSize(
|
||||
width,
|
||||
height,
|
||||
info.FormatInfo.BlockWidth,
|
||||
info.FormatInfo.BlockHeight,
|
||||
info.FormatInfo.BytesPerPixel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return SizeCalculator.GetBlockLinearAlignedSize(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
info.FormatInfo.BlockWidth,
|
||||
info.FormatInfo.BlockHeight,
|
||||
info.FormatInfo.BytesPerPixel,
|
||||
info.GobBlocksInY,
|
||||
info.GobBlocksInZ,
|
||||
info.GobBlocksInTileX);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the aligned sizes of the specified texture information.
|
||||
/// The alignment depends on the texture layout and format bytes per pixel.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information to calculate the aligned size from</param>
|
||||
/// <param name="level">Mipmap level for texture views</param>
|
||||
/// <returns>The aligned texture size</returns>
|
||||
public static Size GetAlignedSize(TextureInfo info, int level = 0)
|
||||
{
|
||||
int width = Math.Max(1, info.Width >> level);
|
||||
int height = Math.Max(1, info.Height >> level);
|
||||
int depth = Math.Max(1, info.GetDepth() >> level);
|
||||
|
||||
return GetAlignedSize(info, width, height, depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size in blocks for the given texture information.
|
||||
/// For non-compressed formats, that's the same as the regular size.
|
||||
/// </summary>
|
||||
/// <param name="info">Texture information to calculate the aligned size from</param>
|
||||
/// <param name="level">Mipmap level for texture views</param>
|
||||
/// <returns>The texture size in blocks</returns>
|
||||
public static Size GetSizeInBlocks(TextureInfo info, int level = 0)
|
||||
{
|
||||
int width = Math.Max(1, info.Width >> level);
|
||||
int height = Math.Max(1, info.Height >> level);
|
||||
int depth = Math.Max(1, info.GetDepth() >> level);
|
||||
|
||||
return new Size(
|
||||
BitUtils.DivRoundUp(width, info.FormatInfo.BlockWidth),
|
||||
BitUtils.DivRoundUp(height, info.FormatInfo.BlockHeight),
|
||||
depth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if it's possible to create a view with the layout of the second texture information from the first.
|
||||
/// The layout information is composed of the Stride for linear textures, or GOB block size
|
||||
/// for block linear textures.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to compare against</param>
|
||||
/// <param name="level">Start level of the texture view, in relation with the first texture</param>
|
||||
/// <returns>True if the layout is compatible, false otherwise</returns>
|
||||
public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int level)
|
||||
{
|
||||
if (lhs.IsLinear != rhs.IsLinear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For linear textures, gob block sizes are ignored.
|
||||
// For block linear textures, the stride is ignored.
|
||||
if (rhs.IsLinear)
|
||||
{
|
||||
int stride = Math.Max(1, lhs.Stride >> level);
|
||||
stride = BitUtils.AlignUp(stride, Constants.StrideAlignment);
|
||||
|
||||
return stride == rhs.Stride;
|
||||
}
|
||||
else
|
||||
{
|
||||
int height = Math.Max(1, lhs.Height >> level);
|
||||
int depth = Math.Max(1, lhs.GetDepth() >> level);
|
||||
|
||||
(int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
|
||||
height,
|
||||
depth,
|
||||
lhs.FormatInfo.BlockHeight,
|
||||
lhs.GobBlocksInY,
|
||||
lhs.GobBlocksInZ);
|
||||
|
||||
return gobBlocksInY == rhs.GobBlocksInY &&
|
||||
gobBlocksInZ == rhs.GobBlocksInZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if it's possible to create a view with the layout of the second texture information from the first.
|
||||
/// The layout information is composed of the Stride for linear textures, or GOB block size
|
||||
/// for block linear textures.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view to compare against</param>
|
||||
/// <param name="lhsLevel">Start level of the texture view, in relation with the first texture</param>
|
||||
/// <param name="rhsLevel">Start level of the texture view, in relation with the second texture</param>
|
||||
/// <returns>True if the layout is compatible, false otherwise</returns>
|
||||
public static bool ViewLayoutCompatible(TextureInfo lhs, TextureInfo rhs, int lhsLevel, int rhsLevel)
|
||||
{
|
||||
if (lhs.IsLinear != rhs.IsLinear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// For linear textures, gob block sizes are ignored.
|
||||
// For block linear textures, the stride is ignored.
|
||||
if (rhs.IsLinear)
|
||||
{
|
||||
int lhsStride = Math.Max(1, lhs.Stride >> lhsLevel);
|
||||
lhsStride = BitUtils.AlignUp(lhsStride, Constants.StrideAlignment);
|
||||
|
||||
int rhsStride = Math.Max(1, rhs.Stride >> rhsLevel);
|
||||
rhsStride = BitUtils.AlignUp(rhsStride, Constants.StrideAlignment);
|
||||
|
||||
return lhsStride == rhsStride;
|
||||
}
|
||||
else
|
||||
{
|
||||
int lhsHeight = Math.Max(1, lhs.Height >> lhsLevel);
|
||||
int lhsDepth = Math.Max(1, lhs.GetDepth() >> lhsLevel);
|
||||
|
||||
(int lhsGobBlocksInY, int lhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
|
||||
lhsHeight,
|
||||
lhsDepth,
|
||||
lhs.FormatInfo.BlockHeight,
|
||||
lhs.GobBlocksInY,
|
||||
lhs.GobBlocksInZ);
|
||||
|
||||
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
|
||||
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
|
||||
|
||||
(int rhsGobBlocksInY, int rhsGobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
|
||||
rhsHeight,
|
||||
rhsDepth,
|
||||
rhs.FormatInfo.BlockHeight,
|
||||
rhs.GobBlocksInY,
|
||||
rhs.GobBlocksInZ);
|
||||
|
||||
return lhsGobBlocksInY == rhsGobBlocksInY &&
|
||||
lhsGobBlocksInZ == rhsGobBlocksInZ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view format of the first texture format is compatible with the format of the second.
|
||||
/// In general, the formats are considered compatible if the bytes per pixel values are equal,
|
||||
/// but there are more complex rules for some formats, like compressed or depth-stencil formats.
|
||||
/// This follows the host API copy compatibility rules.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param>
|
||||
/// <param name="rhs">Texture information of the texture view</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>The view compatibility level of the texture formats</returns>
|
||||
public static TextureViewCompatibility ViewFormatCompatible(TextureInfo lhs, TextureInfo rhs, Capabilities caps)
|
||||
{
|
||||
FormatInfo lhsFormat = lhs.FormatInfo;
|
||||
FormatInfo rhsFormat = rhs.FormatInfo;
|
||||
|
||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||
{
|
||||
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||
{
|
||||
return lhsFormat.Format == rhsFormat.Format ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
if (lhsFormat.IsCompressed && rhsFormat.IsCompressed)
|
||||
{
|
||||
FormatClass lhsClass = GetFormatClass(lhsFormat.Format);
|
||||
FormatClass rhsClass = GetFormatClass(rhsFormat.Format);
|
||||
|
||||
return lhsClass == rhsClass ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
else if (lhsFormat.BytesPerPixel == rhsFormat.BytesPerPixel)
|
||||
{
|
||||
return lhs.FormatInfo.IsCompressed == rhs.FormatInfo.IsCompressed
|
||||
? TextureViewCompatibility.Full
|
||||
: TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else if (IsIncompatibleFormatAliasingAllowed(lhsFormat, rhsFormat))
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
|
||||
/// using copy dependencies.
|
||||
/// </summary>
|
||||
/// <param name="lhsFormat">Format information of the first texture</param
|
||||
/// <param name="rhsFormat">Format information of the second texture</param>
|
||||
/// <returns>True if aliasing should be allowed, false otherwise</returns>
|
||||
private static bool IsIncompatibleFormatAliasingAllowed(FormatInfo lhsFormat, FormatInfo rhsFormat)
|
||||
{
|
||||
// Some games will try to alias textures with incompatible foramts, with different BPP (bytes per pixel).
|
||||
// We allow that in some cases as long Width * BPP is equal on both textures.
|
||||
// This is very conservative right now as we want to avoid copies as much as possible,
|
||||
// so we only consider the formats we have seen being aliased.
|
||||
|
||||
if (rhsFormat.BytesPerPixel < lhsFormat.BytesPerPixel)
|
||||
{
|
||||
(lhsFormat, rhsFormat) = (rhsFormat, lhsFormat);
|
||||
}
|
||||
|
||||
return lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the target of the first texture view information is compatible with the target of the second texture view information.
|
||||
/// This follows the host API target compatibility rules.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information of the texture view</param
|
||||
/// <param name="rhs">Texture information of the texture view</param>
|
||||
/// <param name="caps">Host GPU capabilities</param>
|
||||
/// <returns>True if the targets are compatible, false otherwise</returns>
|
||||
public static TextureViewCompatibility ViewTargetCompatible(TextureInfo lhs, TextureInfo rhs, ref Capabilities caps)
|
||||
{
|
||||
bool result = false;
|
||||
switch (lhs.Target)
|
||||
{
|
||||
case Target.Texture1D:
|
||||
case Target.Texture1DArray:
|
||||
result = rhs.Target == Target.Texture1D ||
|
||||
rhs.Target == Target.Texture1DArray;
|
||||
break;
|
||||
|
||||
case Target.Texture2D:
|
||||
result = rhs.Target == Target.Texture2D ||
|
||||
rhs.Target == Target.Texture2DArray;
|
||||
break;
|
||||
|
||||
case Target.Texture2DArray:
|
||||
result = rhs.Target == Target.Texture2D ||
|
||||
rhs.Target == Target.Texture2DArray;
|
||||
|
||||
if (rhs.Target == Target.Cubemap || rhs.Target == Target.CubemapArray)
|
||||
{
|
||||
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
break;
|
||||
case Target.Cubemap:
|
||||
case Target.CubemapArray:
|
||||
result = rhs.Target == Target.Cubemap ||
|
||||
rhs.Target == Target.CubemapArray;
|
||||
|
||||
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
|
||||
{
|
||||
return caps.SupportsCubemapView ? TextureViewCompatibility.Full : TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
break;
|
||||
case Target.Texture2DMultisample:
|
||||
case Target.Texture2DMultisampleArray:
|
||||
if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
result = rhs.Target == Target.Texture2DMultisample ||
|
||||
rhs.Target == Target.Texture2DMultisampleArray;
|
||||
break;
|
||||
|
||||
case Target.Texture3D:
|
||||
if (rhs.Target == Target.Texture2D)
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
|
||||
result = rhs.Target == Target.Texture3D;
|
||||
break;
|
||||
}
|
||||
|
||||
return result ? TextureViewCompatibility.Full : TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a swizzle component in two textures functionally match, taking into account if the components are defined.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <param name="swizzleLhs">Swizzle component for the first texture</param>
|
||||
/// <param name="swizzleRhs">Swizzle component for the second texture</param>
|
||||
/// <param name="component">Component index, starting at 0 for red</param>
|
||||
/// <returns>True if the swizzle components functionally match, false othersize</returns>
|
||||
private static bool SwizzleComponentMatches(TextureInfo lhs, TextureInfo rhs, SwizzleComponent swizzleLhs, SwizzleComponent swizzleRhs, int component)
|
||||
{
|
||||
int lhsComponents = lhs.FormatInfo.Components;
|
||||
int rhsComponents = rhs.FormatInfo.Components;
|
||||
|
||||
if (lhsComponents == 4 && rhsComponents == 4)
|
||||
{
|
||||
return swizzleLhs == swizzleRhs;
|
||||
}
|
||||
|
||||
// Swizzles after the number of components a format defines are "undefined".
|
||||
// We allow these to not be equal under certain circumstances.
|
||||
// This can only happen when there are less than 4 components in a format.
|
||||
// It tends to happen when float depth textures are sampled.
|
||||
|
||||
bool lhsDefined = (swizzleLhs - SwizzleComponent.Red) < lhsComponents;
|
||||
bool rhsDefined = (swizzleRhs - SwizzleComponent.Red) < rhsComponents;
|
||||
|
||||
if (lhsDefined == rhsDefined)
|
||||
{
|
||||
// If both are undefined, return true. Otherwise just check if they're equal.
|
||||
return lhsDefined ? swizzleLhs == swizzleRhs : true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SwizzleComponent defined = lhsDefined ? swizzleLhs : swizzleRhs;
|
||||
SwizzleComponent undefined = lhsDefined ? swizzleRhs : swizzleLhs;
|
||||
|
||||
// Undefined swizzle can be matched by a forced value (0, 1), exact equality, or expected value.
|
||||
// For example, R___ matches R001, RGBA but not RBGA.
|
||||
return defined == undefined || defined < SwizzleComponent.Red || defined == SwizzleComponent.Red + component;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the texture shader sampling parameters of two texture informations match.
|
||||
/// </summary>
|
||||
/// <param name="lhs">Texture information to compare</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <returns>True if the texture shader sampling parameters matches, false otherwise</returns>
|
||||
public static bool SamplerParamsMatches(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
return lhs.DepthStencilMode == rhs.DepthStencilMode &&
|
||||
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleR, rhs.SwizzleR, 0) &&
|
||||
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleG, rhs.SwizzleG, 1) &&
|
||||
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleB, rhs.SwizzleB, 2) &&
|
||||
SwizzleComponentMatches(lhs, rhs, lhs.SwizzleA, rhs.SwizzleA, 3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the texture target and samples count (for multisampled textures) matches.
|
||||
/// </summary>
|
||||
/// <param name="first">Texture information to compare with</param>
|
||||
/// <param name="rhs">Texture information to compare with</param>
|
||||
/// <returns>True if the texture target and samples count matches, false otherwise</returns>
|
||||
public static bool TargetAndSamplesCompatible(TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
return lhs.Target == rhs.Target &&
|
||||
lhs.SamplesInX == rhs.SamplesInX &&
|
||||
lhs.SamplesInY == rhs.SamplesInY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture format class, for compressed textures, or Unclassified otherwise.
|
||||
/// </summary>
|
||||
/// <param name="format">The format</param>
|
||||
/// <returns>Format class</returns>
|
||||
private static FormatClass GetFormatClass(Format format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case Format.Bc1RgbaSrgb:
|
||||
case Format.Bc1RgbaUnorm:
|
||||
return FormatClass.Bc1Rgba;
|
||||
case Format.Bc2Srgb:
|
||||
case Format.Bc2Unorm:
|
||||
return FormatClass.Bc2;
|
||||
case Format.Bc3Srgb:
|
||||
case Format.Bc3Unorm:
|
||||
return FormatClass.Bc3;
|
||||
case Format.Bc4Snorm:
|
||||
case Format.Bc4Unorm:
|
||||
return FormatClass.Bc4;
|
||||
case Format.Bc5Snorm:
|
||||
case Format.Bc5Unorm:
|
||||
return FormatClass.Bc5;
|
||||
case Format.Bc6HSfloat:
|
||||
case Format.Bc6HUfloat:
|
||||
return FormatClass.Bc6;
|
||||
case Format.Bc7Srgb:
|
||||
case Format.Bc7Unorm:
|
||||
return FormatClass.Bc7;
|
||||
case Format.Etc2RgbSrgb:
|
||||
case Format.Etc2RgbUnorm:
|
||||
return FormatClass.Etc2Rgb;
|
||||
case Format.Etc2RgbaSrgb:
|
||||
case Format.Etc2RgbaUnorm:
|
||||
return FormatClass.Etc2Rgba;
|
||||
case Format.Astc4x4Srgb:
|
||||
case Format.Astc4x4Unorm:
|
||||
return FormatClass.Astc4x4;
|
||||
case Format.Astc5x4Srgb:
|
||||
case Format.Astc5x4Unorm:
|
||||
return FormatClass.Astc5x4;
|
||||
case Format.Astc5x5Srgb:
|
||||
case Format.Astc5x5Unorm:
|
||||
return FormatClass.Astc5x5;
|
||||
case Format.Astc6x5Srgb:
|
||||
case Format.Astc6x5Unorm:
|
||||
return FormatClass.Astc6x5;
|
||||
case Format.Astc6x6Srgb:
|
||||
case Format.Astc6x6Unorm:
|
||||
return FormatClass.Astc6x6;
|
||||
case Format.Astc8x5Srgb:
|
||||
case Format.Astc8x5Unorm:
|
||||
return FormatClass.Astc8x5;
|
||||
case Format.Astc8x6Srgb:
|
||||
case Format.Astc8x6Unorm:
|
||||
return FormatClass.Astc8x6;
|
||||
case Format.Astc8x8Srgb:
|
||||
case Format.Astc8x8Unorm:
|
||||
return FormatClass.Astc8x8;
|
||||
case Format.Astc10x5Srgb:
|
||||
case Format.Astc10x5Unorm:
|
||||
return FormatClass.Astc10x5;
|
||||
case Format.Astc10x6Srgb:
|
||||
case Format.Astc10x6Unorm:
|
||||
return FormatClass.Astc10x6;
|
||||
case Format.Astc10x8Srgb:
|
||||
case Format.Astc10x8Unorm:
|
||||
return FormatClass.Astc10x8;
|
||||
case Format.Astc10x10Srgb:
|
||||
case Format.Astc10x10Unorm:
|
||||
return FormatClass.Astc10x10;
|
||||
case Format.Astc12x10Srgb:
|
||||
case Format.Astc12x10Unorm:
|
||||
return FormatClass.Astc12x10;
|
||||
case Format.Astc12x12Srgb:
|
||||
case Format.Astc12x12Unorm:
|
||||
return FormatClass.Astc12x12;
|
||||
}
|
||||
|
||||
return FormatClass.Unclassified;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
Normal file
43
src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture swizzle color component.
|
||||
/// </summary>
|
||||
enum TextureComponent
|
||||
{
|
||||
Zero = 0,
|
||||
Red = 2,
|
||||
Green = 3,
|
||||
Blue = 4,
|
||||
Alpha = 5,
|
||||
OneSI = 6,
|
||||
OneF = 7
|
||||
}
|
||||
|
||||
static class TextureComponentConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the texture swizzle color component enum to the respective Graphics Abstraction Layer enum.
|
||||
/// </summary>
|
||||
/// <param name="component">Texture swizzle color component</param>
|
||||
/// <returns>Converted enum</returns>
|
||||
public static SwizzleComponent Convert(this TextureComponent component)
|
||||
{
|
||||
switch (component)
|
||||
{
|
||||
case TextureComponent.Zero: return SwizzleComponent.Zero;
|
||||
case TextureComponent.Red: return SwizzleComponent.Red;
|
||||
case TextureComponent.Green: return SwizzleComponent.Green;
|
||||
case TextureComponent.Blue: return SwizzleComponent.Blue;
|
||||
case TextureComponent.Alpha: return SwizzleComponent.Alpha;
|
||||
case TextureComponent.OneSI:
|
||||
case TextureComponent.OneF:
|
||||
return SwizzleComponent.One;
|
||||
}
|
||||
|
||||
return SwizzleComponent.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
Normal file
37
src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// One side of a two-way dependency between one texture view and another.
|
||||
/// Contains a reference to the handle owning the dependency, and the other dependency.
|
||||
/// </summary>
|
||||
class TextureDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle that owns this dependency.
|
||||
/// </summary>
|
||||
public TextureGroupHandle Handle;
|
||||
|
||||
/// <summary>
|
||||
/// The other dependency linked to this one, which belongs to another handle.
|
||||
/// </summary>
|
||||
public TextureDependency Other;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new texture dependency.
|
||||
/// </summary>
|
||||
/// <param name="handle">The handle that owns the dependency</param>
|
||||
public TextureDependency(TextureGroupHandle handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the owner of this dependency has been modified,
|
||||
/// meaning that the other dependency's handle must defer a copy from it.
|
||||
/// </summary>
|
||||
public void SignalModified()
|
||||
{
|
||||
Other.Handle.DeferCopy(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
273
src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
Normal file
273
src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
|
||||
/// </summary>
|
||||
struct TextureDescriptor : ITextureDescriptor, IEquatable<TextureDescriptor>
|
||||
{
|
||||
#pragma warning disable CS0649
|
||||
public uint Word0;
|
||||
public uint Word1;
|
||||
public uint Word2;
|
||||
public uint Word3;
|
||||
public uint Word4;
|
||||
public uint Word5;
|
||||
public uint Word6;
|
||||
public uint Word7;
|
||||
#pragma warning restore CS0649
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks Maxwell texture format integer.
|
||||
/// </summary>
|
||||
/// <returns>The texture format integer</returns>
|
||||
public uint UnpackFormat()
|
||||
{
|
||||
return Word0 & 0x8007ffff;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the swizzle component for the texture red color channel.
|
||||
/// </summary>
|
||||
/// <returns>The swizzle component</returns>
|
||||
public TextureComponent UnpackSwizzleR()
|
||||
{
|
||||
return(TextureComponent)((Word0 >> 19) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the swizzle component for the texture green color channel.
|
||||
/// </summary>
|
||||
/// <returns>The swizzle component</returns>
|
||||
public TextureComponent UnpackSwizzleG()
|
||||
{
|
||||
return(TextureComponent)((Word0 >> 22) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the swizzle component for the texture blue color channel.
|
||||
/// </summary>
|
||||
/// <returns>The swizzle component</returns>
|
||||
public TextureComponent UnpackSwizzleB()
|
||||
{
|
||||
return(TextureComponent)((Word0 >> 25) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the swizzle component for the texture alpha color channel.
|
||||
/// </summary>
|
||||
/// <returns>The swizzle component</returns>
|
||||
public TextureComponent UnpackSwizzleA()
|
||||
{
|
||||
return(TextureComponent)((Word0 >> 28) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the 40-bits texture GPU virtual address.
|
||||
/// </summary>
|
||||
/// <returns>The GPU virtual address</returns>
|
||||
public ulong UnpackAddress()
|
||||
{
|
||||
return Word1 | ((ulong)(Word2 & 0xffff) << 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks texture descriptor type for this texture descriptor.
|
||||
/// This defines the texture layout, among other things.
|
||||
/// </summary>
|
||||
/// <returns>The texture descriptor type</returns>
|
||||
public TextureDescriptorType UnpackTextureDescriptorType()
|
||||
{
|
||||
return (TextureDescriptorType)((Word2 >> 21) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture stride (bytes per line) for linear textures only.
|
||||
/// Always 32-bytes aligned.
|
||||
/// </summary>
|
||||
/// <returns>The linear texture stride</returns>
|
||||
public int UnpackStride()
|
||||
{
|
||||
return (int)(Word3 & 0xffff) << 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the GOB block size in X (width) for block linear textures.
|
||||
/// Must be always 1, ignored by the GPU.
|
||||
/// </summary>
|
||||
/// <returns>THe GOB block X size</returns>
|
||||
public int UnpackGobBlocksInX()
|
||||
{
|
||||
return 1 << (int)(Word3 & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the GOB block size in Y (height) for block linear textures.
|
||||
/// Must be always a power of 2, with a maximum value of 32.
|
||||
/// </summary>
|
||||
/// <returns>THe GOB block Y size</returns>
|
||||
public int UnpackGobBlocksInY()
|
||||
{
|
||||
return 1 << (int)((Word3 >> 3) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the GOB block size in Z (depth) for block linear textures.
|
||||
/// Must be always a power of 2, with a maximum value of 32.
|
||||
/// Must be 1 for any texture target other than 3D textures.
|
||||
/// </summary>
|
||||
/// <returns>The GOB block Z size</returns>
|
||||
public int UnpackGobBlocksInZ()
|
||||
{
|
||||
return 1 << (int)((Word3 >> 6) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Number of GOB blocks per tile in the X direction.
|
||||
/// This is only used for sparse textures, should be 1 otherwise.
|
||||
/// </summary>
|
||||
/// <returns>The number of GOB blocks per tile</returns>
|
||||
public int UnpackGobBlocksInTileX()
|
||||
{
|
||||
return 1 << (int)((Word3 >> 10) & 7);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the number of mipmap levels of the texture.
|
||||
/// </summary>
|
||||
/// <returns>The number of mipmap levels</returns>
|
||||
public int UnpackLevels()
|
||||
{
|
||||
return (int)(Word3 >> 28) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack the base level texture width size.
|
||||
/// </summary>
|
||||
/// <returns>The texture width</returns>
|
||||
public int UnpackWidth()
|
||||
{
|
||||
return (int)(Word4 & 0xffff) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack the width of a buffer texture.
|
||||
/// </summary>
|
||||
/// <returns>The texture width</returns>
|
||||
public int UnpackBufferTextureWidth()
|
||||
{
|
||||
return (int)((Word4 & 0xffff) | (Word3 << 16)) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture sRGB format flag.
|
||||
/// </summary>
|
||||
/// <returns>True if the texture is sRGB, false otherwise</returns>
|
||||
public bool UnpackSrgb()
|
||||
{
|
||||
return (Word4 & (1 << 22)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture target.
|
||||
/// </summary>
|
||||
/// <returns>The texture target</returns>
|
||||
public TextureTarget UnpackTextureTarget()
|
||||
{
|
||||
return (TextureTarget)((Word4 >> 23) & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack the base level texture height size, or array layers for 1D array textures.
|
||||
/// Should be ignored for 1D or buffer textures.
|
||||
/// </summary>
|
||||
/// <returns>The texture height or layers count</returns>
|
||||
public int UnpackHeight()
|
||||
{
|
||||
return (int)(Word5 & 0xffff) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpack the base level texture depth size, number of array layers or cubemap faces.
|
||||
/// The meaning of this value depends on the texture target.
|
||||
/// </summary>
|
||||
/// <returns>The texture depth, layer or faces count</returns>
|
||||
public int UnpackDepth()
|
||||
{
|
||||
return (int)((Word5 >> 16) & 0x3fff) + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the texture coordinates normalized flag.
|
||||
/// When this is true, texture coordinates are expected to be in the [0, 1] range on the shader.
|
||||
/// When this is false, texture coordinates are expected to be in the [0, W], [0, H] and [0, D] range.
|
||||
/// It must be set to false (by the guest driver) for rectangle textures.
|
||||
/// </summary>
|
||||
/// <returns>The texture coordinates normalized flag</returns>
|
||||
public bool UnpackTextureCoordNormalized()
|
||||
{
|
||||
return (Word5 & (1 << 31)) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the base mipmap level of the texture.
|
||||
/// </summary>
|
||||
/// <returns>The base mipmap level of the texture</returns>
|
||||
public int UnpackBaseLevel()
|
||||
{
|
||||
return (int)(Word7 & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the maximum mipmap level (inclusive) of the texture.
|
||||
/// Usually equal to Levels minus 1.
|
||||
/// </summary>
|
||||
/// <returns>The maximum mipmap level (inclusive) of the texture</returns>
|
||||
public int UnpackMaxLevelInclusive()
|
||||
{
|
||||
return (int)((Word7 >> 4) & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unpacks the multisampled texture samples count in each direction.
|
||||
/// Must be ignored for non-multisample textures.
|
||||
/// </summary>
|
||||
/// <returns>The multisample counts enum</returns>
|
||||
public TextureMsaaMode UnpackTextureMsaaMode()
|
||||
{
|
||||
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two descriptors are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The descriptor to compare against</param>
|
||||
/// <returns>True if they are equal, false otherwise</returns>
|
||||
public bool Equals(ref TextureDescriptor other)
|
||||
{
|
||||
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two descriptors are equal.
|
||||
/// </summary>
|
||||
/// <param name="other">The descriptor to compare against</param>
|
||||
/// <returns>True if they are equal, false otherwise</returns>
|
||||
public bool Equals(TextureDescriptor other)
|
||||
{
|
||||
return Equals(ref other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a hash code for this descriptor.
|
||||
/// </summary>
|
||||
/// <returns>The hash code for this descriptor.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
Normal file
16
src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// The texture descriptor type.
|
||||
/// This specifies the texture memory layout.
|
||||
/// The texture descriptor structure depends on the type.
|
||||
/// </summary>
|
||||
enum TextureDescriptorType
|
||||
{
|
||||
Buffer,
|
||||
LinearColorKey,
|
||||
Linear,
|
||||
BlockLinear,
|
||||
BlockLinearColorKey
|
||||
}
|
||||
}
|
||||
1611
src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
Normal file
1611
src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
Normal file
File diff suppressed because it is too large
Load Diff
554
src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
Normal file
554
src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
Normal file
@@ -0,0 +1,554 @@
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// A tracking handle for a texture group, which represents a range of views in a storage texture.
|
||||
/// Retains a list of overlapping texture views, a modified flag, and tracking for each
|
||||
/// CPU VA range that the views cover.
|
||||
/// Also tracks copy dependencies for the handle - references to other handles that must be kept
|
||||
/// in sync with this one before use.
|
||||
/// </summary>
|
||||
class TextureGroupHandle : IDisposable
|
||||
{
|
||||
private TextureGroup _group;
|
||||
private int _bindCount;
|
||||
private int _firstLevel;
|
||||
private int _firstLayer;
|
||||
|
||||
// Sync state for texture flush.
|
||||
|
||||
/// <summary>
|
||||
/// The sync number last registered.
|
||||
/// </summary>
|
||||
private ulong _registeredSync;
|
||||
|
||||
/// <summary>
|
||||
/// The sync number when the texture was last modified by GPU.
|
||||
/// </summary>
|
||||
private ulong _modifiedSync;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a tracking action is currently registered or not. (0/1)
|
||||
/// </summary>
|
||||
private int _actionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// Whether a sync action is currently registered or not.
|
||||
/// </summary>
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
/// <summary>
|
||||
/// The byte offset from the start of the storage of this handle.
|
||||
/// </summary>
|
||||
public int Offset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The size in bytes covered by this handle.
|
||||
/// </summary>
|
||||
public int Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The base slice index for this handle.
|
||||
/// </summary>
|
||||
public int BaseSlice { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of slices covered by this handle.
|
||||
/// </summary>
|
||||
public int SliceCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The textures which this handle overlaps with.
|
||||
/// </summary>
|
||||
public List<Texture> Overlaps { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The CPU memory tracking handles that cover this handle.
|
||||
/// </summary>
|
||||
public CpuRegionHandle[] Handles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// True if a texture overlapping this handle has been modified. Is set false when the flush action is called.
|
||||
/// </summary>
|
||||
public bool Modified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dependencies to handles from other texture groups.
|
||||
/// </summary>
|
||||
public List<TextureDependency> Dependencies { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A flag indicating that a copy is required from one of the dependencies.
|
||||
/// </summary>
|
||||
public bool NeedsCopy => DeferredCopy != null;
|
||||
|
||||
/// <summary>
|
||||
/// A data copy that must be acknowledged the next time this handle is used.
|
||||
/// </summary>
|
||||
public TextureGroupHandle DeferredCopy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new texture group handle, representing a range of views in a storage texture.
|
||||
/// </summary>
|
||||
/// <param name="group">The TextureGroup that the handle belongs to</param>
|
||||
/// <param name="offset">The byte offset from the start of the storage of the handle</param>
|
||||
/// <param name="size">The size in bytes covered by the handle</param>
|
||||
/// <param name="views">All views of the storage texture, used to calculate overlaps</param>
|
||||
/// <param name="firstLayer">The first layer of this handle in the storage texture</param>
|
||||
/// <param name="firstLevel">The first level of this handle in the storage texture</param>
|
||||
/// <param name="baseSlice">The base slice index of this handle</param>
|
||||
/// <param name="sliceCount">The number of slices this handle covers</param>
|
||||
/// <param name="handles">The memory tracking handles that cover this handle</param>
|
||||
public TextureGroupHandle(TextureGroup group,
|
||||
int offset,
|
||||
ulong size,
|
||||
List<Texture> views,
|
||||
int firstLayer,
|
||||
int firstLevel,
|
||||
int baseSlice,
|
||||
int sliceCount,
|
||||
CpuRegionHandle[] handles)
|
||||
{
|
||||
_group = group;
|
||||
_firstLayer = firstLayer;
|
||||
_firstLevel = firstLevel;
|
||||
|
||||
Offset = offset;
|
||||
Size = (int)size;
|
||||
Overlaps = new List<Texture>();
|
||||
Dependencies = new List<TextureDependency>();
|
||||
|
||||
BaseSlice = baseSlice;
|
||||
SliceCount = sliceCount;
|
||||
|
||||
if (views != null)
|
||||
{
|
||||
RecalculateOverlaps(group, views);
|
||||
}
|
||||
|
||||
Handles = handles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate a list of which views overlap this handle.
|
||||
/// </summary>
|
||||
/// <param name="group">The parent texture group, used to find a view's base CPU VA offset</param>
|
||||
/// <param name="views">The list of views to search for overlaps</param>
|
||||
public void RecalculateOverlaps(TextureGroup group, List<Texture> views)
|
||||
{
|
||||
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||
lock (Overlaps)
|
||||
{
|
||||
int endOffset = Offset + Size;
|
||||
|
||||
Overlaps.Clear();
|
||||
|
||||
foreach (Texture view in views)
|
||||
{
|
||||
int viewOffset = group.FindOffset(view);
|
||||
if (viewOffset < endOffset && Offset < viewOffset + (int)view.Size)
|
||||
{
|
||||
Overlaps.Add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single texture view as an overlap if its range overlaps.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the view in the group</param>
|
||||
/// <param name="view">The texture to add as an overlap</param>
|
||||
public void AddOverlap(int offset, Texture view)
|
||||
{
|
||||
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||
|
||||
if (OverlapsWith(offset, (int)view.Size))
|
||||
{
|
||||
lock (Overlaps)
|
||||
{
|
||||
Overlaps.Add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a single texture view as an overlap if its range overlaps.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset of the view in the group</param>
|
||||
/// <param name="view">The texture to add as an overlap</param>
|
||||
public void RemoveOverlap(int offset, Texture view)
|
||||
{
|
||||
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||
|
||||
if (OverlapsWith(offset, (int)view.Size))
|
||||
{
|
||||
lock (Overlaps)
|
||||
{
|
||||
Overlaps.Remove(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
private void RegisterSync(GpuContext context)
|
||||
{
|
||||
if (!_syncActionRegistered)
|
||||
{
|
||||
_modifiedSync = context.SyncNumber;
|
||||
context.RegisterSyncAction(SyncAction, true);
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that this handle has been modified to any existing dependencies, and set the modified flag.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
public void SignalModified(GpuContext context)
|
||||
{
|
||||
Modified = true;
|
||||
|
||||
// If this handle has any copy dependencies, notify the other handle that a copy needs to be performed.
|
||||
|
||||
foreach (TextureDependency dependency in Dependencies)
|
||||
{
|
||||
dependency.SignalModified();
|
||||
}
|
||||
|
||||
RegisterSync(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that this handle has either started or ended being modified.
|
||||
/// </summary>
|
||||
/// <param name="bound">True if this handle is being bound, false if unbound</param>
|
||||
/// <param name="context">The GPU context to register a sync action on</param>
|
||||
public void SignalModifying(bool bound, GpuContext context)
|
||||
{
|
||||
SignalModified(context);
|
||||
|
||||
// Note: Bind count currently resets to 0 on inherit for safety, as the handle <-> view relationship can change.
|
||||
_bindCount = Math.Max(0, _bindCount + (bound ? 1 : -1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize dependent textures, if any of them have deferred a copy from this texture.
|
||||
/// </summary>
|
||||
public void SynchronizeDependents()
|
||||
{
|
||||
foreach (TextureDependency dependency in Dependencies)
|
||||
{
|
||||
TextureGroupHandle otherHandle = dependency.Other.Handle;
|
||||
|
||||
if (otherHandle.DeferredCopy == this)
|
||||
{
|
||||
otherHandle._group.Storage.SynchronizeMemory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for the latest sync number that the texture handle was written to,
|
||||
/// removing the modified flag if it was reached, or leaving it set if it has not yet been created.
|
||||
/// </summary>
|
||||
/// <param name="context">The GPU context used to wait for sync</param>
|
||||
public void Sync(GpuContext context)
|
||||
{
|
||||
ulong registeredSync = _registeredSync;
|
||||
long diff = (long)(context.SyncNumber - registeredSync);
|
||||
|
||||
if (diff > 0)
|
||||
{
|
||||
context.Renderer.WaitSync(registeredSync);
|
||||
|
||||
if ((long)(_modifiedSync - registeredSync) > 0)
|
||||
{
|
||||
// Flush the data in a previous state. Do not remove the modified flag - it will be removed to ignore following writes.
|
||||
return;
|
||||
}
|
||||
|
||||
Modified = false;
|
||||
}
|
||||
|
||||
// If the difference is <= 0, no data is not ready yet. Flush any data we can without waiting or removing modified flag.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the action registered variable, indicating that the tracking action should be
|
||||
/// re-registered on the next modification.
|
||||
/// </summary>
|
||||
public void ClearActionRegistered()
|
||||
{
|
||||
Interlocked.Exchange(ref _actionRegistered, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to perform when a sync number is registered after modification.
|
||||
/// This action will register a read tracking action on the memory tracking handle so that a flush from CPU can happen.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
{
|
||||
// The storage will need to signal modified again to update the sync number in future.
|
||||
_group.Storage.SignalModifiedDirty();
|
||||
|
||||
lock (Overlaps)
|
||||
{
|
||||
foreach (Texture texture in Overlaps)
|
||||
{
|
||||
texture.SignalModifiedDirty();
|
||||
}
|
||||
}
|
||||
|
||||
// Register region tracking for CPU? (again)
|
||||
_registeredSync = _modifiedSync;
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (Interlocked.Exchange(ref _actionRegistered, 1) == 0)
|
||||
{
|
||||
_group.RegisterAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that a copy dependent texture has been modified, and must have its data copied to this one.
|
||||
/// </summary>
|
||||
/// <param name="copyFrom">The texture handle that must defer a copy to this one</param>
|
||||
public void DeferCopy(TextureGroupHandle copyFrom)
|
||||
{
|
||||
Modified = false;
|
||||
|
||||
DeferredCopy = copyFrom;
|
||||
|
||||
_group.Storage.SignalGroupDirty();
|
||||
|
||||
foreach (Texture overlap in Overlaps)
|
||||
{
|
||||
overlap.SignalGroupDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy dependency between this handle, and another.
|
||||
/// </summary>
|
||||
/// <param name="other">The handle to create a copy dependency to</param>
|
||||
/// <param name="copyToOther">True if a copy should be deferred to all of the other handle's dependencies</param>
|
||||
public void CreateCopyDependency(TextureGroupHandle other, bool copyToOther = false)
|
||||
{
|
||||
// Does this dependency already exist?
|
||||
foreach (TextureDependency existing in Dependencies)
|
||||
{
|
||||
if (existing.Other.Handle == other)
|
||||
{
|
||||
// Do not need to create it again. May need to set the dirty flag.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_group.HasCopyDependencies = true;
|
||||
other._group.HasCopyDependencies = true;
|
||||
|
||||
TextureDependency dependency = new TextureDependency(this);
|
||||
TextureDependency otherDependency = new TextureDependency(other);
|
||||
|
||||
dependency.Other = otherDependency;
|
||||
otherDependency.Other = dependency;
|
||||
|
||||
Dependencies.Add(dependency);
|
||||
other.Dependencies.Add(otherDependency);
|
||||
|
||||
// Recursively create dependency:
|
||||
// All of this handle's dependencies must depend on the other.
|
||||
foreach (TextureDependency existing in Dependencies.ToArray())
|
||||
{
|
||||
if (existing != dependency && existing.Other.Handle != other)
|
||||
{
|
||||
existing.Other.Handle.CreateCopyDependency(other);
|
||||
}
|
||||
}
|
||||
|
||||
// All of the other handle's dependencies must depend on this.
|
||||
foreach (TextureDependency existing in other.Dependencies.ToArray())
|
||||
{
|
||||
if (existing != otherDependency && existing.Other.Handle != this)
|
||||
{
|
||||
existing.Other.Handle.CreateCopyDependency(this);
|
||||
|
||||
if (copyToOther)
|
||||
{
|
||||
existing.Other.Handle.DeferCopy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a dependency from this handle's dependency list.
|
||||
/// </summary>
|
||||
/// <param name="dependency">The dependency to remove</param>
|
||||
public void RemoveDependency(TextureDependency dependency)
|
||||
{
|
||||
Dependencies.Remove(dependency);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any of this handle's memory tracking handles are dirty.
|
||||
/// </summary>
|
||||
/// <returns>True if at least one of the handles is dirty</returns>
|
||||
private bool CheckDirty()
|
||||
{
|
||||
return Handles.Any(handle => handle.Dirty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a copy from the provided handle to this one, or perform a deferred copy if none is provided.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context to register sync for modified handles</param>
|
||||
/// <param name="fromHandle">The handle to copy from. If not provided, this method will copy from and clear the deferred copy instead</param>
|
||||
/// <returns>True if the copy was performed, false otherwise</returns>
|
||||
public bool Copy(GpuContext context, TextureGroupHandle fromHandle = null)
|
||||
{
|
||||
bool result = false;
|
||||
bool shouldCopy = false;
|
||||
|
||||
if (fromHandle == null)
|
||||
{
|
||||
fromHandle = DeferredCopy;
|
||||
|
||||
if (fromHandle != null)
|
||||
{
|
||||
// Only copy if the copy texture is still modified.
|
||||
// It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush.
|
||||
// It will also set as unmodified if a copy is deferred to it.
|
||||
|
||||
shouldCopy = fromHandle.Modified;
|
||||
|
||||
if (fromHandle._bindCount == 0)
|
||||
{
|
||||
// Repeat the copy in future if the bind count is greater than 0.
|
||||
DeferredCopy = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Copies happen directly when initializing a copy dependency.
|
||||
// If dirty, do not copy. Its data no longer matters, and this handle should also be dirty.
|
||||
// Also, only direct copy if the data in this handle is not already modified (can be set by copies from modified handles).
|
||||
shouldCopy = !fromHandle.CheckDirty() && (fromHandle.Modified || !Modified);
|
||||
}
|
||||
|
||||
if (shouldCopy)
|
||||
{
|
||||
Texture from = fromHandle._group.Storage;
|
||||
Texture to = _group.Storage;
|
||||
|
||||
if (from.ScaleFactor != to.ScaleFactor)
|
||||
{
|
||||
to.PropagateScale(from);
|
||||
}
|
||||
|
||||
from.HostTexture.CopyTo(
|
||||
to.HostTexture,
|
||||
fromHandle._firstLayer,
|
||||
_firstLayer,
|
||||
fromHandle._firstLevel,
|
||||
_firstLevel);
|
||||
|
||||
if (fromHandle.Modified)
|
||||
{
|
||||
Modified = true;
|
||||
|
||||
RegisterSync(context);
|
||||
}
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this handle has a dependency to a given texture group.
|
||||
/// </summary>
|
||||
/// <param name="group">The texture group to check for</param>
|
||||
/// <returns>True if there is a dependency, false otherwise</returns>
|
||||
public bool HasDependencyTo(TextureGroup group)
|
||||
{
|
||||
foreach (TextureDependency dep in Dependencies)
|
||||
{
|
||||
if (dep.Other.Handle._group == group)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit modified flags and dependencies from another texture handle.
|
||||
/// </summary>
|
||||
/// <param name="old">The texture handle to inherit from</param>
|
||||
/// <param name="withCopies">Whether the handle should inherit copy dependencies or not</param>
|
||||
public void Inherit(TextureGroupHandle old, bool withCopies)
|
||||
{
|
||||
Modified |= old.Modified;
|
||||
|
||||
if (withCopies)
|
||||
{
|
||||
foreach (TextureDependency dependency in old.Dependencies.ToArray())
|
||||
{
|
||||
CreateCopyDependency(dependency.Other.Handle);
|
||||
|
||||
if (dependency.Other.Handle.DeferredCopy == old)
|
||||
{
|
||||
dependency.Other.Handle.DeferredCopy = this;
|
||||
}
|
||||
}
|
||||
|
||||
DeferredCopy = old.DeferredCopy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if this region overlaps with another.
|
||||
/// </summary>
|
||||
/// <param name="address">Base address</param>
|
||||
/// <param name="size">Size of the region</param>
|
||||
/// <returns>True if overlapping, false otherwise</returns>
|
||||
public bool OverlapsWith(int offset, int size)
|
||||
{
|
||||
return Offset < offset + size && offset < Offset + Size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose this texture group handle, removing all its dependencies and disposing its memory tracking handles.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (CpuRegionHandle handle in Handles)
|
||||
{
|
||||
handle.Dispose();
|
||||
}
|
||||
|
||||
foreach (TextureDependency dependency in Dependencies.ToArray())
|
||||
{
|
||||
dependency.Other.Handle.RemoveDependency(dependency.Other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
381
src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
Normal file
381
src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
Normal file
@@ -0,0 +1,381 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture information.
|
||||
/// </summary>
|
||||
readonly struct TextureInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Address of the texture in GPU mapped memory.
|
||||
/// </summary>
|
||||
public ulong GpuAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The width of the texture.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The height of the texture, or layers count for 1D array textures.
|
||||
/// </summary>
|
||||
public int Height { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The depth of the texture (for 3D textures), or layers count for array textures.
|
||||
/// </summary>
|
||||
public int DepthOrLayers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of mipmap levels of the texture.
|
||||
/// </summary>
|
||||
public int Levels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of samples in the X direction for multisampled textures.
|
||||
/// </summary>
|
||||
public int SamplesInX { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of samples in the Y direction for multisampled textures.
|
||||
/// </summary>
|
||||
public int SamplesInY { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of bytes per line for linear textures.
|
||||
/// </summary>
|
||||
public int Stride { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever or not the texture is a linear texture.
|
||||
/// </summary>
|
||||
public bool IsLinear { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GOB blocks in the Y direction, for block linear textures.
|
||||
/// </summary>
|
||||
public int GobBlocksInY { get; }
|
||||
|
||||
/// <summary>
|
||||
/// GOB blocks in the Z direction, for block linear textures.
|
||||
/// </summary>
|
||||
public int GobBlocksInZ { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of GOB blocks per tile in the X direction, for block linear textures.
|
||||
/// </summary>
|
||||
public int GobBlocksInTileX { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Total number of samples for multisampled textures.
|
||||
/// </summary>
|
||||
public int Samples => SamplesInX * SamplesInY;
|
||||
|
||||
/// <summary>
|
||||
/// Texture target type.
|
||||
/// </summary>
|
||||
public Target Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture format information.
|
||||
/// </summary>
|
||||
public FormatInfo FormatInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Depth-stencil mode of the texture. This defines whenever the depth or stencil value is read from shaders,
|
||||
/// for depth-stencil texture formats.
|
||||
/// </summary>
|
||||
public DepthStencilMode DepthStencilMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture swizzle for the red color channel.
|
||||
/// </summary>
|
||||
public SwizzleComponent SwizzleR { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture swizzle for the green color channel.
|
||||
/// </summary>
|
||||
public SwizzleComponent SwizzleG { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture swizzle for the blue color channel.
|
||||
/// </summary>
|
||||
public SwizzleComponent SwizzleB { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Texture swizzle for the alpha color channel.
|
||||
/// </summary>
|
||||
public SwizzleComponent SwizzleA { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the texture information structure.
|
||||
/// </summary>
|
||||
/// <param name="gpuAddress">The GPU address of the texture</param>
|
||||
/// <param name="width">The width of the texture</param>
|
||||
/// <param name="height">The height or the texture</param>
|
||||
/// <param name="depthOrLayers">The depth or layers count of the texture</param>
|
||||
/// <param name="levels">The amount of mipmap levels of the texture</param>
|
||||
/// <param name="samplesInX">The number of samples in the X direction for multisample textures, should be 1 otherwise</param>
|
||||
/// <param name="samplesInY">The number of samples in the Y direction for multisample textures, should be 1 otherwise</param>
|
||||
/// <param name="stride">The stride for linear textures</param>
|
||||
/// <param name="isLinear">Whenever the texture is linear or block linear</param>
|
||||
/// <param name="gobBlocksInY">Number of GOB blocks in the Y direction</param>
|
||||
/// <param name="gobBlocksInZ">Number of GOB blocks in the Z direction</param>
|
||||
/// <param name="gobBlocksInTileX">Number of GOB blocks per tile in the X direction</param>
|
||||
/// <param name="target">Texture target type</param>
|
||||
/// <param name="formatInfo">Texture format information</param>
|
||||
/// <param name="depthStencilMode">Depth-stencil mode</param>
|
||||
/// <param name="swizzleR">Swizzle for the red color channel</param>
|
||||
/// <param name="swizzleG">Swizzle for the green color channel</param>
|
||||
/// <param name="swizzleB">Swizzle for the blue color channel</param>
|
||||
/// <param name="swizzleA">Swizzle for the alpha color channel</param>
|
||||
public TextureInfo(
|
||||
ulong gpuAddress,
|
||||
int width,
|
||||
int height,
|
||||
int depthOrLayers,
|
||||
int levels,
|
||||
int samplesInX,
|
||||
int samplesInY,
|
||||
int stride,
|
||||
bool isLinear,
|
||||
int gobBlocksInY,
|
||||
int gobBlocksInZ,
|
||||
int gobBlocksInTileX,
|
||||
Target target,
|
||||
FormatInfo formatInfo,
|
||||
DepthStencilMode depthStencilMode = DepthStencilMode.Depth,
|
||||
SwizzleComponent swizzleR = SwizzleComponent.Red,
|
||||
SwizzleComponent swizzleG = SwizzleComponent.Green,
|
||||
SwizzleComponent swizzleB = SwizzleComponent.Blue,
|
||||
SwizzleComponent swizzleA = SwizzleComponent.Alpha)
|
||||
{
|
||||
GpuAddress = gpuAddress;
|
||||
Width = width;
|
||||
Height = height;
|
||||
DepthOrLayers = depthOrLayers;
|
||||
Levels = levels;
|
||||
SamplesInX = samplesInX;
|
||||
SamplesInY = samplesInY;
|
||||
Stride = stride;
|
||||
IsLinear = isLinear;
|
||||
GobBlocksInY = gobBlocksInY;
|
||||
GobBlocksInZ = gobBlocksInZ;
|
||||
GobBlocksInTileX = gobBlocksInTileX;
|
||||
Target = target;
|
||||
FormatInfo = formatInfo;
|
||||
DepthStencilMode = depthStencilMode;
|
||||
SwizzleR = swizzleR;
|
||||
SwizzleG = swizzleG;
|
||||
SwizzleB = swizzleB;
|
||||
SwizzleA = swizzleA;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the real texture depth.
|
||||
/// Returns 1 for any target other than 3D textures.
|
||||
/// </summary>
|
||||
/// <returns>Texture depth</returns>
|
||||
public int GetDepth()
|
||||
{
|
||||
return GetDepth(Target, DepthOrLayers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the real texture depth.
|
||||
/// Returns 1 for any target other than 3D textures.
|
||||
/// </summary>
|
||||
/// <param name="target">Texture target</param>
|
||||
/// <param name="depthOrLayers">Texture depth if the texture is 3D, otherwise ignored</param>
|
||||
/// <returns>Texture depth</returns>
|
||||
public static int GetDepth(Target target, int depthOrLayers)
|
||||
{
|
||||
return target == Target.Texture3D ? depthOrLayers : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of layers of the texture.
|
||||
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
|
||||
/// </summary>
|
||||
/// <returns>The number of texture layers</returns>
|
||||
public int GetLayers()
|
||||
{
|
||||
return GetLayers(Target, DepthOrLayers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of layers of the texture.
|
||||
/// Returns 1 for non-array textures, 6 for cubemap textures, and layer faces for cubemap array textures.
|
||||
/// </summary>
|
||||
/// <param name="target">Texture target</param>
|
||||
/// <param name="depthOrLayers">Texture layers if the is a array texture, ignored otherwise</param>
|
||||
/// <returns>The number of texture layers</returns>
|
||||
public static int GetLayers(Target target, int depthOrLayers)
|
||||
{
|
||||
if (target == Target.Texture2DArray || target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return depthOrLayers;
|
||||
}
|
||||
else if (target == Target.CubemapArray)
|
||||
{
|
||||
return depthOrLayers * 6;
|
||||
}
|
||||
else if (target == Target.Cubemap)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of 2D slices of the texture.
|
||||
/// Returns 6 for cubemap textures, layer faces for cubemap array textures, and DepthOrLayers for everything else.
|
||||
/// </summary>
|
||||
/// <returns>The number of texture slices</returns>
|
||||
public int GetSlices()
|
||||
{
|
||||
if (Target == Target.Texture3D || Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
return DepthOrLayers;
|
||||
}
|
||||
else if (Target == Target.CubemapArray)
|
||||
{
|
||||
return DepthOrLayers * 6;
|
||||
}
|
||||
else if (Target == Target.Cubemap)
|
||||
{
|
||||
return 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the size information from the texture information.
|
||||
/// </summary>
|
||||
/// <param name="layerSize">Optional size of each texture layer in bytes</param>
|
||||
/// <returns>Texture size information</returns>
|
||||
public SizeInfo CalculateSizeInfo(int layerSize = 0)
|
||||
{
|
||||
if (Target == Target.TextureBuffer)
|
||||
{
|
||||
return new SizeInfo(Width * FormatInfo.BytesPerPixel);
|
||||
}
|
||||
else if (IsLinear)
|
||||
{
|
||||
return SizeCalculator.GetLinearTextureSize(
|
||||
Stride,
|
||||
Height,
|
||||
FormatInfo.BlockHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
return SizeCalculator.GetBlockLinearTextureSize(
|
||||
Width,
|
||||
Height,
|
||||
GetDepth(),
|
||||
Levels,
|
||||
GetLayers(),
|
||||
FormatInfo.BlockWidth,
|
||||
FormatInfo.BlockHeight,
|
||||
FormatInfo.BytesPerPixel,
|
||||
GobBlocksInY,
|
||||
GobBlocksInZ,
|
||||
GobBlocksInTileX,
|
||||
layerSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates texture information for a given mipmap level of the specified parent texture and this information.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent texture</param>
|
||||
/// <param name="firstLevel">The first level of the texture view</param>
|
||||
/// <returns>The adjusted texture information with the new size</returns>
|
||||
public TextureInfo CreateInfoForLevelView(Texture parent, int firstLevel)
|
||||
{
|
||||
// When the texture is used as view of another texture, we must
|
||||
// ensure that the sizes are valid, otherwise data uploads would fail
|
||||
// (and the size wouldn't match the real size used on the host API).
|
||||
// Given a parent texture from where the view is created, we have the
|
||||
// following rules:
|
||||
// - The view size must be equal to the parent size, divided by (2 ^ l),
|
||||
// where l is the first mipmap level of the view. The division result must
|
||||
// be rounded down, and the result must be clamped to 1.
|
||||
// - If the parent format is compressed, and the view format isn't, the
|
||||
// view size is calculated as above, but the width and height of the
|
||||
// view must be also divided by the compressed format block width and height.
|
||||
// - If the parent format is not compressed, and the view is, the view
|
||||
// size is calculated as described on the first point, but the width and height
|
||||
// of the view must be also multiplied by the block width and height.
|
||||
int width = Math.Max(1, parent.Info.Width >> firstLevel);
|
||||
int height = Math.Max(1, parent.Info.Height >> firstLevel);
|
||||
|
||||
if (parent.Info.FormatInfo.IsCompressed && !FormatInfo.IsCompressed)
|
||||
{
|
||||
width = BitUtils.DivRoundUp(width, parent.Info.FormatInfo.BlockWidth);
|
||||
height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
|
||||
}
|
||||
else if (!parent.Info.FormatInfo.IsCompressed && FormatInfo.IsCompressed)
|
||||
{
|
||||
width *= FormatInfo.BlockWidth;
|
||||
height *= FormatInfo.BlockHeight;
|
||||
}
|
||||
|
||||
int depthOrLayers;
|
||||
|
||||
if (Target == Target.Texture3D)
|
||||
{
|
||||
depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
depthOrLayers = DepthOrLayers;
|
||||
}
|
||||
|
||||
// 2D and 2D multisample textures are not considered compatible.
|
||||
// This specific case is required for copies, where the source texture might be multisample.
|
||||
// In this case, we inherit the parent texture multisample state.
|
||||
Target target = Target;
|
||||
int samplesInX = SamplesInX;
|
||||
int samplesInY = SamplesInY;
|
||||
|
||||
if (target == Target.Texture2D && parent.Target == Target.Texture2DMultisample)
|
||||
{
|
||||
target = Target.Texture2DMultisample;
|
||||
samplesInX = parent.Info.SamplesInX;
|
||||
samplesInY = parent.Info.SamplesInY;
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
GpuAddress,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
Levels,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
Stride,
|
||||
IsLinear,
|
||||
GobBlocksInY,
|
||||
GobBlocksInZ,
|
||||
GobBlocksInTileX,
|
||||
target,
|
||||
FormatInfo,
|
||||
DepthStencilMode,
|
||||
SwizzleR,
|
||||
SwizzleG,
|
||||
SwizzleB,
|
||||
SwizzleA);
|
||||
}
|
||||
}
|
||||
}
|
||||
498
src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
Normal file
498
src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
Normal file
@@ -0,0 +1,498 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture manager.
|
||||
/// </summary>
|
||||
class TextureManager : IDisposable
|
||||
{
|
||||
private readonly GpuContext _context;
|
||||
private readonly GpuChannel _channel;
|
||||
|
||||
private readonly TextureBindingsManager _cpBindingsManager;
|
||||
private readonly TextureBindingsManager _gpBindingsManager;
|
||||
private readonly TexturePoolCache _texturePoolCache;
|
||||
private readonly SamplerPoolCache _samplerPoolCache;
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
private Texture _rtDepthStencil;
|
||||
private ITexture _rtHostDs;
|
||||
|
||||
public int ClipRegionWidth { get; private set; }
|
||||
public int ClipRegionHeight { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The scaling factor applied to all currently bound render targets.
|
||||
/// </summary>
|
||||
public float RenderTargetScale { get; private set; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the texture manager.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture manager belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture manager belongs to</param>
|
||||
public TextureManager(GpuContext context, GpuChannel channel)
|
||||
{
|
||||
_context = context;
|
||||
_channel = channel;
|
||||
|
||||
TexturePoolCache texturePoolCache = new TexturePoolCache(context);
|
||||
SamplerPoolCache samplerPoolCache = new SamplerPoolCache(context);
|
||||
|
||||
float[] scales = new float[64];
|
||||
new Span<float>(scales).Fill(1f);
|
||||
|
||||
_cpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: true);
|
||||
_gpBindingsManager = new TextureBindingsManager(context, channel, texturePoolCache, samplerPoolCache, scales, isCompute: false);
|
||||
_texturePoolCache = texturePoolCache;
|
||||
_samplerPoolCache = samplerPoolCache;
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture and image bindings for the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="bindings">Bindings for the active shader</param>
|
||||
public void SetComputeBindings(CachedShaderBindings bindings)
|
||||
{
|
||||
_cpBindingsManager.SetBindings(bindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture and image bindings for the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="bindings">Bindings for the active shader</param>
|
||||
public void SetGraphicsBindings(CachedShaderBindings bindings)
|
||||
{
|
||||
_gpBindingsManager.SetBindings(bindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture constant buffer index on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="index">The texture constant buffer index</param>
|
||||
public void SetComputeTextureBufferIndex(int index)
|
||||
{
|
||||
_cpBindingsManager.SetTextureBufferIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the texture constant buffer index on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="index">The texture constant buffer index</param>
|
||||
public void SetGraphicsTextureBufferIndex(int index)
|
||||
{
|
||||
_gpBindingsManager.SetTextureBufferIndex(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current sampler pool on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">The start GPU virtual address of the sampler pool</param>
|
||||
/// <param name="maximumId">The maximum ID of the sampler pool</param>
|
||||
/// <param name="samplerIndex">The indexing type of the sampler pool</param>
|
||||
public void SetComputeSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
|
||||
{
|
||||
_cpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current sampler pool on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">The start GPU virtual address of the sampler pool</param>
|
||||
/// <param name="maximumId">The maximum ID of the sampler pool</param>
|
||||
/// <param name="samplerIndex">The indexing type of the sampler pool</param>
|
||||
public void SetGraphicsSamplerPool(ulong gpuVa, int maximumId, SamplerIndex samplerIndex)
|
||||
{
|
||||
_gpBindingsManager.SetSamplerPool(gpuVa, maximumId, samplerIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current texture pool on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">The start GPU virtual address of the texture pool</param>
|
||||
/// <param name="maximumId">The maximum ID of the texture pool</param>
|
||||
public void SetComputeTexturePool(ulong gpuVa, int maximumId)
|
||||
{
|
||||
_cpBindingsManager.SetTexturePool(gpuVa, maximumId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current texture pool on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="gpuVa">The start GPU virtual address of the texture pool</param>
|
||||
/// <param name="maximumId">The maximum ID of the texture pool</param>
|
||||
public void SetGraphicsTexturePool(ulong gpuVa, int maximumId)
|
||||
{
|
||||
_gpBindingsManager.SetTexturePool(gpuVa, maximumId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a texture's scale must be updated to match the configured resolution scale.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture to check</param>
|
||||
/// <returns>True if the scale needs updating, false if the scale is up to date</returns>
|
||||
private bool ScaleNeedsUpdated(Texture texture)
|
||||
{
|
||||
return texture != null && !(texture.ScaleMode == TextureScaleMode.Blacklisted || texture.ScaleMode == TextureScaleMode.Undesired) && texture.ScaleFactor != GraphicsConfig.ResScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the render target color buffer.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the color buffer to set (up to 8)</param>
|
||||
/// <param name="color">The color buffer texture</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetColor(int index, Texture color)
|
||||
{
|
||||
bool hasValue = color != null;
|
||||
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
||||
|
||||
if (_rtColors[index] != color)
|
||||
{
|
||||
_rtColors[index]?.SignalModifying(false);
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
color.SynchronizeMemory();
|
||||
color.SignalModifying(true);
|
||||
}
|
||||
|
||||
_rtColors[index] = color;
|
||||
}
|
||||
|
||||
return changesScale || ScaleNeedsUpdated(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the render target depth-stencil buffer.
|
||||
/// </summary>
|
||||
/// <param name="depthStencil">The depth-stencil buffer texture</param>
|
||||
/// <returns>True if render target scale must be updated.</returns>
|
||||
public bool SetRenderTargetDepthStencil(Texture depthStencil)
|
||||
{
|
||||
bool hasValue = depthStencil != null;
|
||||
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
||||
|
||||
if (_rtDepthStencil != depthStencil)
|
||||
{
|
||||
_rtDepthStencil?.SignalModifying(false);
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
depthStencil.SynchronizeMemory();
|
||||
depthStencil.SignalModifying(true);
|
||||
}
|
||||
|
||||
_rtDepthStencil = depthStencil;
|
||||
}
|
||||
|
||||
return changesScale || ScaleNeedsUpdated(depthStencil);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the host clip region, which should be the intersection of all render target texture sizes.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the clip region, defined as the minimum width across all bound textures</param>
|
||||
/// <param name="height">Height of the clip region, defined as the minimum height across all bound textures</param>
|
||||
public void SetClipRegion(int width, int height)
|
||||
{
|
||||
ClipRegionWidth = width;
|
||||
ClipRegionHeight = height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first available bound colour target, or the depth stencil target if not present.
|
||||
/// </summary>
|
||||
/// <returns>The first bound colour target, otherwise the depth stencil target</returns>
|
||||
public Texture GetAnyRenderTarget()
|
||||
{
|
||||
return _rtColors[0] ?? _rtDepthStencil;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Render Target scale, given the currently bound render targets.
|
||||
/// This will update scale to match the configured scale, scale textures that are eligible but not scaled,
|
||||
/// and propagate blacklisted status from one texture to the ones bound with it.
|
||||
/// </summary>
|
||||
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
||||
public void UpdateRenderTargetScale(int singleUse)
|
||||
{
|
||||
// Make sure all scales for render targets are at the highest they should be. Blacklisted targets should propagate their scale to the other targets.
|
||||
bool mismatch = false;
|
||||
bool blacklisted = false;
|
||||
bool hasUpscaled = false;
|
||||
bool hasUndesired = false;
|
||||
float targetScale = GraphicsConfig.ResScale;
|
||||
|
||||
void ConsiderTarget(Texture target)
|
||||
{
|
||||
if (target == null) return;
|
||||
float scale = target.ScaleFactor;
|
||||
|
||||
switch (target.ScaleMode)
|
||||
{
|
||||
case TextureScaleMode.Blacklisted:
|
||||
mismatch |= scale != 1f;
|
||||
blacklisted = true;
|
||||
break;
|
||||
case TextureScaleMode.Eligible:
|
||||
mismatch = true; // We must make a decision.
|
||||
break;
|
||||
case TextureScaleMode.Undesired:
|
||||
hasUndesired = true;
|
||||
mismatch |= scale != 1f || hasUpscaled; // If another target is upscaled, scale this one up too.
|
||||
break;
|
||||
case TextureScaleMode.Scaled:
|
||||
hasUpscaled = true;
|
||||
mismatch |= hasUndesired || scale != targetScale; // If the target scale has changed, reset the scale for all targets.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (singleUse != -1)
|
||||
{
|
||||
// If only one target is in use (by a clear, for example) the others do not need to be checked for mismatching scale.
|
||||
ConsiderTarget(_rtColors[singleUse]);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
ConsiderTarget(color);
|
||||
}
|
||||
}
|
||||
|
||||
ConsiderTarget(_rtDepthStencil);
|
||||
|
||||
mismatch |= blacklisted && hasUpscaled;
|
||||
|
||||
if (blacklisted || (hasUndesired && !hasUpscaled))
|
||||
{
|
||||
targetScale = 1f;
|
||||
}
|
||||
|
||||
if (mismatch)
|
||||
{
|
||||
if (blacklisted)
|
||||
{
|
||||
// Propagate the blacklisted state to the other textures.
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
color?.BlacklistScale();
|
||||
}
|
||||
|
||||
_rtDepthStencil?.BlacklistScale();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the scale of the other textures.
|
||||
foreach (Texture color in _rtColors)
|
||||
{
|
||||
color?.SetScale(targetScale);
|
||||
}
|
||||
|
||||
_rtDepthStencil?.SetScale(targetScale);
|
||||
}
|
||||
}
|
||||
|
||||
RenderTargetScale = targetScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture and a sampler from their respective pools from a texture ID and a sampler ID.
|
||||
/// </summary>
|
||||
/// <param name="textureId">ID of the texture</param>
|
||||
/// <param name="samplerId">ID of the sampler</param>
|
||||
public (Texture, Sampler) GetGraphicsTextureAndSampler(int textureId, int samplerId)
|
||||
{
|
||||
return _gpBindingsManager.GetTextureAndSampler(textureId, samplerId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits bindings on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||
public bool CommitComputeBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
// Every time we switch between graphics and compute work,
|
||||
// we must rebind everything.
|
||||
// Since compute work happens less often, we always do that
|
||||
// before and after the compute dispatch.
|
||||
|
||||
_texturePoolCache.Tick();
|
||||
_samplerPoolCache.Tick();
|
||||
|
||||
_cpBindingsManager.Rebind();
|
||||
bool result = _cpBindingsManager.CommitBindings(specState);
|
||||
_gpBindingsManager.Rebind();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits bindings on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="specState">Specialization state for the bound shader</param>
|
||||
/// <returns>True if all bound textures match the current shader specialization state, false otherwise</returns>
|
||||
public bool CommitGraphicsBindings(ShaderSpecializationState specState)
|
||||
{
|
||||
_texturePoolCache.Tick();
|
||||
_samplerPoolCache.Tick();
|
||||
|
||||
bool result = _gpBindingsManager.CommitBindings(specState);
|
||||
|
||||
UpdateRenderTargets();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a texture pool from the cache, with the given address and maximum id.
|
||||
/// </summary>
|
||||
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <returns>The texture pool</returns>
|
||||
public TexturePool GetTexturePool(ulong poolGpuVa, int maximumId)
|
||||
{
|
||||
ulong poolAddress = _channel.MemoryManager.Translate(poolGpuVa);
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||
|
||||
return texturePool;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture descriptor used on the compute pipeline.
|
||||
/// </summary>
|
||||
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="handle">Shader "fake" handle of the texture</param>
|
||||
/// <param name="cbufSlot">Shader constant buffer slot of the texture</param>
|
||||
/// <returns>The texture descriptor</returns>
|
||||
public TextureDescriptor GetComputeTextureDescriptor(ulong poolGpuVa, int bufferIndex, int maximumId, int handle, int cbufSlot)
|
||||
{
|
||||
return _cpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, 0, handle, cbufSlot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a texture descriptor used on the graphics pipeline.
|
||||
/// </summary>
|
||||
/// <param name="poolGpuVa">GPU virtual address of the texture pool</param>
|
||||
/// <param name="bufferIndex">Index of the constant buffer with texture handles</param>
|
||||
/// <param name="maximumId">Maximum ID of the texture pool</param>
|
||||
/// <param name="stageIndex">Index of the shader stage where the texture is bound</param>
|
||||
/// <param name="handle">Shader "fake" handle of the texture</param>
|
||||
/// <param name="cbufSlot">Shader constant buffer slot of the texture</param>
|
||||
/// <returns>The texture descriptor</returns>
|
||||
public TextureDescriptor GetGraphicsTextureDescriptor(
|
||||
ulong poolGpuVa,
|
||||
int bufferIndex,
|
||||
int maximumId,
|
||||
int stageIndex,
|
||||
int handle,
|
||||
int cbufSlot)
|
||||
{
|
||||
return _gpBindingsManager.GetTextureDescriptor(poolGpuVa, bufferIndex, maximumId, stageIndex, handle, cbufSlot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update host framebuffer attachments based on currently bound render target buffers.
|
||||
/// </summary>
|
||||
public void UpdateRenderTargets()
|
||||
{
|
||||
bool anyChanged = false;
|
||||
|
||||
if (_rtHostDs != _rtDepthStencil?.HostTexture)
|
||||
{
|
||||
_rtHostDs = _rtDepthStencil?.HostTexture;
|
||||
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _rtColors.Length; index++)
|
||||
{
|
||||
ITexture hostTexture = _rtColors[index]?.HostTexture;
|
||||
|
||||
if (_rtHostColors[index] != hostTexture)
|
||||
{
|
||||
_rtHostColors[index] = hostTexture;
|
||||
|
||||
anyChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyChanged)
|
||||
{
|
||||
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update host framebuffer attachments based on currently bound render target buffers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All color attachments will be unbound.
|
||||
/// </remarks>
|
||||
public void UpdateRenderTargetDepthStencil()
|
||||
{
|
||||
new Span<ITexture>(_rtHostColors).Fill(null);
|
||||
_rtHostDs = _rtDepthStencil?.HostTexture;
|
||||
|
||||
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
|
||||
/// </summary>
|
||||
public void ReloadPools()
|
||||
{
|
||||
_cpBindingsManager.ReloadPools();
|
||||
_gpBindingsManager.ReloadPools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces all textures, samplers, images and render targets to be rebound the next time
|
||||
/// CommitGraphicsBindings is called.
|
||||
/// </summary>
|
||||
public void Rebind()
|
||||
{
|
||||
_gpBindingsManager.Rebind();
|
||||
|
||||
for (int index = 0; index < _rtHostColors.Length; index++)
|
||||
{
|
||||
_rtHostColors[index] = null;
|
||||
}
|
||||
|
||||
_rtHostDs = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the texture manager.
|
||||
/// It's an error to use the texture manager after disposal.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// Textures are owned by the texture cache, so we shouldn't dispose the texture pool cache.
|
||||
_samplerPoolCache.Dispose();
|
||||
|
||||
for (int i = 0; i < _rtColors.Length; i++)
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
_rtColors[i] = null;
|
||||
}
|
||||
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
_rtDepthStencil = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs
Normal file
9
src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
enum TextureMatchQuality
|
||||
{
|
||||
NoMatch,
|
||||
FormatAlias,
|
||||
Perfect
|
||||
}
|
||||
}
|
||||
68
src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
Normal file
68
src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Multisampled texture samples count.
|
||||
/// </summary>
|
||||
enum TextureMsaaMode
|
||||
{
|
||||
Ms1x1 = 0,
|
||||
Ms2x2 = 2,
|
||||
Ms4x2 = 4,
|
||||
Ms2x1 = 5,
|
||||
Ms4x4 = 6
|
||||
}
|
||||
|
||||
static class TextureMsaaModeConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the total number of samples from the MSAA mode.
|
||||
/// </summary>
|
||||
/// <param name="msaaMode">The MSAA mode</param>
|
||||
/// <returns>The total number of samples</returns>
|
||||
public static int SamplesCount(this TextureMsaaMode msaaMode)
|
||||
{
|
||||
return msaaMode switch
|
||||
{
|
||||
TextureMsaaMode.Ms2x1 => 2,
|
||||
TextureMsaaMode.Ms2x2 => 4,
|
||||
TextureMsaaMode.Ms4x2 => 8,
|
||||
TextureMsaaMode.Ms4x4 => 16,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of samples in the X direction from the MSAA mode.
|
||||
/// </summary>
|
||||
/// <param name="msaaMode">The MSAA mode</param>
|
||||
/// <returns>The number of samples in the X direction</returns>
|
||||
public static int SamplesInX(this TextureMsaaMode msaaMode)
|
||||
{
|
||||
return msaaMode switch
|
||||
{
|
||||
TextureMsaaMode.Ms2x1 => 2,
|
||||
TextureMsaaMode.Ms2x2 => 2,
|
||||
TextureMsaaMode.Ms4x2 => 4,
|
||||
TextureMsaaMode.Ms4x4 => 4,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of samples in the Y direction from the MSAA mode.
|
||||
/// </summary>
|
||||
/// <param name="msaaMode">The MSAA mode</param>
|
||||
/// <returns>The number of samples in the Y direction</returns>
|
||||
public static int SamplesInY(this TextureMsaaMode msaaMode)
|
||||
{
|
||||
return msaaMode switch
|
||||
{
|
||||
TextureMsaaMode.Ms2x1 => 1,
|
||||
TextureMsaaMode.Ms2x2 => 2,
|
||||
TextureMsaaMode.Ms4x2 => 2,
|
||||
TextureMsaaMode.Ms4x4 => 4,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
603
src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
Normal file
603
src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
Normal file
@@ -0,0 +1,603 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.Texture;
|
||||
using Ryujinx.Memory.Range;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture pool.
|
||||
/// </summary>
|
||||
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
||||
{
|
||||
/// <summary>
|
||||
/// A request to dereference a texture from a pool.
|
||||
/// </summary>
|
||||
private struct DereferenceRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the dereference is due to a mapping change or not.
|
||||
/// </summary>
|
||||
public readonly bool IsRemapped;
|
||||
|
||||
/// <summary>
|
||||
/// The texture being dereferenced.
|
||||
/// </summary>
|
||||
public readonly Texture Texture;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the pool entry this reference belonged to.
|
||||
/// </summary>
|
||||
public readonly int ID;
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
|
||||
/// </summary>
|
||||
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
|
||||
/// <param name="texture">The texture being dereferenced</param>
|
||||
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||
private DereferenceRequest(bool isRemapped, Texture texture, int id)
|
||||
{
|
||||
IsRemapped = isRemapped;
|
||||
Texture = texture;
|
||||
ID = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture removal.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being removed</param>
|
||||
/// <returns>A texture removal dereference request</returns>
|
||||
public static DereferenceRequest Remove(Texture texture)
|
||||
{
|
||||
return new DereferenceRequest(false, texture, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a dereference request for a texture remapping with a specific pool ID.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being remapped</param>
|
||||
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||
/// <returns>A remap dereference request</returns>
|
||||
public static DereferenceRequest Remap(Texture texture, int id)
|
||||
{
|
||||
return new DereferenceRequest(true, texture, id);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly GpuChannel _channel;
|
||||
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
|
||||
private TextureDescriptor _defaultDescriptor;
|
||||
|
||||
/// <summary>
|
||||
/// Linked list node used on the texture pool cache.
|
||||
/// </summary>
|
||||
public LinkedListNode<TexturePool> CacheNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timestamp used by the texture pool cache, updated on every use of this texture pool.
|
||||
/// </summary>
|
||||
public ulong CacheTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the texture pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||
public TexturePool(GpuContext context, GpuChannel channel, ulong address, int maximumId) : base(context, channel.MemoryManager.Physical, address, maximumId)
|
||||
{
|
||||
_channel = channel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descripor and texture with the given ID with no bounds check or synchronization.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||
/// <param name="texture">The texture with the given ID</param>
|
||||
/// <returns>The texture descriptor with the given ID</returns>
|
||||
private ref readonly TextureDescriptor GetInternal(int id, out Texture texture)
|
||||
{
|
||||
texture = Items[id];
|
||||
|
||||
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(id);
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||
|
||||
// The dereference queue can put our texture back on the cache.
|
||||
if ((texture = ProcessDereferenceQueue(id)) != null)
|
||||
{
|
||||
return ref descriptor;
|
||||
}
|
||||
|
||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||
|
||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||
if (texture == null)
|
||||
{
|
||||
return ref descriptor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
Items[id] = texture;
|
||||
|
||||
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
|
||||
|
||||
DescriptorCache[id] = descriptor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// On the path above (texture not yet in the pool), memory is automatically synchronized on texture creation.
|
||||
texture.SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ref descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture with the given ID.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||
/// <returns>The texture with the given ID</returns>
|
||||
public override Texture Get(int id)
|
||||
{
|
||||
if ((uint)id >= Items.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
GetInternal(id, out Texture texture);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture descriptor and texture with the given ID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method assumes that the pool has been manually synchronized before doing binding.
|
||||
/// </remarks>
|
||||
/// <param name="id">ID of the texture. This is effectively a zero-based index</param>
|
||||
/// <param name="texture">The texture with the given ID</param>
|
||||
/// <returns>The texture descriptor with the given ID</returns>
|
||||
public ref readonly TextureDescriptor GetForBinding(int id, out Texture texture)
|
||||
{
|
||||
if ((uint)id >= Items.Length)
|
||||
{
|
||||
texture = null;
|
||||
return ref _defaultDescriptor;
|
||||
}
|
||||
|
||||
// When getting for binding, assume the pool has already been synchronized.
|
||||
|
||||
return ref GetInternal(id, out texture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the pool was modified, and returns the last sequence number where a modification was detected.
|
||||
/// </summary>
|
||||
/// <returns>A number that increments each time a modification is detected</returns>
|
||||
public int CheckModified()
|
||||
{
|
||||
if (SequenceNumber != Context.SequenceNumber)
|
||||
{
|
||||
SequenceNumber = Context.SequenceNumber;
|
||||
|
||||
SynchronizeMemory();
|
||||
}
|
||||
|
||||
return ModifiedSequenceNumber;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forcibly remove a texture from this pool's items.
|
||||
/// If deferred, the dereference will be queued to occur on the render thread.
|
||||
/// </summary>
|
||||
/// <param name="texture">The texture being removed</param>
|
||||
/// <param name="id">The ID of the texture in this pool</param>
|
||||
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
||||
public void ForceRemove(Texture texture, int id, bool deferred)
|
||||
{
|
||||
var previous = Interlocked.Exchange(ref Items[id], null);
|
||||
|
||||
if (deferred)
|
||||
{
|
||||
if (previous != null)
|
||||
{
|
||||
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a request to update a texture's mapping.
|
||||
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture with potential mapping change</param>
|
||||
/// <param name="id">ID in cache of texture with potential mapping change</param>
|
||||
public void QueueUpdateMapping(Texture texture, int id)
|
||||
{
|
||||
if (Interlocked.Exchange(ref Items[id], null) == texture)
|
||||
{
|
||||
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
||||
/// This is used to ensure that texture disposal happens on the render thread.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the entry that triggered this method</param>
|
||||
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
|
||||
private Texture ProcessDereferenceQueue(int id = -1)
|
||||
{
|
||||
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
|
||||
{
|
||||
Texture texture = request.Texture;
|
||||
|
||||
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
|
||||
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
|
||||
|
||||
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
|
||||
{
|
||||
// Has the mapping for this texture changed?
|
||||
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
|
||||
|
||||
ulong address = descriptor.UnpackAddress();
|
||||
|
||||
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||
|
||||
// If the texture is not mapped at all, delete its reference.
|
||||
|
||||
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
continue;
|
||||
}
|
||||
|
||||
Items[request.ID] = texture;
|
||||
|
||||
// Create a new pool reference, as the last one was removed on unmap.
|
||||
|
||||
texture.IncrementReferenceCount(this, request.ID, address);
|
||||
texture.DecrementReferenceCount();
|
||||
|
||||
// Refetch the range. Changes since the last check could have been lost
|
||||
// as the cache entry was not restored (required to queue mapping change).
|
||||
|
||||
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||
|
||||
if (!range.Equals(texture.Range))
|
||||
{
|
||||
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
||||
{
|
||||
// Texture could not be remapped due to a collision, just delete it.
|
||||
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||
{
|
||||
// If this is null, a request was already queued to decrement reference.
|
||||
texture.DecrementReferenceCount(this, request.ID);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (request.ID == id)
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
texture.DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the texture pool range invalidation.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range of the texture pool</param>
|
||||
/// <param name="size">Size of the range being invalidated</param>
|
||||
protected override void InvalidateRangeImpl(ulong address, ulong size)
|
||||
{
|
||||
ProcessDereferenceQueue();
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
for (; address < endAddress; address += DescriptorSize)
|
||||
{
|
||||
int id = (int)((address - Address) / DescriptorSize);
|
||||
|
||||
Texture texture = Items[id];
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id];
|
||||
ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address);
|
||||
|
||||
// If the descriptors are the same, the texture is the same,
|
||||
// we don't need to remove as it was not modified. Just continue.
|
||||
if (descriptor.Equals(ref cachedDescriptor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (texture.HasOneReference())
|
||||
{
|
||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||
}
|
||||
|
||||
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||
{
|
||||
texture.DecrementReferenceCount(this, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets texture information from a texture descriptor.
|
||||
/// </summary>
|
||||
/// <param name="descriptor">The texture descriptor</param>
|
||||
/// <param name="layerSize">Layer size for textures using a sub-range of mipmap levels, otherwise 0</param>
|
||||
/// <returns>The texture information</returns>
|
||||
private TextureInfo GetInfo(in TextureDescriptor descriptor, out int layerSize)
|
||||
{
|
||||
int depthOrLayers = descriptor.UnpackDepth();
|
||||
int levels = descriptor.UnpackLevels();
|
||||
|
||||
TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
|
||||
|
||||
int samplesInX = msaaMode.SamplesInX();
|
||||
int samplesInY = msaaMode.SamplesInY();
|
||||
|
||||
int stride = descriptor.UnpackStride();
|
||||
|
||||
TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
|
||||
|
||||
bool isLinear = descriptorType == TextureDescriptorType.Linear;
|
||||
|
||||
Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
|
||||
|
||||
int width = target == Target.TextureBuffer ? descriptor.UnpackBufferTextureWidth() : descriptor.UnpackWidth();
|
||||
int height = descriptor.UnpackHeight();
|
||||
|
||||
if (target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray)
|
||||
{
|
||||
// This is divided back before the backend texture is created.
|
||||
width *= samplesInX;
|
||||
height *= samplesInY;
|
||||
}
|
||||
|
||||
// We use 2D targets for 1D textures as that makes texture cache
|
||||
// management easier. We don't know the target for render target
|
||||
// and copies, so those would normally use 2D targets, which are
|
||||
// not compatible with 1D targets. By doing that we also allow those
|
||||
// to match when looking for compatible textures on the cache.
|
||||
if (target == Target.Texture1D)
|
||||
{
|
||||
target = Target.Texture2D;
|
||||
height = 1;
|
||||
}
|
||||
else if (target == Target.Texture1DArray)
|
||||
{
|
||||
target = Target.Texture2DArray;
|
||||
height = 1;
|
||||
}
|
||||
|
||||
uint format = descriptor.UnpackFormat();
|
||||
bool srgb = descriptor.UnpackSrgb();
|
||||
|
||||
ulong gpuVa = descriptor.UnpackAddress();
|
||||
|
||||
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
|
||||
{
|
||||
if (gpuVa != 0 && (int)format > 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
|
||||
}
|
||||
|
||||
formatInfo = FormatInfo.Default;
|
||||
}
|
||||
|
||||
int gobBlocksInY = descriptor.UnpackGobBlocksInY();
|
||||
int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
|
||||
|
||||
int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
|
||||
|
||||
layerSize = 0;
|
||||
|
||||
int minLod = descriptor.UnpackBaseLevel();
|
||||
int maxLod = descriptor.UnpackMaxLevelInclusive();
|
||||
|
||||
// Linear textures don't support mipmaps, so we don't handle this case here.
|
||||
if ((minLod != 0 || maxLod + 1 != levels) && target != Target.TextureBuffer && !isLinear)
|
||||
{
|
||||
int depth = TextureInfo.GetDepth(target, depthOrLayers);
|
||||
int layers = TextureInfo.GetLayers(target, depthOrLayers);
|
||||
|
||||
SizeInfo sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
levels,
|
||||
layers,
|
||||
formatInfo.BlockWidth,
|
||||
formatInfo.BlockHeight,
|
||||
formatInfo.BytesPerPixel,
|
||||
gobBlocksInY,
|
||||
gobBlocksInZ,
|
||||
gobBlocksInTileX);
|
||||
|
||||
layerSize = sizeInfo.LayerSize;
|
||||
|
||||
if (minLod != 0 && minLod < levels)
|
||||
{
|
||||
// If the base level is not zero, we additionally add the mip level offset
|
||||
// to the address, this allows the texture manager to find the base level from the
|
||||
// address if there is a overlapping texture on the cache that can contain the new texture.
|
||||
gpuVa += (ulong)sizeInfo.GetMipOffset(minLod);
|
||||
|
||||
width = Math.Max(1, width >> minLod);
|
||||
height = Math.Max(1, height >> minLod);
|
||||
|
||||
if (target == Target.Texture3D)
|
||||
{
|
||||
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
|
||||
}
|
||||
|
||||
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
|
||||
}
|
||||
|
||||
levels = (maxLod - minLod) + 1;
|
||||
}
|
||||
|
||||
SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
|
||||
SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
|
||||
SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
|
||||
SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
|
||||
|
||||
DepthStencilMode depthStencilMode = GetDepthStencilMode(
|
||||
formatInfo.Format,
|
||||
swizzleR,
|
||||
swizzleG,
|
||||
swizzleB,
|
||||
swizzleA);
|
||||
|
||||
if (formatInfo.Format.IsDepthOrStencil())
|
||||
{
|
||||
swizzleR = SwizzleComponent.Red;
|
||||
swizzleG = SwizzleComponent.Red;
|
||||
swizzleB = SwizzleComponent.Red;
|
||||
|
||||
if (depthStencilMode == DepthStencilMode.Depth)
|
||||
{
|
||||
swizzleA = SwizzleComponent.One;
|
||||
}
|
||||
else
|
||||
{
|
||||
swizzleA = SwizzleComponent.Red;
|
||||
}
|
||||
}
|
||||
|
||||
return new TextureInfo(
|
||||
gpuVa,
|
||||
width,
|
||||
height,
|
||||
depthOrLayers,
|
||||
levels,
|
||||
samplesInX,
|
||||
samplesInY,
|
||||
stride,
|
||||
isLinear,
|
||||
gobBlocksInY,
|
||||
gobBlocksInZ,
|
||||
gobBlocksInTileX,
|
||||
target,
|
||||
formatInfo,
|
||||
depthStencilMode,
|
||||
swizzleR,
|
||||
swizzleG,
|
||||
swizzleB,
|
||||
swizzleA);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the texture depth-stencil mode, based on the swizzle components of each color channel.
|
||||
/// The depth-stencil mode is determined based on how the driver sets those parameters.
|
||||
/// </summary>
|
||||
/// <param name="format">The format of the texture</param>
|
||||
/// <param name="components">The texture swizzle components</param>
|
||||
/// <returns>The depth-stencil mode</returns>
|
||||
private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
|
||||
{
|
||||
// R = Depth, G = Stencil.
|
||||
// On 24-bits depth formats, this is inverted (Stencil is R etc).
|
||||
// NVN setup:
|
||||
// For depth, A is set to 1.0f, the other components are set to Depth.
|
||||
// For stencil, all components are set to Stencil.
|
||||
SwizzleComponent component = components[0];
|
||||
|
||||
for (int index = 1; index < 4 && !IsRG(component); index++)
|
||||
{
|
||||
component = components[index];
|
||||
}
|
||||
|
||||
if (!IsRG(component))
|
||||
{
|
||||
return DepthStencilMode.Depth;
|
||||
}
|
||||
|
||||
if (format == Format.D24UnormS8Uint)
|
||||
{
|
||||
return component == SwizzleComponent.Red
|
||||
? DepthStencilMode.Stencil
|
||||
: DepthStencilMode.Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
return component == SwizzleComponent.Red
|
||||
? DepthStencilMode.Depth
|
||||
: DepthStencilMode.Stencil;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the swizzle component is equal to the red or green channels.
|
||||
/// </summary>
|
||||
/// <param name="component">The swizzle component to check</param>
|
||||
/// <returns>True if the swizzle component is equal to the red or green, false otherwise</returns>
|
||||
private static bool IsRG(SwizzleComponent component)
|
||||
{
|
||||
return component == SwizzleComponent.Red ||
|
||||
component == SwizzleComponent.Green;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the reference count of the texture.
|
||||
/// This indicates that the texture pool is not using it anymore.
|
||||
/// </summary>
|
||||
/// <param name="item">The texture to be deleted</param>
|
||||
protected override void Delete(Texture item)
|
||||
{
|
||||
item?.DecrementReferenceCount(this);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
ProcessDereferenceQueue();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
Normal file
30
src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture pool cache.
|
||||
/// This can keep multiple texture pools, and return the current one as needed.
|
||||
/// It is useful for applications that uses multiple texture pools.
|
||||
/// </summary>
|
||||
class TexturePoolCache : PoolCache<TexturePool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a new instance of the texture pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
public TexturePoolCache(GpuContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the texture pool.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the texture pool belongs to</param>
|
||||
/// <param name="channel">GPU channel that the texture pool belongs to</param>
|
||||
/// <param name="address">Address of the texture pool in guest memory</param>
|
||||
/// <param name="maximumId">Maximum texture ID of the texture pool (equal to maximum textures minus one)</param>
|
||||
protected override TexturePool CreatePool(GpuContext context, GpuChannel channel, ulong address, int maximumId)
|
||||
{
|
||||
return new TexturePool(context, channel, address, maximumId);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
16
src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// The scale mode for a given texture.
|
||||
/// Blacklisted textures cannot be scaled, Eligible textures have not been scaled yet,
|
||||
/// and Scaled textures have been scaled already.
|
||||
/// Undesired textures will stay at 1x until a situation where they must match a scaled texture.
|
||||
/// </summary>
|
||||
enum TextureScaleMode
|
||||
{
|
||||
Eligible = 0,
|
||||
Scaled = 1,
|
||||
Blacklisted = 2,
|
||||
Undesired = 3
|
||||
}
|
||||
}
|
||||
17
src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
Normal file
17
src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture search flags, defines texture information comparison rules.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
enum TextureSearchFlags
|
||||
{
|
||||
None = 0,
|
||||
ForSampler = 1 << 1,
|
||||
ForCopy = 1 << 2,
|
||||
WithUpscale = 1 << 3,
|
||||
NoCreate = 1 << 4
|
||||
}
|
||||
}
|
||||
81
src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
Normal file
81
src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture target.
|
||||
/// </summary>
|
||||
enum TextureTarget : byte
|
||||
{
|
||||
Texture1D,
|
||||
Texture2D,
|
||||
Texture3D,
|
||||
Cubemap,
|
||||
Texture1DArray,
|
||||
Texture2DArray,
|
||||
TextureBuffer,
|
||||
Texture2DRect,
|
||||
CubemapArray
|
||||
}
|
||||
|
||||
static class TextureTargetConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the texture target enum to a host compatible, Graphics Abstraction Layer enum.
|
||||
/// </summary>
|
||||
/// <param name="target">The target enum to convert</param>
|
||||
/// <param name="isMultisample">True if the texture is a multisampled texture</param>
|
||||
/// <returns>The host compatible texture target</returns>
|
||||
public static Target Convert(this TextureTarget target, bool isMultisample)
|
||||
{
|
||||
if (isMultisample)
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case TextureTarget.Texture2D: return Target.Texture2DMultisample;
|
||||
case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case TextureTarget.Texture1D: return Target.Texture1D;
|
||||
case TextureTarget.Texture2D: return Target.Texture2D;
|
||||
case TextureTarget.Texture2DRect: return Target.Texture2D;
|
||||
case TextureTarget.Texture3D: return Target.Texture3D;
|
||||
case TextureTarget.Texture1DArray: return Target.Texture1DArray;
|
||||
case TextureTarget.Texture2DArray: return Target.Texture2DArray;
|
||||
case TextureTarget.Cubemap: return Target.Cubemap;
|
||||
case TextureTarget.CubemapArray: return Target.CubemapArray;
|
||||
case TextureTarget.TextureBuffer: return Target.TextureBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
return Target.Texture1D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the texture target enum to a shader sampler type.
|
||||
/// </summary>
|
||||
/// <param name="target">The target enum to convert</param>
|
||||
/// <returns>The shader sampler type</returns>
|
||||
public static SamplerType ConvertSamplerType(this TextureTarget target)
|
||||
{
|
||||
return target switch
|
||||
{
|
||||
TextureTarget.Texture1D => SamplerType.Texture1D,
|
||||
TextureTarget.Texture2D => SamplerType.Texture2D,
|
||||
TextureTarget.Texture3D => SamplerType.Texture3D,
|
||||
TextureTarget.Cubemap => SamplerType.TextureCube,
|
||||
TextureTarget.Texture1DArray => SamplerType.Texture1D | SamplerType.Array,
|
||||
TextureTarget.Texture2DArray => SamplerType.Texture2D | SamplerType.Array,
|
||||
TextureTarget.TextureBuffer => SamplerType.TextureBuffer,
|
||||
TextureTarget.Texture2DRect => SamplerType.Texture2D,
|
||||
TextureTarget.CubemapArray => SamplerType.TextureCube | SamplerType.Array,
|
||||
_ => SamplerType.Texture2D
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs
Normal file
14
src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
/// <summary>
|
||||
/// The level of view compatibility one texture has to another.
|
||||
/// Values are increasing in compatibility from 0 (incompatible).
|
||||
/// </summary>
|
||||
enum TextureViewCompatibility
|
||||
{
|
||||
Incompatible = 0,
|
||||
LayoutIncompatible,
|
||||
CopyOnly,
|
||||
Full
|
||||
}
|
||||
}
|
||||
544
src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
Normal file
544
src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
Normal file
@@ -0,0 +1,544 @@
|
||||
using Ryujinx.Cpu.Tracking;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Memory.Range;
|
||||
using Ryujinx.Memory.Tracking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
|
||||
/// </summary>
|
||||
class Buffer : IRange, IDisposable
|
||||
{
|
||||
private const ulong GranularBufferThreshold = 4096;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
private readonly PhysicalMemory _physicalMemory;
|
||||
|
||||
/// <summary>
|
||||
/// Host buffer handle.
|
||||
/// </summary>
|
||||
public BufferHandle Handle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start address of the buffer in guest memory.
|
||||
/// </summary>
|
||||
public ulong Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the buffer in bytes.
|
||||
/// </summary>
|
||||
public ulong Size { get; }
|
||||
|
||||
/// <summary>
|
||||
/// End address of the buffer in guest memory.
|
||||
/// </summary>
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
/// <summary>
|
||||
/// Increments when the buffer is (partially) unmapped or disposed.
|
||||
/// </summary>
|
||||
public int UnmappedSequence { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ranges of the buffer that have been modified on the GPU.
|
||||
/// Ranges defined here cannot be updated from CPU until a CPU waiting sync point is reached.
|
||||
/// Then, write tracking will signal, wait for GPU sync (generated at the syncpoint) and flush these regions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is null until at least one modification occurs.
|
||||
/// </remarks>
|
||||
private BufferModifiedRangeList _modifiedRanges = null;
|
||||
|
||||
private readonly CpuMultiRegionHandle _memoryTrackingGranular;
|
||||
private readonly CpuRegionHandle _memoryTracking;
|
||||
|
||||
private readonly RegionSignal _externalFlushDelegate;
|
||||
private readonly Action<ulong, ulong> _loadDelegate;
|
||||
private readonly Action<ulong, ulong> _modifiedDelegate;
|
||||
|
||||
private int _sequenceNumber;
|
||||
|
||||
private bool _useGranular;
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that the buffer belongs to</param>
|
||||
/// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
|
||||
/// <param name="address">Start address of the buffer</param>
|
||||
/// <param name="size">Size of the buffer in bytes</param>
|
||||
/// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
|
||||
public Buffer(GpuContext context, PhysicalMemory physicalMemory, ulong address, ulong size, IEnumerable<Buffer> baseBuffers = null)
|
||||
{
|
||||
_context = context;
|
||||
_physicalMemory = physicalMemory;
|
||||
Address = address;
|
||||
Size = size;
|
||||
|
||||
Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
|
||||
|
||||
_useGranular = size > GranularBufferThreshold;
|
||||
|
||||
IEnumerable<IRegionHandle> baseHandles = null;
|
||||
|
||||
if (baseBuffers != null)
|
||||
{
|
||||
baseHandles = baseBuffers.SelectMany(buffer =>
|
||||
{
|
||||
if (buffer._useGranular)
|
||||
{
|
||||
return buffer._memoryTrackingGranular.GetHandles();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Repeat(buffer._memoryTracking.GetHandle(), 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
|
||||
|
||||
_memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
|
||||
|
||||
if (baseHandles != null)
|
||||
{
|
||||
_memoryTracking.Reprotect(false);
|
||||
|
||||
foreach (IRegionHandle handle in baseHandles)
|
||||
{
|
||||
if (handle.Dirty)
|
||||
{
|
||||
_memoryTracking.Reprotect(true);
|
||||
}
|
||||
|
||||
handle.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_memoryTracking.RegisterPreciseAction(PreciseAction);
|
||||
}
|
||||
|
||||
_externalFlushDelegate = new RegionSignal(ExternalFlush);
|
||||
_loadDelegate = new Action<ulong, ulong>(LoadRegion);
|
||||
_modifiedDelegate = new Action<ulong, ulong>(RegionModified);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a sub-range from the buffer, from a start address till the end of the buffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to bind and use sub-ranges of the buffer on the host API.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
|
||||
/// <returns>The buffer sub-range</returns>
|
||||
public BufferRange GetRange(ulong address)
|
||||
{
|
||||
ulong offset = address - Address;
|
||||
|
||||
return new BufferRange(Handle, (int)offset, (int)(Size - offset));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a sub-range from the buffer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used to bind and use sub-ranges of the buffer on the host API.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
|
||||
/// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
|
||||
/// <returns>The buffer sub-range</returns>
|
||||
public BufferRange GetRange(ulong address, ulong size)
|
||||
{
|
||||
int offset = (int)(address - Address);
|
||||
|
||||
return new BufferRange(Handle, offset, (int)size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>True if the range overlaps, false otherwise</returns>
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range is fully contained in the buffer.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
/// <returns>True if the range is contained, false otherwise</returns>
|
||||
public bool FullyContains(ulong address, ulong size)
|
||||
{
|
||||
return address >= Address && address + size <= EndAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs guest to host memory synchronization of the buffer data.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This causes the buffer data to be overwritten if a write was detected from the CPU,
|
||||
/// since the last call to this method.
|
||||
/// </remarks>
|
||||
/// <param name="address">Start address of the range to synchronize</param>
|
||||
/// <param name="size">Size in bytes of the range to synchronize</param>
|
||||
public void SynchronizeMemory(ulong address, ulong size)
|
||||
{
|
||||
if (_useGranular)
|
||||
{
|
||||
_memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_context.SequenceNumber != _sequenceNumber && _memoryTracking.DirtyOrVolatile())
|
||||
{
|
||||
_memoryTracking.Reprotect();
|
||||
|
||||
if (_modifiedRanges != null)
|
||||
{
|
||||
_modifiedRanges.ExcludeModifiedRegions(Address, Size, _loadDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
|
||||
}
|
||||
|
||||
_sequenceNumber = _context.SequenceNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure that the modified range list exists.
|
||||
/// </summary>
|
||||
private void EnsureRangeList()
|
||||
{
|
||||
if (_modifiedRanges == null)
|
||||
{
|
||||
_modifiedRanges = new BufferModifiedRangeList(_context, this, Flush);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that the given region of the buffer has been modified.
|
||||
/// </summary>
|
||||
/// <param name="address">The start address of the modified region</param>
|
||||
/// <param name="size">The size of the modified region</param>
|
||||
public void SignalModified(ulong address, ulong size)
|
||||
{
|
||||
EnsureRangeList();
|
||||
|
||||
_modifiedRanges.SignalModified(address, size);
|
||||
|
||||
if (!_syncActionRegistered)
|
||||
{
|
||||
_context.RegisterSyncAction(SyncAction);
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that mofifications in a given region of this buffer have been overwritten.
|
||||
/// </summary>
|
||||
/// <param name="address">The start address of the region</param>
|
||||
/// <param name="size">The size of the region</param>
|
||||
public void ClearModified(ulong address, ulong size)
|
||||
{
|
||||
_modifiedRanges?.Clear(address, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Action to be performed when a syncpoint is reached after modification.
|
||||
/// This will register read/write tracking to flush the buffer from GPU when its memory is used.
|
||||
/// </summary>
|
||||
private void SyncAction()
|
||||
{
|
||||
_syncActionRegistered = false;
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
_modifiedRanges?.GetRanges(Address, Size, (address, size) =>
|
||||
{
|
||||
_memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
|
||||
SynchronizeMemory(address, size);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryTracking.RegisterAction(_externalFlushDelegate);
|
||||
SynchronizeMemory(Address, Size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inherit modified ranges from another buffer.
|
||||
/// </summary>
|
||||
/// <param name="from">The buffer to inherit from</param>
|
||||
public void InheritModifiedRanges(Buffer from)
|
||||
{
|
||||
if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
|
||||
{
|
||||
if (from._syncActionRegistered && !_syncActionRegistered)
|
||||
{
|
||||
_context.RegisterSyncAction(SyncAction);
|
||||
_syncActionRegistered = true;
|
||||
}
|
||||
|
||||
Action<ulong, ulong> registerRangeAction = (ulong address, ulong size) =>
|
||||
{
|
||||
if (_useGranular)
|
||||
{
|
||||
_memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryTracking.RegisterAction(_externalFlushDelegate);
|
||||
}
|
||||
};
|
||||
|
||||
EnsureRangeList();
|
||||
|
||||
_modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a given region of the buffer has been modified, and must be flushed.
|
||||
/// </summary>
|
||||
/// <param name="address">The start address of the region</param>
|
||||
/// <param name="size">The size of the region</param>
|
||||
/// <returns></returns>
|
||||
public bool IsModified(ulong address, ulong size)
|
||||
{
|
||||
if (_modifiedRanges != null)
|
||||
{
|
||||
return _modifiedRanges.HasRange(address, size);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that a region of the buffer was modified, and must be loaded from memory.
|
||||
/// </summary>
|
||||
/// <param name="mAddress">Start address of the modified region</param>
|
||||
/// <param name="mSize">Size of the modified region</param>
|
||||
private void RegionModified(ulong mAddress, ulong mSize)
|
||||
{
|
||||
if (mAddress < Address)
|
||||
{
|
||||
mAddress = Address;
|
||||
}
|
||||
|
||||
ulong maxSize = Address + Size - mAddress;
|
||||
|
||||
if (mSize > maxSize)
|
||||
{
|
||||
mSize = maxSize;
|
||||
}
|
||||
|
||||
if (_modifiedRanges != null)
|
||||
{
|
||||
_modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadRegion(mAddress, mSize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a region of the buffer from memory.
|
||||
/// </summary>
|
||||
/// <param name="mAddress">Start address of the modified region</param>
|
||||
/// <param name="mSize">Size of the modified region</param>
|
||||
private void LoadRegion(ulong mAddress, ulong mSize)
|
||||
{
|
||||
int offset = (int)(mAddress - Address);
|
||||
|
||||
_context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check.
|
||||
/// </summary>
|
||||
/// <param name="mAddress">Start address of the modified region</param>
|
||||
/// <param name="mSize">Size of the region to force dirty</param>
|
||||
public void ForceDirty(ulong mAddress, ulong mSize)
|
||||
{
|
||||
_modifiedRanges?.Clear(mAddress, mSize);
|
||||
|
||||
if (_useGranular)
|
||||
{
|
||||
_memoryTrackingGranular.ForceDirty(mAddress, mSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
_memoryTracking.ForceDirty();
|
||||
_sequenceNumber--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs copy of all the buffer data from one buffer to another.
|
||||
/// </summary>
|
||||
/// <param name="destination">The destination buffer to copy the data into</param>
|
||||
/// <param name="dstOffset">The offset of the destination buffer to copy into</param>
|
||||
public void CopyTo(Buffer destination, int dstOffset)
|
||||
{
|
||||
_context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flushes a range of the buffer.
|
||||
/// This writes the range data back into guest memory.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the range</param>
|
||||
/// <param name="size">Size in bytes of the range</param>
|
||||
public void Flush(ulong address, ulong size)
|
||||
{
|
||||
int offset = (int)(address - Address);
|
||||
|
||||
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||
|
||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||
_physicalMemory.WriteUntracked(address, data.Get());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Align a given address and size region to page boundaries.
|
||||
/// </summary>
|
||||
/// <param name="address">The start address of the region</param>
|
||||
/// <param name="size">The size of the region</param>
|
||||
/// <returns>The page aligned address and size</returns>
|
||||
private static (ulong address, ulong size) PageAlign(ulong address, ulong size)
|
||||
{
|
||||
ulong pageMask = MemoryManager.PageMask;
|
||||
ulong rA = address & ~pageMask;
|
||||
ulong rS = ((address + size + pageMask) & ~pageMask) - rA;
|
||||
return (rA, rS);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush modified ranges of the buffer from another thread.
|
||||
/// This will flush all modifications made before the active SyncNumber was set, and may block to wait for GPU sync.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the memory action</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
public void ExternalFlush(ulong address, ulong size)
|
||||
{
|
||||
_context.Renderer.BackgroundContextAction(() =>
|
||||
{
|
||||
var ranges = _modifiedRanges;
|
||||
|
||||
if (ranges != null)
|
||||
{
|
||||
(address, size) = PageAlign(address, size);
|
||||
ranges.WaitForAndFlushRanges(address, size);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action to be performed when a precise memory access occurs to this resource.
|
||||
/// For buffers, this skips flush-on-write by punching holes directly into the modified range list.
|
||||
/// </summary>
|
||||
/// <param name="address">Address of the memory action</param>
|
||||
/// <param name="size">Size in bytes</param>
|
||||
/// <param name="write">True if the access was a write, false otherwise</param>
|
||||
private bool PreciseAction(ulong address, ulong size, bool write)
|
||||
{
|
||||
if (!write)
|
||||
{
|
||||
// We only want to skip flush-on-write.
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong maxAddress = Math.Max(address, Address);
|
||||
ulong minEndAddress = Math.Min(address + size, Address + Size);
|
||||
|
||||
if (maxAddress >= minEndAddress)
|
||||
{
|
||||
// Access doesn't overlap.
|
||||
return false;
|
||||
}
|
||||
|
||||
ForceDirty(maxAddress, minEndAddress - maxAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when part of the memory for this buffer has been unmapped.
|
||||
/// Calls are from non-GPU threads.
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the unmapped region</param>
|
||||
/// <param name="size">Size of the unmapped region</param>
|
||||
public void Unmapped(ulong address, ulong size)
|
||||
{
|
||||
BufferModifiedRangeList modifiedRanges = _modifiedRanges;
|
||||
|
||||
modifiedRanges?.Clear(address, size);
|
||||
|
||||
UnmappedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the buffer reference count.
|
||||
/// </summary>
|
||||
public void IncrementReferenceCount()
|
||||
{
|
||||
_referenceCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrements the buffer reference count.
|
||||
/// </summary>
|
||||
public void DecrementReferenceCount()
|
||||
{
|
||||
if (--_referenceCount == 0)
|
||||
{
|
||||
DisposeData();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host buffer's data, not its tracking handles.
|
||||
/// </summary>
|
||||
public void DisposeData()
|
||||
{
|
||||
_modifiedRanges?.Clear();
|
||||
|
||||
_context.Renderer.DeleteBuffer(Handle);
|
||||
|
||||
UnmappedSequence++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the host buffer.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_memoryTrackingGranular?.Dispose();
|
||||
_memoryTracking?.Dispose();
|
||||
|
||||
DecrementReferenceCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user