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 System;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Engine
{
using Texture = Image.Texture;
///
/// GPU method implementations.
///
partial class Methods
{
private readonly GpuContext _context;
private readonly ShaderProgramInfo[] _currentProgramInfo;
///
/// In-memory shader cache.
///
public ShaderCache ShaderCache { get; }
///
/// GPU buffer manager.
///
public BufferManager BufferManager { get; }
///
/// GPU texture manager.
///
public TextureManager TextureManager { get; }
private bool _isAnyVbInstanced;
private bool _vsUsesInstanceId;
///
/// Creates a new instance of the GPU methods class.
///
/// GPU context
public Methods(GpuContext context)
{
_context = context;
ShaderCache = new ShaderCache(_context);
_currentProgramInfo = new ShaderProgramInfo[Constants.ShaderStages];
BufferManager = new BufferManager(context);
TextureManager = new TextureManager(context);
}
///
/// Register callback for GPU method calls that triggers an action on the GPU.
///
/// GPU state where the triggers will be registered
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);
}
///
/// Updates host state based on the current guest GPU state.
///
/// Guest GPU state
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.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.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();
}
///
/// Ensures that the bindings are visible to the host GPU.
/// Note: this actually performs the binding using the host graphics API.
///
private void CommitBindings()
{
UpdateStorageBuffers();
BufferManager.CommitBindings();
TextureManager.CommitGraphicsBindings();
}
///
/// Updates storage buffer bindings.
///
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 sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
SbDescriptor sbDescriptor = MemoryMarshal.Cast(sbDescriptorData)[0];
BufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
}
}
}
///
/// Updates render targets (color and depth-stencil buffers) based on current render target state.
///
/// Current GPU state
/// Use draw buffers information from render target control register
private void UpdateRenderTargetState(GpuState state, bool useControl)
{
var rtControl = state.Get(MethodOffset.RtControl);
int count = useControl ? rtControl.UnpackCount() : Constants.TotalRenderTargets;
var msaaMode = state.Get(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(MethodOffset.RtColorState, rtIndex);
if (index >= count || !IsRtEnabled(colorState))
{
TextureManager.SetRenderTargetColor(index, null);
continue;
}
Texture color = TextureManager.FindOrCreateTexture(colorState, samplesInX, samplesInY);
TextureManager.SetRenderTargetColor(index, color);
if (color != null)
{
color.Modified = true;
}
}
bool dsEnable = state.Get(MethodOffset.RtDepthStencilEnable);
Texture depthStencil = null;
if (dsEnable)
{
var dsState = state.Get(MethodOffset.RtDepthStencilState);
var dsSize = state.Get (MethodOffset.RtDepthStencilSize);
depthStencil = TextureManager.FindOrCreateTexture(dsState, dsSize, samplesInX, samplesInY);
}
TextureManager.SetRenderTargetDepthStencil(depthStencil);
if (depthStencil != null)
{
depthStencil.Modified = true;
}
}
///
/// Checks if a render target color buffer is used.
///
/// Color buffer information
/// True if the specified buffer is enabled/used, false otherwise
private static bool IsRtEnabled(RtColorState colorState)
{
// Colors are disabled by writing 0 to the format.
return colorState.Format != 0 && colorState.WidthOrStride != 0;
}
///
/// Updates host depth test state based on current GPU state.
///
/// Current GPU state
private void UpdateDepthTestState(GpuState state)
{
_context.Renderer.Pipeline.SetDepthTest(new DepthTestDescriptor(
state.Get(MethodOffset.DepthTestEnable),
state.Get(MethodOffset.DepthWriteEnable),
state.Get(MethodOffset.DepthTestFunc)));
}
///
/// Updates host viewport transform and clipping state based on current GPU state.
///
/// Current GPU state
private void UpdateViewportTransform(GpuState state)
{
DepthMode depthMode = state.Get(MethodOffset.DepthMode);
_context.Renderer.Pipeline.SetDepthMode(depthMode);
bool flipY = (state.Get(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(MethodOffset.ViewportTransform, index);
var extents = state.Get (MethodOffset.ViewportExtents, index);
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;
RectangleF region = new RectangleF(x, y, width, height);
viewports[index] = new Viewport(
region,
transform.UnpackSwizzleX(),
transform.UnpackSwizzleY(),
transform.UnpackSwizzleZ(),
transform.UnpackSwizzleW(),
extents.DepthNear,
extents.DepthFar);
}
_context.Renderer.Pipeline.SetViewports(0, viewports);
}
///
/// Updates host depth bias (also called polygon offset) state based on current GPU state.
///
/// Current GPU state
private void UpdateDepthBiasState(GpuState state)
{
var depthBias = state.Get(MethodOffset.DepthBiasState);
float factor = state.Get(MethodOffset.DepthBiasFactor);
float units = state.Get(MethodOffset.DepthBiasUnits);
float clamp = state.Get(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, clamp);
}
///
/// Updates host stencil test state based on current GPU state.
///
/// Current GPU state
private void UpdateStencilTestState(GpuState state)
{
var backMasks = state.Get (MethodOffset.StencilBackMasks);
var test = state.Get (MethodOffset.StencilTestState);
var backTest = state.Get(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));
}
///
/// Updates current sampler pool address and size based on guest GPU state.
///
/// Current GPU state
private void UpdateSamplerPoolState(GpuState state)
{
var texturePool = state.Get(MethodOffset.TexturePoolState);
var samplerPool = state.Get(MethodOffset.SamplerPoolState);
var samplerIndex = state.Get(MethodOffset.SamplerIndex);
int maximumId = samplerIndex == SamplerIndex.ViaHeaderIndex
? texturePool.MaximumId
: samplerPool.MaximumId;
TextureManager.SetGraphicsSamplerPool(samplerPool.Address.Pack(), maximumId, samplerIndex);
}
///
/// Updates current texture pool address and size based on guest GPU state.
///
/// Current GPU state
private void UpdateTexturePoolState(GpuState state)
{
var texturePool = state.Get(MethodOffset.TexturePoolState);
TextureManager.SetGraphicsTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
TextureManager.SetGraphicsTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex));
}
///
/// Updates host vertex attributes based on guest GPU state.
///
/// Current GPU state
private void UpdateVertexAttribState(GpuState state)
{
VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
for (int index = 0; index < 16; index++)
{
var vertexAttrib = state.Get(MethodOffset.VertexAttribState, index);
if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
{
Logger.PrintDebug(LogClass.Gpu, $"Invalid attribute format 0x{vertexAttrib.UnpackFormat():X}.");
format = Format.R32G32B32A32Float;
}
vertexAttribs[index] = new VertexAttribDescriptor(
vertexAttrib.UnpackBufferIndex(),
vertexAttrib.UnpackOffset(),
format);
}
_context.Renderer.Pipeline.SetVertexAttribs(vertexAttribs);
}
///
/// Updates host primitive restart based on guest GPU state.
///
/// Current GPU state
private void UpdatePrimitiveRestartState(GpuState state)
{
PrimitiveRestartState primitiveRestart = state.Get(MethodOffset.PrimitiveRestartState);
_context.Renderer.Pipeline.SetPrimitiveRestart(
primitiveRestart.Enable,
primitiveRestart.Index);
}
///
/// Updates host index buffer binding based on guest GPU state.
///
/// Current GPU state
private void UpdateIndexBufferState(GpuState state)
{
var indexBuffer = state.Get(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);
}
///
/// Updates host vertex buffer bindings based on guest GPU state.
///
/// Current GPU state
private void UpdateVertexBufferState(GpuState state)
{
_isAnyVbInstanced = false;
for (int index = 0; index < 16; index++)
{
var vertexBuffer = state.Get(MethodOffset.VertexBufferState, index);
if (!vertexBuffer.UnpackEnable())
{
BufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
continue;
}
GpuVa endAddress = state.Get(MethodOffset.VertexBufferEndAddress, index);
ulong address = vertexBuffer.Address.Pack();
int stride = vertexBuffer.UnpackStride();
bool instanced = state.Get(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(MethodOffset.FirstInstance);
var drawState = state.Get(MethodOffset.VertexBufferDrawState);
size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
}
BufferManager.SetVertexBuffer(index, address, size, stride, divisor);
}
}
///
/// Updates host face culling and orientation based on guest GPU state.
///
/// Current GPU state
private void UpdateFaceState(GpuState state)
{
var face = state.Get(MethodOffset.FaceState);
_context.Renderer.Pipeline.SetFaceCulling(face.CullEnable, face.CullFace);
_context.Renderer.Pipeline.SetFrontFace(face.FrontFace);
}
///
/// Updates host render target color masks, based on guest GPU state.
/// This defines which color channels are written to each color buffer.
///
/// Current GPU state
private void UpdateRtColorMask(GpuState state)
{
bool rtColorMaskShared = state.Get(MethodOffset.RtColorMaskShared);
uint[] componentMasks = new uint[Constants.TotalRenderTargets];
for (int index = 0; index < Constants.TotalRenderTargets; index++)
{
var colorMask = state.Get(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);
}
///
/// Updates host render target color buffer blending state, based on guest state.
///
/// Current GPU state
private void UpdateBlendState(GpuState state)
{
bool blendIndependent = state.Get(MethodOffset.BlendIndependent);
for (int index = 0; index < 8; index++)
{
BlendDescriptor descriptor;
if (blendIndependent)
{
bool enable = state.Get (MethodOffset.BlendEnable, index);
var blend = state.Get(MethodOffset.BlendState, index);
descriptor = new BlendDescriptor(
enable,
blend.ColorOp,
blend.ColorSrcFactor,
blend.ColorDstFactor,
blend.AlphaOp,
blend.AlphaSrcFactor,
blend.AlphaDstFactor);
}
else
{
bool enable = state.Get (MethodOffset.BlendEnable, 0);
var blend = state.Get(MethodOffset.BlendStateCommon);
descriptor = new BlendDescriptor(
enable,
blend.ColorOp,
blend.ColorSrcFactor,
blend.ColorDstFactor,
blend.AlphaOp,
blend.AlphaSrcFactor,
blend.AlphaDstFactor);
}
_context.Renderer.Pipeline.SetBlendState(index, descriptor);
}
}
///
/// Storage buffer address and size information.
///
private struct SbDescriptor
{
public uint AddressLow;
public uint AddressHigh;
public int Size;
public int Padding;
public ulong PackAddress()
{
return AddressLow | ((ulong)AddressHigh << 32);
}
}
///
/// Updates host shaders based on the guest GPU state.
///
/// Current GPU state
private void UpdateShaderState(GpuState state)
{
ShaderAddresses addresses = new ShaderAddresses();
Span addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
Span addressesArray = MemoryMarshal.Cast(addressesSpan);
ulong baseAddress = state.Get(MethodOffset.ShaderBaseAddress).Pack();
for (int index = 0; index < 6; index++)
{
var shader = state.Get(MethodOffset.ShaderState, index);
if (!shader.UnpackEnable() && index != 1)
{
continue;
}
addressesArray[index] = baseAddress + shader.Offset;
}
GraphicsShader gs = ShaderCache.GetGraphicsShader(state, addresses);
_vsUsesInstanceId = gs.Shaders[0].Program.Info.UsesInstanceId;
for (int stage = 0; stage < Constants.ShaderStages; stage++)
{
ShaderProgramInfo info = gs.Shaders[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);
if (descriptor.IsBindless)
{
textureBindings[index] = new TextureBindingInfo(target, descriptor.CbufSlot, descriptor.CbufOffset);
}
else
{
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.SetProgram(gs.HostProgram);
}
///
/// Gets texture target from a sampler type.
///
/// Sampler type
/// Texture target value
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;
}
Logger.PrintWarning(LogClass.Gpu, $"Invalid sampler type \"{type}\".");
return Target.Texture2D;
}
///
/// Issues a texture barrier.
/// This waits until previous texture writes from the GPU to finish, before
/// performing new operations with said textures.
///
/// Current GPU state (unused)
/// Method call argument (unused)
private void TextureBarrier(GpuState state, int argument)
{
_context.Renderer.Pipeline.TextureBarrier();
}
///
/// Invalidates all modified textures on the cache.
///
/// Current GPU state (unused)
/// Method call argument (unused)
private void InvalidateTextures(GpuState state, int argument)
{
TextureManager.Flush();
}
///
/// 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.
///
/// Current GPU state (unused)
/// Method call argument (unused)
private void TextureBarrierTiled(GpuState state, int argument)
{
_context.Renderer.Pipeline.TextureBarrierTiled();
}
}
}