mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-08-19 15:18:43 +02:00
"Screen scissor" is the minimum size of all render targets, and is set when any render target is bound on NVN or OpenGL. Since it works on all active texture's real sizes, it is therefore more reliable than viewport 0's width, and is actually set before clear. This fixes a regression with Hyrule Warriors: Age Of Calamity's cubemaps, which did not set viewport dimensions before clear. This resulted in attempting to create a cubemap with rectangular sides, which is logically and physically impossible. (also it just fails)
1143 lines
44 KiB
C#
1143 lines
44 KiB
C#
using Ryujinx.Common.Logging;
|
|
using Ryujinx.Graphics.GAL;
|
|
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 Ryujinx.Graphics.Texture;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Ryujinx.Graphics.Gpu.Engine
|
|
{
|
|
using Texture = Image.Texture;
|
|
|
|
/// <summary>
|
|
/// GPU method implementations.
|
|
/// </summary>
|
|
partial class Methods
|
|
{
|
|
private readonly GpuContext _context;
|
|
private readonly ShaderProgramInfo[] _currentProgramInfo;
|
|
|
|
/// <summary>
|
|
/// In-memory shader cache.
|
|
/// </summary>
|
|
public ShaderCache ShaderCache { get; }
|
|
|
|
/// <summary>
|
|
/// GPU buffer manager.
|
|
/// </summary>
|
|
public BufferManager BufferManager { get; }
|
|
|
|
/// <summary>
|
|
/// GPU texture manager.
|
|
/// </summary>
|
|
public TextureManager TextureManager { get; }
|
|
|
|
private bool _isAnyVbInstanced;
|
|
private bool _vsUsesInstanceId;
|
|
|
|
private bool _forceShaderUpdate;
|
|
|
|
private bool _prevTfEnable;
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of the GPU methods class.
|
|
/// </summary>
|
|
/// <param name="context">GPU context</param>
|
|
public Methods(GpuContext context)
|
|
{
|
|
_context = context;
|
|
|
|
ShaderCache = new ShaderCache(_context);
|
|
|
|
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
|
|
|
|
BufferManager = new BufferManager(context);
|
|
TextureManager = new TextureManager(context);
|
|
|
|
context.MemoryManager.MemoryUnmapped += _counterCache.MemoryUnmappedHandler;
|
|
context.MemoryManager.MemoryUnmapped += TextureManager.MemoryUnmappedHandler;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Register callback for GPU method calls that triggers an action on the GPU.
|
|
/// </summary>
|
|
/// <param name="state">GPU state where the triggers will be registered</param>
|
|
public void RegisterCallbacks(GpuState state)
|
|
{
|
|
state.RegisterCallback(MethodOffset.LaunchDma, LaunchDma);
|
|
state.RegisterCallback(MethodOffset.LoadInlineData, LoadInlineData);
|
|
|
|
state.RegisterCallback(MethodOffset.Dispatch, Dispatch);
|
|
|
|
state.RegisterCallback(MethodOffset.SyncpointAction, IncrementSyncpoint);
|
|
|
|
state.RegisterCallback(MethodOffset.CopyBuffer, CopyBuffer);
|
|
state.RegisterCallback(MethodOffset.CopyTexture, CopyTexture);
|
|
|
|
state.RegisterCallback(MethodOffset.TextureBarrier, TextureBarrier);
|
|
state.RegisterCallback(MethodOffset.TextureBarrierTiled, TextureBarrierTiled);
|
|
|
|
state.RegisterCallback(MethodOffset.VbElementU8, VbElementU8);
|
|
state.RegisterCallback(MethodOffset.VbElementU16, VbElementU16);
|
|
state.RegisterCallback(MethodOffset.VbElementU32, VbElementU32);
|
|
|
|
state.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
|
|
|
|
state.RegisterCallback(MethodOffset.DrawEnd, DrawEnd);
|
|
state.RegisterCallback(MethodOffset.DrawBegin, DrawBegin);
|
|
state.RegisterCallback(MethodOffset.DrawIndexedSmall, DrawIndexedSmall);
|
|
state.RegisterCallback(MethodOffset.DrawIndexedSmall2, DrawIndexedSmall2);
|
|
state.RegisterCallback(MethodOffset.DrawIndexedSmallIncInstance, DrawIndexedSmallIncInstance);
|
|
state.RegisterCallback(MethodOffset.DrawIndexedSmallIncInstance2, DrawIndexedSmallIncInstance2);
|
|
|
|
state.RegisterCallback(MethodOffset.IndexBufferCount, SetIndexBufferCount);
|
|
|
|
state.RegisterCallback(MethodOffset.Clear, Clear);
|
|
|
|
state.RegisterCallback(MethodOffset.Report, Report);
|
|
|
|
state.RegisterCallback(MethodOffset.FirmwareCall4, FirmwareCall4);
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host state based on the current guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Guest GPU state</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>
|
|
private void UpdateState(GpuState state, int firstIndex, int indexCount)
|
|
{
|
|
bool tfEnable = state.Get<Boolean32>(MethodOffset.TfEnable);
|
|
|
|
if (!tfEnable && _prevTfEnable)
|
|
{
|
|
_context.Renderer.Pipeline.EndTransformFeedback();
|
|
_prevTfEnable = false;
|
|
}
|
|
|
|
// 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) || _forceShaderUpdate)
|
|
{
|
|
_forceShaderUpdate = false;
|
|
|
|
UpdateShaderState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.TfBufferState))
|
|
{
|
|
UpdateTfBufferState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.ClipDistanceEnable))
|
|
{
|
|
UpdateUserClipState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.RasterizeEnable))
|
|
{
|
|
UpdateRasterizerState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.RtColorState,
|
|
MethodOffset.RtDepthStencilState,
|
|
MethodOffset.RtControl,
|
|
MethodOffset.RtDepthStencilSize,
|
|
MethodOffset.RtDepthStencilEnable))
|
|
{
|
|
UpdateRenderTargetState(state, useControl: true);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.ScissorState))
|
|
{
|
|
UpdateScissorState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.ViewVolumeClipControl))
|
|
{
|
|
UpdateDepthClampState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.AlphaTestEnable,
|
|
MethodOffset.AlphaTestRef,
|
|
MethodOffset.AlphaTestFunc))
|
|
{
|
|
UpdateAlphaTestState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.DepthTestEnable,
|
|
MethodOffset.DepthWriteEnable,
|
|
MethodOffset.DepthTestFunc))
|
|
{
|
|
UpdateDepthTestState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.DepthMode,
|
|
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, MethodOffset.SamplerIndex))
|
|
{
|
|
UpdateSamplerPoolState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.TexturePoolState))
|
|
{
|
|
UpdateTexturePoolState(state);
|
|
}
|
|
|
|
// Input assembler state.
|
|
if (state.QueryModified(MethodOffset.VertexAttribState))
|
|
{
|
|
UpdateVertexAttribState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.PointSize,
|
|
MethodOffset.VertexProgramPointSize,
|
|
MethodOffset.PointSpriteEnable,
|
|
MethodOffset.PointCoordReplace))
|
|
{
|
|
UpdatePointState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.PrimitiveRestartState))
|
|
{
|
|
UpdatePrimitiveRestartState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.IndexBufferState))
|
|
{
|
|
UpdateIndexBufferState(state, firstIndex, indexCount);
|
|
}
|
|
|
|
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.BlendConstant,
|
|
MethodOffset.BlendStateCommon,
|
|
MethodOffset.BlendEnableCommon,
|
|
MethodOffset.BlendEnable,
|
|
MethodOffset.BlendState))
|
|
{
|
|
UpdateBlendState(state);
|
|
}
|
|
|
|
if (state.QueryModified(MethodOffset.LogicOpState))
|
|
{
|
|
UpdateLogicOpState(state);
|
|
}
|
|
|
|
CommitBindings();
|
|
|
|
if (tfEnable && !_prevTfEnable)
|
|
{
|
|
_context.Renderer.Pipeline.BeginTransformFeedback(Topology);
|
|
_prevTfEnable = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates Rasterizer primitive discard state based on guest gpu state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateRasterizerState(GpuState state)
|
|
{
|
|
Boolean32 enable = state.Get<Boolean32>(MethodOffset.RasterizeEnable);
|
|
_context.Renderer.Pipeline.SetRasterizerDiscard(!enable);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures that the bindings are visible to the host GPU.
|
|
/// Note: this actually performs the binding using the host graphics API.
|
|
/// </summary>
|
|
private void CommitBindings()
|
|
{
|
|
UpdateStorageBuffers();
|
|
|
|
BufferManager.CommitGraphicsBindings();
|
|
TextureManager.CommitGraphicsBindings();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates storage buffer bindings.
|
|
/// </summary>
|
|
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;
|
|
|
|
SbDescriptor sbDescriptor = _context.PhysicalMemory.Read<SbDescriptor>(sbDescAddress);
|
|
|
|
BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
/// <param name="useControl">Use draw buffers information from render target control register</param>
|
|
/// <param name="singleUse">If this is not -1, it indicates that only the given indexed target will be used.</param>
|
|
private void UpdateRenderTargetState(GpuState state, bool useControl, int singleUse = -1)
|
|
{
|
|
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();
|
|
|
|
var scissor = state.Get<ScreenScissorState>(MethodOffset.ScreenScissorState);
|
|
Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1);
|
|
|
|
bool changedScale = false;
|
|
|
|
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))
|
|
{
|
|
changedScale |= TextureManager.SetRenderTargetColor(index, null);
|
|
|
|
continue;
|
|
}
|
|
|
|
Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY, sizeHint);
|
|
|
|
changedScale |= TextureManager.SetRenderTargetColor(index, color);
|
|
|
|
if (color != null)
|
|
{
|
|
color.SignalModified();
|
|
}
|
|
}
|
|
|
|
bool dsEnable = state.Get<Boolean32>(MethodOffset.RtDepthStencilEnable);
|
|
|
|
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, sizeHint);
|
|
}
|
|
|
|
changedScale |= TextureManager.SetRenderTargetDepthStencil(depthStencil);
|
|
|
|
if (changedScale)
|
|
{
|
|
TextureManager.UpdateRenderTargetScale(singleUse);
|
|
_context.Renderer.Pipeline.SetRenderTargetScale(TextureManager.RenderTargetScale);
|
|
|
|
UpdateViewportTransform(state);
|
|
UpdateScissorState(state);
|
|
}
|
|
|
|
if (depthStencil != null)
|
|
{
|
|
depthStencil.SignalModified();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a render target color buffer is used.
|
|
/// </summary>
|
|
/// <param name="colorState">Color buffer information</param>
|
|
/// <returns>True if the specified buffer is enabled/used, false otherwise</returns>
|
|
private static bool IsRtEnabled(RtColorState colorState)
|
|
{
|
|
// Colors are disabled by writing 0 to the format.
|
|
return colorState.Format != 0 && colorState.WidthOrStride != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host scissor test state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateScissorState(GpuState state)
|
|
{
|
|
for (int index = 0; index < Constants.TotalViewports; index++)
|
|
{
|
|
ScissorState scissor = state.Get<ScissorState>(MethodOffset.ScissorState, index);
|
|
|
|
bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
|
|
|
|
_context.Renderer.Pipeline.SetScissorEnable(index, enable);
|
|
|
|
if (enable)
|
|
{
|
|
int x = scissor.X1;
|
|
int y = scissor.Y1;
|
|
int width = scissor.X2 - x;
|
|
int height = scissor.Y2 - y;
|
|
|
|
float scale = TextureManager.RenderTargetScale;
|
|
if (scale != 1f)
|
|
{
|
|
x = (int)(x * scale);
|
|
y = (int)(y * scale);
|
|
width = (int)Math.Ceiling(width * scale);
|
|
height = (int)Math.Ceiling(height * scale);
|
|
}
|
|
|
|
_context.Renderer.Pipeline.SetScissor(index, x, y, width, height);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host depth clamp state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateDepthClampState(GpuState state)
|
|
{
|
|
ViewVolumeClipControl clip = state.Get<ViewVolumeClipControl>(MethodOffset.ViewVolumeClipControl);
|
|
_context.Renderer.Pipeline.SetDepthClamp((clip & ViewVolumeClipControl.DepthClampDisabled) == 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host alpha test state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateAlphaTestState(GpuState state)
|
|
{
|
|
_context.Renderer.Pipeline.SetAlphaTest(
|
|
state.Get<Boolean32>(MethodOffset.AlphaTestEnable),
|
|
state.Get<float>(MethodOffset.AlphaTestRef),
|
|
state.Get<CompareOp>(MethodOffset.AlphaTestFunc));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host depth test state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
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)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host viewport transform and clipping state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateViewportTransform(GpuState state)
|
|
{
|
|
var yControl = state.Get<YControl> (MethodOffset.YControl);
|
|
var face = state.Get<FaceState>(MethodOffset.FaceState);
|
|
|
|
UpdateFrontFace(yControl, face.FrontFace);
|
|
|
|
bool flipY = yControl.HasFlag(YControl.NegateY);
|
|
|
|
Span<Viewport> viewports = stackalloc 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);
|
|
|
|
float scaleX = MathF.Abs(transform.ScaleX);
|
|
float scaleY = transform.ScaleY;
|
|
|
|
if (flipY)
|
|
{
|
|
scaleY = -scaleY;
|
|
}
|
|
|
|
if (!_context.Capabilities.SupportsViewportSwizzle && transform.UnpackSwizzleY() == ViewportSwizzle.NegativeY)
|
|
{
|
|
scaleY = -scaleY;
|
|
}
|
|
|
|
if (index == 0)
|
|
{
|
|
// Try to guess the depth mode being used on the high level API
|
|
// based on current transform.
|
|
// It is setup like so by said APIs:
|
|
// If depth mode is ZeroToOne:
|
|
// TranslateZ = Near
|
|
// ScaleZ = Far - Near
|
|
// If depth mode is MinusOneToOne:
|
|
// TranslateZ = (Near + Far) / 2
|
|
// ScaleZ = (Far - Near) / 2
|
|
// DepthNear/Far are sorted such as that Near is always less than Far.
|
|
DepthMode depthMode = extents.DepthNear != transform.TranslateZ &&
|
|
extents.DepthFar != transform.TranslateZ ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne;
|
|
|
|
_context.Renderer.Pipeline.SetDepthMode(depthMode);
|
|
}
|
|
|
|
float x = transform.TranslateX - scaleX;
|
|
float y = transform.TranslateY - scaleY;
|
|
|
|
float width = scaleX * 2;
|
|
float height = scaleY * 2;
|
|
|
|
float scale = TextureManager.RenderTargetScale;
|
|
if (scale != 1f)
|
|
{
|
|
x *= scale;
|
|
y *= scale;
|
|
width *= scale;
|
|
height *= scale;
|
|
}
|
|
|
|
RectangleF region = new RectangleF(x, y, width, height);
|
|
|
|
ViewportSwizzle swizzleX = transform.UnpackSwizzleX();
|
|
ViewportSwizzle swizzleY = transform.UnpackSwizzleY();
|
|
ViewportSwizzle swizzleZ = transform.UnpackSwizzleZ();
|
|
ViewportSwizzle swizzleW = transform.UnpackSwizzleW();
|
|
|
|
float depthNear = extents.DepthNear;
|
|
float depthFar = extents.DepthFar;
|
|
|
|
if (transform.ScaleZ < 0)
|
|
{
|
|
float temp = depthNear;
|
|
depthNear = depthFar;
|
|
depthFar = temp;
|
|
}
|
|
|
|
viewports[index] = new Viewport(region, swizzleX, swizzleY, swizzleZ, swizzleW, depthNear, depthFar);
|
|
}
|
|
|
|
_context.Renderer.Pipeline.SetViewports(0, viewports);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host depth bias (also called polygon offset) state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
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;
|
|
|
|
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 / 2f, clamp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host stencil test state based on current GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates current sampler pool address and size based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateSamplerPoolState(GpuState state)
|
|
{
|
|
var texturePool = state.Get<PoolState>(MethodOffset.TexturePoolState);
|
|
var samplerPool = state.Get<PoolState>(MethodOffset.SamplerPoolState);
|
|
|
|
var samplerIndex = state.Get<SamplerIndex>(MethodOffset.SamplerIndex);
|
|
|
|
int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex
|
|
? texturePool.MaximumId
|
|
: samplerPool.MaximumId;
|
|
|
|
TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates current texture pool address and size based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host vertex attributes based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateVertexAttribState(GpuState state)
|
|
{
|
|
Span<VertexAttribDescriptor> vertexAttribs = stackalloc VertexAttribDescriptor[Constants.TotalVertexAttribs];
|
|
|
|
for (int index = 0; index < Constants.TotalVertexAttribs; index++)
|
|
{
|
|
var vertexAttrib = state.Get<VertexAttribState>(MethodOffset.VertexAttribState, index);
|
|
|
|
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
|
|
{
|
|
Logger.Debug?.Print(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
|
|
|
|
format = Format.R32G32B32A32Float;
|
|
}
|
|
|
|
vertexAttribs[index] = new VertexAttribDescriptor(
|
|
vertexAttrib.UnpackBufferIndex(),
|
|
vertexAttrib.UnpackOffset(),
|
|
vertexAttrib.UnpackIsConstant(),
|
|
format);
|
|
}
|
|
|
|
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host point size based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdatePointState(GpuState state)
|
|
{
|
|
float size = state.Get<float>(MethodOffset.PointSize);
|
|
bool isProgramPointSize = state.Get<Boolean32>(MethodOffset.VertexProgramPointSize);
|
|
bool enablePointSprite = state.Get<Boolean32>(MethodOffset.PointSpriteEnable);
|
|
|
|
// TODO: Need to figure out a way to map PointCoordReplace enable bit.
|
|
Origin origin = (state.Get<int>(MethodOffset.PointCoordReplace) & 4) == 0 ? Origin.LowerLeft : Origin.UpperLeft;
|
|
|
|
_context.Renderer.Pipeline.SetPointParameters(size, isProgramPointSize, enablePointSprite, origin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host primitive restart based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdatePrimitiveRestartState(GpuState state)
|
|
{
|
|
PrimitiveRestartState primitiveRestart = state.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
|
|
|
|
_context.Renderer.Pipeline.SetPrimitiveRestart(
|
|
primitiveRestart.Enable,
|
|
primitiveRestart.Index);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host index buffer binding based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</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>
|
|
private void UpdateIndexBufferState(GpuState state, int firstIndex, int indexCount)
|
|
{
|
|
var indexBuffer = state.Get<IndexBufferState>(MethodOffset.IndexBufferState);
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host vertex buffer bindings based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateVertexBufferState(GpuState state)
|
|
{
|
|
_isAnyVbInstanced = false;
|
|
|
|
for (int index = 0; index < Constants.TotalVertexBuffers; 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 (_ibStreamer.HasInlineIndexData || _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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host face culling and orientation based on guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateFaceState(GpuState state)
|
|
{
|
|
var yControl = state.Get<YControl> (MethodOffset.YControl);
|
|
var face = state.Get<FaceState>(MethodOffset.FaceState);
|
|
|
|
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
|
|
|
|
UpdateFrontFace(yControl, face.FrontFace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the front face based on the current front face and the origin.
|
|
/// </summary>
|
|
/// <param name="yControl">Y control register value, where the origin is located</param>
|
|
/// <param name="frontFace">Front face</param>
|
|
private void UpdateFrontFace(YControl yControl, FrontFace frontFace)
|
|
{
|
|
bool isUpperLeftOrigin = !yControl.HasFlag(YControl.TriangleRastFlip);
|
|
|
|
if (isUpperLeftOrigin)
|
|
{
|
|
frontFace = frontFace == FrontFace.CounterClockwise ? FrontFace.Clockwise : FrontFace.CounterClockwise;
|
|
}
|
|
|
|
_context.Renderer.Pipeline.SetFrontFace(frontFace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host render target color masks, based on guest GPU state.
|
|
/// This defines which color channels are written to each color buffer.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateRtColorMask(GpuState state)
|
|
{
|
|
bool rtColorMaskShared = state.Get<Boolean32>(MethodOffset.RtColorMaskShared);
|
|
|
|
Span<uint> componentMasks = stackalloc uint[Constants.TotalRenderTargets];
|
|
|
|
for (int index = 0; index < Constants.TotalRenderTargets; index++)
|
|
{
|
|
var colorMask = state.Get<RtColorMask>(MethodOffset.RtColorMask, rtColorMaskShared ? 0 : index);
|
|
|
|
uint componentMask;
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host render target color buffer blending state, based on guest state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateBlendState(GpuState state)
|
|
{
|
|
bool blendIndependent = state.Get<Boolean32>(MethodOffset.BlendIndependent);
|
|
ColorF blendConstant = state.Get<ColorF>(MethodOffset.BlendConstant);
|
|
|
|
for (int index = 0; index < Constants.TotalRenderTargets; 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,
|
|
blendConstant,
|
|
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,
|
|
blendConstant,
|
|
blend.ColorOp,
|
|
blend.ColorSrcFactor,
|
|
blend.ColorDstFactor,
|
|
blend.AlphaOp,
|
|
blend.AlphaSrcFactor,
|
|
blend.AlphaDstFactor);
|
|
}
|
|
|
|
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host logical operation state, based on guest state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
public void UpdateLogicOpState(GpuState state)
|
|
{
|
|
LogicalOpState logicOpState = state.Get<LogicalOpState>(MethodOffset.LogicOpState);
|
|
|
|
_context.Renderer.Pipeline.SetLogicOpState(logicOpState.Enable, logicOpState.LogicalOp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Storage buffer address and size information.
|
|
/// </summary>
|
|
private 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates host shaders based on the guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
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;
|
|
}
|
|
|
|
ShaderBundle gs = ShaderCache.GetGraphicsShader(state, addresses);
|
|
|
|
_vsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
|
|
|
|
int storageBufferBindingsCount = 0;
|
|
int uniformBufferBindingsCount = 0;
|
|
|
|
for (int stage = 0; stage < Constants.ShaderStages; stage++)
|
|
{
|
|
ShaderProgramInfo info = gs.Shaders[stage]?.Info;
|
|
|
|
_currentProgramInfo[stage] = info;
|
|
|
|
if (info == null)
|
|
{
|
|
TextureManager.SetGraphicsTextures(stage, Array.Empty<TextureBindingInfo>());
|
|
TextureManager.SetGraphicsImages(stage, Array.Empty<TextureBindingInfo>());
|
|
BufferManager.SetGraphicsStorageBufferBindings(stage, null);
|
|
BufferManager.SetGraphicsUniformBufferBindings(stage, 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 = ShaderTexture.GetTarget(descriptor.Type);
|
|
|
|
textureBindings[index] = new TextureBindingInfo(
|
|
target,
|
|
descriptor.Binding,
|
|
descriptor.CbufSlot,
|
|
descriptor.HandleIndex,
|
|
descriptor.Flags);
|
|
}
|
|
|
|
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 = ShaderTexture.GetTarget(descriptor.Type);
|
|
Format format = ShaderTexture.GetFormat(descriptor.Format);
|
|
|
|
imageBindings[index] = new TextureBindingInfo(
|
|
target,
|
|
format,
|
|
descriptor.Binding,
|
|
descriptor.CbufSlot,
|
|
descriptor.HandleIndex,
|
|
descriptor.Flags);
|
|
}
|
|
|
|
TextureManager.SetGraphicsImages(stage, imageBindings);
|
|
|
|
BufferManager.SetGraphicsStorageBufferBindings(stage, info.SBuffers);
|
|
BufferManager.SetGraphicsUniformBufferBindings(stage, info.CBuffers);
|
|
|
|
if (info.SBuffers.Count != 0)
|
|
{
|
|
storageBufferBindingsCount = Math.Max(storageBufferBindingsCount, info.SBuffers.Max(x => x.Binding) + 1);
|
|
}
|
|
|
|
if (info.CBuffers.Count != 0)
|
|
{
|
|
uniformBufferBindingsCount = Math.Max(uniformBufferBindingsCount, info.CBuffers.Max(x => x.Binding) + 1);
|
|
}
|
|
}
|
|
|
|
BufferManager.SetGraphicsStorageBufferBindingsCount(storageBufferBindingsCount);
|
|
BufferManager.SetGraphicsUniformBufferBindingsCount(uniformBufferBindingsCount);
|
|
|
|
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates transform feedback buffer state based on the guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateTfBufferState(GpuState state)
|
|
{
|
|
for (int index = 0; index < Constants.TotalTransformFeedbackBuffers; index++)
|
|
{
|
|
TfBufferState tfb = state.Get<TfBufferState>(MethodOffset.TfBufferState, index);
|
|
|
|
if (!tfb.Enable)
|
|
{
|
|
BufferManager.SetTransformFeedbackBuffer(index, 0, 0);
|
|
|
|
continue;
|
|
}
|
|
|
|
BufferManager.SetTransformFeedbackBuffer(index, tfb.Address.Pack(), (uint)tfb.Size);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates user-defined clipping based on the guest GPU state.
|
|
/// </summary>
|
|
/// <param name="state">Current GPU state</param>
|
|
private void UpdateUserClipState(GpuState state)
|
|
{
|
|
int clipMask = state.Get<int>(MethodOffset.ClipDistanceEnable);
|
|
|
|
for (int i = 0; i < Constants.TotalClipDistances; ++i)
|
|
{
|
|
_context.Renderer.Pipeline.SetUserClipDistance(i, (clipMask & (1 << i)) != 0);
|
|
}
|
|
}
|
|
|
|
/// <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="state">Current GPU state (unused)</param>
|
|
/// <param name="argument">Method call argument (unused)</param>
|
|
private void TextureBarrier(GpuState state, int argument)
|
|
{
|
|
_context.Renderer.Pipeline.TextureBarrier();
|
|
}
|
|
|
|
/// <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="state">Current GPU state (unused)</param>
|
|
/// <param name="argument">Method call argument (unused)</param>
|
|
private void TextureBarrierTiled(GpuState state, int argument)
|
|
{
|
|
_context.Renderer.Pipeline.TextureBarrierTiled();
|
|
}
|
|
}
|
|
} |