using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Memory;
using System;
namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
///
/// Draw manager.
///
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 _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;
///
/// Creates a new instance of the draw manager.
///
/// GPU context
/// GPU channel
/// Channel state
/// Draw state
/// Specialization state updater
public DrawManager(GpuContext context, GpuChannel channel, DeviceStateWithShadow state, DrawState drawState, SpecializationStateUpdater spec)
{
_context = context;
_channel = channel;
_state = state;
_drawState = drawState;
_currentSpecState = spec;
}
///
/// Marks the entire state as dirty, forcing a full host state update before the next draw.
///
public void ForceStateDirty()
{
_topologySet = false;
}
///
/// Pushes four 8-bit index buffer elements.
///
/// Method call argument
public void VbElementU8(int argument)
{
_drawState.IbStreamer.VbElementU8(_context.Renderer, argument);
}
///
/// Pushes two 16-bit index buffer elements.
///
/// Method call argument
public void VbElementU16(int argument)
{
_drawState.IbStreamer.VbElementU16(_context.Renderer, argument);
}
///
/// Pushes one 32-bit index buffer element.
///
/// Method call argument
public void VbElementU32(int argument)
{
_drawState.IbStreamer.VbElementU32(_context.Renderer, argument);
}
///
/// Finishes the draw call.
/// This draws geometry on the bound buffers based on the current GPU state.
///
/// 3D engine where this method is being called
/// Method call argument
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);
}
///
/// Finishes the draw call.
/// This draws geometry on the bound buffers based on the current GPU state.
///
/// 3D engine where this method is being called
/// Index of the first index buffer element used on the draw
/// Number of index buffer elements used on the draw
/// Index of the first vertex used on the draw
/// Number of vertices used on the draw
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();
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();
}
}
///
/// Starts draw.
/// This sets primitive type and instanced draw parameters.
///
/// Method call argument
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);
}
///
/// Starts draw.
/// This sets primitive type and instanced draw parameters.
///
/// Indicates if the current instance should be incremented
/// Indicates if the current instance should be set to zero
/// Primitive type
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);
}
///
/// Updates the current primitive topology if needed.
///
/// New primitive topology
private void UpdateTopology(PrimitiveTopology topology)
{
if (_drawState.Topology != topology || !_topologySet)
{
_context.Renderer.Pipeline.SetPrimitiveTopology(topology);
_currentSpecState.SetTopology(topology);
_drawState.Topology = topology;
_topologySet = true;
}
}
///
/// Sets the index buffer count.
/// This also sets internal state that indicates that the next draw is an indexed draw.
///
/// Method call argument
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.
///
/// Performs a indexed draw with 8-bit index buffer elements.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer8BeginEndInstanceFirst(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, false);
}
///
/// Performs a indexed draw with 16-bit index buffer elements.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer16BeginEndInstanceFirst(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, false);
}
///
/// Performs a indexed draw with 32-bit index buffer elements.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer32BeginEndInstanceFirst(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, false);
}
///
/// Performs a indexed draw with 8-bit index buffer elements,
/// while also pre-incrementing the current instance value.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer8BeginEndInstanceSubsequent(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, true);
}
///
/// Performs a indexed draw with 16-bit index buffer elements,
/// while also pre-incrementing the current instance value.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer16BeginEndInstanceSubsequent(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, true);
}
///
/// Performs a indexed draw with 32-bit index buffer elements,
/// while also pre-incrementing the current instance value.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawIndexBuffer32BeginEndInstanceSubsequent(ThreedClass engine, int argument)
{
DrawIndexBufferBeginEndInstance(engine, argument, true);
}
///
/// Performs a indexed draw with a low number of index buffer elements,
/// while optionally also pre-incrementing the current instance value.
///
/// 3D engine where this method is being called
/// Method call argument
/// True to increment the current instance value, false otherwise
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;
}
///
/// Performs a non-indexed draw with the specified topology, index and count.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawVertexArrayBeginEndInstanceFirst(ThreedClass engine, int argument)
{
DrawVertexArrayBeginEndInstance(engine, argument, false);
}
///
/// Performs a non-indexed draw with the specified topology, index and count,
/// while incrementing the current instance.
///
/// 3D engine where this method is being called
/// Method call argument
public void DrawVertexArrayBeginEndInstanceSubsequent(ThreedClass engine, int argument)
{
DrawVertexArrayBeginEndInstance(engine, argument, true);
}
///
/// Performs a indexed draw with a low number of index buffer elements,
/// while optionally also pre-incrementing the current instance value.
///
/// 3D engine where this method is being called
/// Method call argument
/// True to increment the current instance value, false otherwise
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;
}
///
/// Performs a texture draw with a source texture and sampler ID, along with source
/// and destination coordinates and sizes.
///
/// 3D engine where this method is being called
/// Method call argument
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));
}
///
/// Performs a indexed or non-indexed draw.
///
/// 3D engine where this method is being called
/// Primitive topology
/// Index count for indexed draws, vertex count for non-indexed draws
/// Instance count
/// First index on the index buffer for indexed draws, ignored for non-indexed draws
/// First vertex on the vertex buffer
/// First instance
/// True if the draw is indexed, false otherwise
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();
}
}
///
/// Performs a indirect draw, with parameters from a GPU buffer.
///
/// 3D engine where this method is being called
/// Primitive topology
/// Address of the buffer with the draw parameters, such as count, first index, etc
/// Address of the buffer with the draw count
/// Maximum number of draws that can be made
/// Distance in bytes between each entry on the data pointed to by
/// Maximum number of indices that the draw can consume
/// Type of the indirect draw, which can be indexed or non-indexed, with or without a draw count
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();
}
}
///
/// 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.
///
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();
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);
}
}
}
///
/// Clears the current color and depth-stencil buffers.
/// Which buffers should be cleared can also be specified with the argument.
///
/// 3D engine where this method is being called
/// Method call argument
public void Clear(ThreedClass engine, int argument)
{
Clear(engine, argument, 1);
}
///
/// Clears the current color and depth-stencil buffers.
/// Which buffers should be cleared can also specified with the arguments.
///
/// 3D engine where this method is being called
/// Method call argument
/// For array and 3D textures, indicates how many layers should be cleared
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;
}
int index = (argument >> 6) & 0xf;
int layer = (argument >> 10) & 0x3ff;
engine.UpdateRenderTargetState(useControl: false, layered: layer != 0 || layerCount > 1, singleUse: index);
// 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> scissors = stackalloc Rectangle[]
{
new Rectangle(scissorX, scissorY, scissorW, scissorH)
};
_context.Renderer.Pipeline.SetScissors(scissors);
}
if (clipMismatch)
{
_channel.TextureManager.UpdateRenderTarget(index);
}
else
{
_channel.TextureManager.UpdateRenderTargets();
}
bool clearDepth = (argument & 1) != 0;
bool clearStencil = (argument & 2) != 0;
uint componentMask = (uint)((argument >> 2) & 0xf);
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(useControl: true);
if (renderEnable == ConditionalRenderEnabled.Host)
{
_context.Renderer.Pipeline.EndHostConditionalRendering();
}
}
}
}