Ryujinx/Ryujinx.Graphics.Gpu/Engine/Methods.cs
gdkchan cb171f6ebf Support shared color mask, implement more shader instructions
Support shared color masks (used by Nouveau and maybe the NVIDIA
driver).
Support draw buffers (also required by OpenGL).
Support viewport transform disable (disabled for now as it breaks some
games).
Fix instanced rendering draw being ignored for multi draw.
Fix IADD and IADD3 immediate shader encodings, that was not matching
some ops.
Implement FFMA32I shader instruction.
Implement IMAD shader instruction.
2020-01-09 02:13:00 +01:00

793 lines
29 KiB
C#

using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Blend;
using Ryujinx.Graphics.GAL.DepthStencil;
using Ryujinx.Graphics.GAL.InputAssembler;
using Ryujinx.Graphics.GAL.Texture;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine
{
partial class Methods
{
private GpuContext _context;
private ShaderCache _shaderCache;
private ShaderProgramInfo[] _currentProgramInfo;
private BufferManager _bufferManager;
private TextureManager _textureManager;
public BufferManager BufferManager => _bufferManager;
public TextureManager TextureManager => _textureManager;
private bool _isAnyVbInstanced;
private bool _vsUsesInstanceId;
public Methods(GpuContext context)
{
_context = context;
_shaderCache = new ShaderCache(_context);
_currentProgramInfo = new ShaderProgramInfo[Constants.TotalShaderStages];
_bufferManager = new BufferManager(context);
_textureManager = new TextureManager(context);
}
public void RegisterCallbacks(GpuState state)
{
state.RegisterCallback(MethodOffset.LaunchDma, LaunchDma);
state.RegisterCallback(MethodOffset.LoadInlineData, LoadInlineData);
state.RegisterCallback(MethodOffset.Dispatch, Dispatch);
state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer);
state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture);
state.RegisterCallback(MethodOffset.TextureBarrier, TextureBarrier);
state.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
state.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled);
state.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
state.RegisterCallback(MethodOffset.DrawEnd, DrawEnd);
state.RegisterCallback(MethodOffset.DrawBegin, DrawBegin);
state.RegisterCallback(MethodOffset.IndexBufferCount, SetIndexBufferCount);
state.RegisterCallback(MethodOffset.Clear, Clear);
state.RegisterCallback(MethodOffset.Report, Report);
state.RegisterCallback(MethodOffset.UniformBufferUpdateData, 16, UniformBufferUpdate);
state.RegisterCallback(MethodOffset.UniformBufferBindVertex, UniformBufferBindVertex);
state.RegisterCallback(MethodOffset.UniformBufferBindTessControl, UniformBufferBindTessControl);
state.RegisterCallback(MethodOffset.UniformBufferBindTessEvaluation, UniformBufferBindTessEvaluation);
state.RegisterCallback(MethodOffset.UniformBufferBindGeometry, UniformBufferBindGeometry);
state.RegisterCallback(MethodOffset.UniformBufferBindFragment, UniformBufferBindFragment);
}
private void UpdateState(GpuState state)
{
// Shaders must be the first one to be updated if modified, because
// some of the other state depends on information from the currently
// bound shaders.
if (state.QueryModified(MethodOffset.ShaderBaseAddress, MethodOffset.ShaderState))
{
UpdateShaderState(state);
}
if (state.QueryModified(MethodOffset.RtColorState,
MethodOffset.RtDepthStencilState,
MethodOffset.RtControl,
MethodOffset.RtDepthStencilSize,
MethodOffset.RtDepthStencilEnable))
{
UpdateRenderTargetState(state, useControl: true);
}
if (state.QueryModified(MethodOffset.DepthTestEnable,
MethodOffset.DepthWriteEnable,
MethodOffset.DepthTestFunc))
{
UpdateDepthTestState(state);
}
if (state.QueryModified(MethodOffset.ViewportTransform, MethodOffset.ViewportExtents))
{
UpdateViewportTransform(state);
}
if (state.QueryModified(MethodOffset.DepthBiasState,
MethodOffset.DepthBiasFactor,
MethodOffset.DepthBiasUnits,
MethodOffset.DepthBiasClamp))
{
UpdateDepthBiasState(state);
}
if (state.QueryModified(MethodOffset.StencilBackMasks,
MethodOffset.StencilTestState,
MethodOffset.StencilBackTestState))
{
UpdateStencilTestState(state);
}
// Pools.
if (state.QueryModified(MethodOffset.SamplerPoolState))
{
UpdateSamplerPoolState(state);
}
if (state.QueryModified(MethodOffset.TexturePoolState))
{
UpdateTexturePoolState(state);
}
// Input assembler state.
if (state.QueryModified(MethodOffset.VertexAttribState))
{
UpdateVertexAttribState(state);
}
if (state.QueryModified(MethodOffset.PrimitiveRestartState))
{
UpdatePrimitiveRestartState(state);
}
if (state.QueryModified(MethodOffset.IndexBufferState))
{
UpdateIndexBufferState(state);
}
if (state.QueryModified(MethodOffset.VertexBufferDrawState,
MethodOffset.VertexBufferInstanced,
MethodOffset.VertexBufferState,
MethodOffset.VertexBufferEndAddress))
{
UpdateVertexBufferState(state);
}
if (state.QueryModified(MethodOffset.FaceState))
{
UpdateFaceState(state);
}
if (state.QueryModified(MethodOffset.RtColorMaskShared, MethodOffset.RtColorMask))
{
UpdateRtColorMask(state);
}
if (state.QueryModified(MethodOffset.BlendIndependent,
MethodOffset.BlendStateCommon,
MethodOffset.BlendEnableCommon,
MethodOffset.BlendEnable,
MethodOffset.BlendState))
{
UpdateBlendState(state);
}
CommitBindings();
}
private void CommitBindings()
{
UpdateStorageBuffers();
_bufferManager.CommitBindings();
_textureManager.CommitGraphicsBindings();
}
private void UpdateStorageBuffers()
{
for (int stage = 0; stage < _currentProgramInfo.Length; stage++)
{
ShaderProgramInfo info = _currentProgramInfo[stage];
if (info == null)
{
continue;
}
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
ulong sbDescAddress = _bufferManager.GetGraphicsUniformBufferAddress(stage, 0);
int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
sbDescAddress += (ulong)sbDescOffset;
Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
_bufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
}
}
}
private void UpdateRenderTargetState(GpuState state, bool useControl)
{
var rtControl = state.Get<RtControl>(MethodOffset.RtControl);
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
var msaaMode = state.Get<TextureMsaaMode>(MethodOffset.RtMsaaMode);
int samplesInX = msaaMode.SamplesInX();
int samplesInY = msaaMode.SamplesInY();
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
int rtIndex = useControl ? rtControl.UnpackPermutationIndex(index) : index;
var colorState = state.Get<RtColorState>(MethodOffset.RtColorState, rtIndex);
if (index >= count || !IsRtEnabled(colorState))
{
_textureManager.SetRenderTargetColor(index, null);
continue;
}
Image.Texture color = _textureManager.FindOrCreateTexture(
colorState,
samplesInX,
samplesInY);
_textureManager.SetRenderTargetColor(index, color);
if (color != null)
{
color.Modified = true;
}
}
bool dsEnable = state.Get<Boolean32>(MethodOffset.RtDepthStencilEnable);
Image.Texture depthStencil = null;
if (dsEnable)
{
var dsState = state.Get<RtDepthStencilState>(MethodOffset.RtDepthStencilState);
var dsSize = state.Get<Size3D> (MethodOffset.RtDepthStencilSize);
depthStencil = _textureManager.FindOrCreateTexture(
dsState,
dsSize,
samplesInX,
samplesInY);
}
_textureManager.SetRenderTargetDepthStencil(depthStencil);
if (depthStencil != null)
{
depthStencil.Modified = true;
}
}
private static bool IsRtEnabled(RtColorState colorState)
{
// Colors are disabled by writing 0 to the format.
return colorState.Format != 0 && colorState.WidthOrStride != 0;
}
private void UpdateDepthTestState(GpuState state)
{
_context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
state.Get<Boolean32>(MethodOffset.DepthTestEnable),
state.Get<Boolean32>(MethodOffset.DepthWriteEnable),
state.Get<CompareOp>(MethodOffset.DepthTestFunc)));
}
private void UpdateViewportTransform(GpuState state)
{
bool transformEnable = GetViewportTransformEnable(state);
bool flipY = (state.Get<int>(MethodOffset.YControl) & 1) != 0;
float yFlip = flipY ? -1 : 1;
Viewport[] viewports = new Viewport[Constants.TotalViewports];
for (int index = 0; index < Constants.TotalViewports; index++)
{
var transform = state.Get<ViewportTransform>(MethodOffset.ViewportTransform, index);
var extents = state.Get<ViewportExtents> (MethodOffset.ViewportExtents, index);
RectangleF region;
if (transformEnable)
{
float x = transform.TranslateX - MathF.Abs(transform.ScaleX);
float y = transform.TranslateY - MathF.Abs(transform.ScaleY);
float width = transform.ScaleX * 2;
float height = transform.ScaleY * 2 * yFlip;
region = new RectangleF(x, y, width, height);
}
else
{
// It's not possible to fully disable viewport transform, at least with the most
// common graphics APIs, but we can effectively disable it with a dummy transform.
// The transform is defined as: xw = (width / 2) * xndc + x + (width / 2)
// By setting x to -(width / 2), we effectively remove the translation.
// By setting the width to 2, we remove the scale since 2 / 2 = 1.
// Now, the only problem is the viewport clipping, that we also can't disable.
// To prevent the values from being clipped, we multiply (-1, -1, 2, 2) by
// the maximum supported viewport dimensions.
// This must be compensated on the shader, by dividing the vertex position
// by the maximum viewport dimensions.
float maxSize = (float)_context.Capabilities.MaximumViewportDimensions;
float halfMaxSize = (float)_context.Capabilities.MaximumViewportDimensions * 0.5f;
region = new RectangleF(-halfMaxSize, -halfMaxSize, maxSize, maxSize * yFlip);
}
viewports[index] = new Viewport(
region,
transform.UnpackSwizzleX(),
transform.UnpackSwizzleY(),
transform.UnpackSwizzleZ(),
transform.UnpackSwizzleW(),
extents.DepthNear,
extents.DepthFar);
}
_context.Renderer.Pipeline.SetViewports(0, viewports);
}
private void UpdateDepthBiasState(GpuState state)
{
var depthBias = state.Get<DepthBiasState>(MethodOffset.DepthBiasState);
float factor = state.Get<float>(MethodOffset.DepthBiasFactor);
float units = state.Get<float>(MethodOffset.DepthBiasUnits);
float clamp = state.Get<float>(MethodOffset.DepthBiasClamp);
PolygonModeMask enables = 0;
enables = (depthBias.PointEnable ? PolygonModeMask.Point : 0);
enables |= (depthBias.LineEnable ? PolygonModeMask.Line : 0);
enables |= (depthBias.FillEnable ? PolygonModeMask.Fill : 0);
_context.Renderer.Pipeline.SetDepthBias(enables, factor, units, clamp);
}
private void UpdateStencilTestState(GpuState state)
{
var backMasks = state.Get<StencilBackMasks> (MethodOffset.StencilBackMasks);
var test = state.Get<StencilTestState> (MethodOffset.StencilTestState);
var backTest = state.Get<StencilBackTestState>(MethodOffset.StencilBackTestState);
CompareOp backFunc;
StencilOp backSFail;
StencilOp backDpPass;
StencilOp backDpFail;
int backFuncRef;
int backFuncMask;
int backMask;
if (backTest.TwoSided)
{
backFunc = backTest.BackFunc;
backSFail = backTest.BackSFail;
backDpPass = backTest.BackDpPass;
backDpFail = backTest.BackDpFail;
backFuncRef = backMasks.FuncRef;
backFuncMask = backMasks.FuncMask;
backMask = backMasks.Mask;
}
else
{
backFunc = test.FrontFunc;
backSFail = test.FrontSFail;
backDpPass = test.FrontDpPass;
backDpFail = test.FrontDpFail;
backFuncRef = test.FrontFuncRef;
backFuncMask = test.FrontFuncMask;
backMask = test.FrontMask;
}
_context.Renderer.Pipeline.SetStencilTest(new StencilTestDescriptor(
test.Enable,
test.FrontFunc,
test.FrontSFail,
test.FrontDpPass,
test.FrontDpFail,
test.FrontFuncRef,
test.FrontFuncMask,
test.FrontMask,
backFunc,
backSFail,
backDpPass,
backDpFail,
backFuncRef,
backFuncMask,
backMask));
}
private void UpdateSamplerPoolState(GpuState state)
{
var samplerPool = state.Get<PoolState>(MethodOffset.SamplerPoolState);
var samplerIndex = state.Get<SamplerIndex>(MethodOffset.SamplerIndex);
_textureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId, samplerIndex);
}
private void UpdateTexturePoolState(GpuState state)
{
var texturePool = state.Get<PoolState>(MethodOffset.TexturePoolState);
_textureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
_textureManager.SetGraphicsTextureBufferIndex(state.Get<int>(MethodOffset.TextureBufferIndex));
}
private void UpdateVertexAttribState(GpuState state)
{
VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
for (int index = 0; index < 16; index++)
{
var vertexAttrib = state.Get<VertexAttribState>(MethodOffset.VertexAttribState, index);
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
{
// TODO: warning.
format = Format.R32G32B32A32Float;
}
vertexAttribs[index] = new VertexAttribDescriptor(
vertexAttrib.UnpackBufferIndex(),
vertexAttrib.UnpackOffset(),
format);
}
_context.Renderer.Pipeline.BindVertexAttribs(vertexAttribs);
}
private void UpdatePrimitiveRestartState(GpuState state)
{
PrimitiveRestartState primitiveRestart = state.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
_context.Renderer.Pipeline.SetPrimitiveRestart(
primitiveRestart.Enable,
primitiveRestart.Index);
}
private void UpdateIndexBufferState(GpuState state)
{
var indexBuffer = state.Get<IndexBufferState>(MethodOffset.IndexBufferState);
_firstIndex = indexBuffer.First;
_indexCount = indexBuffer.Count;
if (_indexCount == 0)
{
return;
}
ulong gpuVa = indexBuffer.Address.Pack();
// Do not use the end address to calculate the size, because
// the result may be much larger than the real size of the index buffer.
ulong size = (ulong)(_firstIndex + _indexCount);
switch (indexBuffer.Type)
{
case IndexType.UShort: size *= 2; break;
case IndexType.UInt: size *= 4; break;
}
_bufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type);
// The index buffer affects the vertex buffer size calculation, we
// need to ensure that they are updated.
UpdateVertexBufferState(state);
}
private void UpdateVertexBufferState(GpuState state)
{
_isAnyVbInstanced = false;
for (int index = 0; index < 16; index++)
{
var vertexBuffer = state.Get<VertexBufferState>(MethodOffset.VertexBufferState, index);
if (!vertexBuffer.UnpackEnable())
{
_bufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
continue;
}
GpuVa endAddress = state.Get<GpuVa>(MethodOffset.VertexBufferEndAddress, index);
ulong address = vertexBuffer.Address.Pack();
int stride = vertexBuffer.UnpackStride();
bool instanced = state.Get<Boolean32>(MethodOffset.VertexBufferInstanced + index);
int divisor = instanced ? vertexBuffer.Divisor : 0;
_isAnyVbInstanced |= divisor != 0;
ulong size;
if (_drawIndexed || stride == 0 || instanced)
{
// This size may be (much) larger than the real vertex buffer size.
// Avoid calculating it this way, unless we don't have any other option.
size = endAddress.Pack() - address + 1;
}
else
{
// For non-indexed draws, we can guess the size from the vertex count
// and stride.
int firstInstance = state.Get<int>(MethodOffset.FirstInstance);
var drawState = state.Get<VertexBufferDrawState>(MethodOffset.VertexBufferDrawState);
size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
}
_bufferManager.SetVertexBuffer(index, address, size, stride, divisor);
}
}
private void UpdateFaceState(GpuState state)
{
var face = state.Get<FaceState>(MethodOffset.FaceState);
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
_context.Renderer.Pipeline.SetFrontFace(face.FrontFace);
}
private void UpdateRtColorMask(GpuState state)
{
bool rtColorMaskShared = state.Get<Boolean32>(MethodOffset.RtColorMaskShared);
uint[] componentMasks = new uint[Constants.TotalRenderTargets];
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
var colorMask = state.Get<RtColorMask>(MethodOffset.RtColorMask, rtColorMaskShared ? 0 : index);
uint componentMask = 0;
componentMask = (colorMask.UnpackRed() ? 1u : 0u);
componentMask |= (colorMask.UnpackGreen() ? 2u : 0u);
componentMask |= (colorMask.UnpackBlue() ? 4u : 0u);
componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
componentMasks[index] = componentMask;
}
_context.Renderer.Pipeline.SetRenderTargetColorMasks(componentMasks);
}
private void UpdateBlendState(GpuState state)
{
bool blendIndependent = state.Get<Boolean32>(MethodOffset.BlendIndependent);
BlendState[] blends = new BlendState[8];
for (int index = 0; index < 8; index++)
{
BlendDescriptor descriptor;
if (blendIndependent)
{
bool enable = state.Get<Boolean32> (MethodOffset.BlendEnable, index);
var blend = state.Get<BlendState>(MethodOffset.BlendState, index);
descriptor = new BlendDescriptor(
enable,
blend.ColorOp,
blend.ColorSrcFactor,
blend.ColorDstFactor,
blend.AlphaOp,
blend.AlphaSrcFactor,
blend.AlphaDstFactor);
}
else
{
bool enable = state.Get<Boolean32> (MethodOffset.BlendEnable, 0);
var blend = state.Get<BlendStateCommon>(MethodOffset.BlendStateCommon);
descriptor = new BlendDescriptor(
enable,
blend.ColorOp,
blend.ColorSrcFactor,
blend.ColorDstFactor,
blend.AlphaOp,
blend.AlphaSrcFactor,
blend.AlphaDstFactor);
}
_context.Renderer.Pipeline.BindBlendState(index, descriptor);
}
}
private struct SbDescriptor
{
public uint AddressLow;
public uint AddressHigh;
public int Size;
public int Padding;
public ulong PackAddress()
{
return AddressLow | ((ulong)AddressHigh << 32);
}
}
private void UpdateShaderState(GpuState state)
{
ShaderAddresses addresses = new ShaderAddresses();
Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
ulong baseAddress = state.Get<GpuVa>(MethodOffset.ShaderBaseAddress).Pack();
for (int index = 0; index < 6; index++)
{
var shader = state.Get<ShaderState>(MethodOffset.ShaderState, index);
if (!shader.UnpackEnable() && index != 1)
{
continue;
}
addressesArray[index] = baseAddress + shader.Offset;
}
bool viewportTransformEnable = GetViewportTransformEnable(state);
GraphicsShader gs = _shaderCache.GetGraphicsShader(addresses, !viewportTransformEnable);
_vsUsesInstanceId = gs.Shader[0].Program.Info.UsesInstanceId;
for (int stage = 0; stage < Constants.TotalShaderStages; stage++)
{
ShaderProgramInfo info = gs.Shader[stage].Program?.Info;
_currentProgramInfo[stage] = info;
if (info == null)
{
continue;
}
var textureBindings = new TextureBindingInfo[info.Textures.Count];
for (int index = 0; index < info.Textures.Count; index++)
{
var descriptor = info.Textures[index];
Target target = GetTarget(descriptor.Type);
textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
}
_textureManager.SetGraphicsTextures(stage, textureBindings);
var imageBindings = new TextureBindingInfo[info.Images.Count];
for (int index = 0; index < info.Images.Count; index++)
{
var descriptor = info.Images[index];
Target target = GetTarget(descriptor.Type);
imageBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
}
_textureManager.SetGraphicsImages(stage, imageBindings);
uint sbEnableMask = 0;
uint ubEnableMask = 0;
for (int index = 0; index < info.SBuffers.Count; index++)
{
sbEnableMask |= 1u << info.SBuffers[index].Slot;
}
for (int index = 0; index < info.CBuffers.Count; index++)
{
ubEnableMask |= 1u << info.CBuffers[index].Slot;
}
_bufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask);
_bufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask);
}
_context.Renderer.Pipeline.BindProgram(gs.HostProgram);
}
private bool GetViewportTransformEnable(GpuState state)
{
// FIXME: We should read ViewportTransformEnable, but it seems that some games writes 0 there?
// return state.Get<Boolean32>(MethodOffset.ViewportTransformEnable) != 0;
return true;
}
private 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;
}
// TODO: Warning.
return Target.Texture2D;
}
private void TextureBarrier(GpuState state, int argument)
{
_context.Renderer.Pipeline.TextureBarrier();
}
private void InvalidateTextures(GpuState state, int argument)
{
_textureManager.Flush();
}
private void TextureBarrierTiled(GpuState state, int argument)
{
_context.Renderer.Pipeline.TextureBarrierTiled();
}
}
}