Files
sbox-public/engine/Sandbox.Engine/Systems/Render/CommandList/CommandList.cs
Sam Pavlovic 920e3760bc Bunch of SSR fixes (#3481)
* Fix SSR not reprojecting correctly and drifting on downsample, reference original code and backport some of the stuff from motion vectors too, major cleanup

* Logic for RenderTarget.GetTemporary probably shouldn't ceil, causes a lot of drifting in SSR even though the logic is correct, can see downsampling up to 10 and still remaining sharp

https://files.facepunch.com/sampavlovic/1b2511b1/sbox-dev_dB9j5ipUTA.mp4

* Barriers are very strict on Intel ARC, make them explicit for SSAO and SSR, while at it make this logic for SSR simpler

For reference this is how it could appear on Intel ARC B580

https://files.facepunch.com/sampavlovic/1b2511b1/sbox-dev_UjkEGVD4k4.mp4

* CommandList.ResourceBarrierTransition for Textures/RenderTargets

* Fix Reflections_GetHitPositionReprojection while being aware of Motion::Get for future Motion Vector API, deal with temporal stability better

https://files.facepunch.com/sampavlovic/1b2811b1/sbox-dev_He8gAbtPau.mp4

* Build shader

* Format
2025-11-28 14:35:05 -03:00

1082 lines
45 KiB
C#

using System.Threading;
namespace Sandbox.Rendering;
public sealed unsafe partial class CommandList
{
readonly Lock _lock = new Lock();
public string DebugName { get; set; }
public bool Enabled { get; set; }
public Flag Flags { get; set; }
public CommandList()
{
Enabled = true;
Attributes = new AttributeAccess( this, GetLocalAttributes );
GlobalAttributes = new AttributeAccess( this, GetFrameAttributes );
}
public CommandList( string debugName ) : this()
{
DebugName = debugName;
}
/// <summary>
/// Holds the function and state data for a single command.
/// We should FIGHT to keep this as small as possible. Every byte
/// you add to this makes it WORSE.
/// </summary>
struct Entry
{
public delegate*< ref Entry, CommandList, void > Execute;
// These should store REFERENCE types only. If you store value types here
// they will be boxed and allocate. It's not the end of the world, but it's something.
public object Object1;
public object Object2;
public object Object3;
public object Object4;
public object Object5;
public Vector4 Data1;
public Vector4 Data2;
public Vector4 Data3;
}
/// <summary>
/// An ordered list of entries that will execute on the render thread.
/// </summary>
readonly List<Entry> _entries = new List<Entry>( 8 );
[System.Runtime.CompilerServices.MethodImpl( System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining )]
void AddEntry( delegate*< ref Entry, CommandList, void > execute, Entry data )
{
data.Execute = execute;
_entries.Add( data );
}
[Obsolete]
RenderAttributes attributes => Graphics.Attributes;
/// <summary>
/// Access to simple 2D painting functions to draw shapes and text.
/// </summary>
public HudPainter Paint => new HudPainter( this );
/// <summary>
/// This lives for the lifetime of the command list and is
/// used to store temporary render targets and other state.
/// </summary>
private class State
{
public Dictionary<string, RenderTarget> renderTargets = new();
/// <summary>
/// Should be called at the end of usage
/// </summary>
public void Reset()
{
// We just clear the list - RenderTargets get freed and
// re-added to the pool automatically.
renderTargets.Clear();
}
/// <summary>
/// Sneaky way for externals to get render target
/// </summary>
public RenderTarget GetRenderTarget( string name )
{
if ( renderTargets.TryGetValue( name, out var target ) )
return target;
return default;
}
}
State state;
public void Reset()
{
GlobalAttributes.ClearRenderTargets();
Attributes.ClearRenderTargets();
_entries.Clear();
}
public void Blit( Material material, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Blit( (Material)entry.Object1, (RenderAttributes)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = material, Object2 = attributes } );
}
public void DrawQuad( Rect rect, Material material, Color color )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawQuad( new Rect( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w ), (Material)entry.Object1, new Color( entry.Data2.x, entry.Data2.y, entry.Data2.z, entry.Data2.w ) );
}
AddEntry( &Execute, new Entry { Data1 = new Vector4( rect.Left, rect.Top, rect.Width, rect.Height ), Object1 = material, Data2 = new Vector4( color.r, color.g, color.b, color.a ) } );
}
public void DrawScreenQuad( Material material, Color color )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawQuad( Graphics.Viewport, (Material)entry.Object1, new Color( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w ) );
}
AddEntry( &Execute, new Entry { Object1 = material, Data1 = new Vector4( color.r, color.g, color.b, color.a ) } );
}
[Obsolete]
public void Set( StringToken token, float f )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( f, 0, 0, 0 ) } );
}
[Obsolete] public void Set( StringToken token, double f ) => Set( token, (float)f );
[Obsolete]
public void Set( StringToken token, Vector2 vector2 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, new Vector2( entry.Data1.x, entry.Data1.y ) );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( vector2.x, vector2.y, 0, 0 ) } );
}
[Obsolete]
public void Set( StringToken token, Vector3 vector3 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, new Vector3( entry.Data1.x, entry.Data1.y, entry.Data1.z ) );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( vector3.x, vector3.y, vector3.z, 0 ) } );
}
[Obsolete]
public void Set( StringToken token, Vector4 vector4 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, entry.Data1 );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = vector4 } );
}
[Obsolete]
public void Set( StringToken token, int i )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( i, 0, 0, 0 ) } );
}
[Obsolete]
public void Set( StringToken token, bool b )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( b ? 1 : 0, 0, 0, 0 ) } );
}
[Obsolete]
public void Set( StringToken token, Matrix matrix )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, (Matrix)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = matrix } );
}
[Obsolete]
public void Set( StringToken token, GpuBuffer buffer )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, (GpuBuffer)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = buffer } );
}
[Obsolete]
public void Set( StringToken token, Texture texture )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( (StringToken)entry.Object1, (Texture)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = texture } );
}
[Obsolete]
public void SetCombo( StringToken token, int value )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetCombo( (StringToken)entry.Object1, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( value, 0, 0, 0 ) } );
}
[Obsolete]
public void SetCombo( StringToken token, bool value )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetCombo( (StringToken)entry.Object1, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( value ? 1 : 0, 0, 0, 0 ) } );
}
[Obsolete]
public void SetCombo<T>( StringToken token, T t ) where T : unmanaged, Enum
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetComboEnum( (StringToken)entry.Object1, (T)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = t } );
}
[Obsolete]
public void SetConstantBuffer<T>( StringToken token, T data ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetData( (StringToken)entry.Object1, (T)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = data } );
}
[Obsolete]
public void SetGlobal( StringToken token, GpuBuffer buffer )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, (GpuBuffer)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = buffer } );
}
[Obsolete]
public void SetGlobal( StringToken token, int i )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( i, 0, 0, 0 ) } );
}
[Obsolete]
public void SetGlobal( StringToken token, bool b )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( b ? 1 : 0, 0, 0, 0 ) } );
}
[Obsolete]
public void SetGlobal( StringToken token, float f )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( f, 0, 0, 0 ) } );
}
[Obsolete] public void SetGlobal( StringToken token, double f ) => SetGlobal( token, (float)f );
[Obsolete]
public void SetGlobal( StringToken token, Vector2 vector2 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, new Vector2( entry.Data1.x, entry.Data1.y ) );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( vector2.x, vector2.y, 0, 0 ) } );
}
[Obsolete]
public void SetGlobal( StringToken token, Vector3 vector3 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, new Vector3( entry.Data1.x, entry.Data1.y, entry.Data1.z ) );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = new Vector4( vector3.x, vector3.y, vector3.z, 0 ) } );
}
[Obsolete]
public void SetGlobal( StringToken token, Vector4 vector4 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, entry.Data1 );
}
AddEntry( &Execute, new Entry { Object1 = token, Data1 = vector4 } );
}
[Obsolete]
public void SetGlobal( StringToken token, Matrix matrix )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, (Matrix)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = matrix } );
}
[Obsolete]
public void SetGlobal( StringToken token, Texture texture )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( (StringToken)entry.Object1, (Texture)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = token, Object2 = texture } );
}
/// <summary>
/// Takes a copy of the framebuffer and returns a handle to it
/// </summary>
/// <param name="token"></param>
/// <param name="withMips">Generates mipmaps on the grabbed texture filtered with gaussian blur for each mip</param>
/// <returns></returns>
[Obsolete]
public RenderTargetHandle GrabFrameTexture( string token, bool withMips = false ) => Attributes.GrabFrameTexture( token, withMips );
/// <summary>
/// Takes a copy of the depthbuffer and returns a handle to it
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[Obsolete]
public RenderTargetHandle GrabDepthTexture( string token ) => Attributes.GrabDepthTexture( token );
/// <summary>
/// Run this CommandList here
/// </summary>
public void InsertList( CommandList otherBuffer )
{
if ( otherBuffer == this ) return;
// TODO - check to make sure we don't create an infinite loop?
// maybe make a local int here, increment every call, throw exception if it's over 2?
static void Execute( ref Entry entry, CommandList commandList )
{
((CommandList)entry.Object1).ExecuteOnRenderThread();
}
AddEntry( &Execute, new Entry { Object1 = otherBuffer } );
}
/// <summary>
/// Run this command list
/// </summary>
internal void ExecuteOnRenderThread()
{
if ( !Enabled )
return;
// lock - we only want to excute this once at a time, because
// we have local state (renderTargets). If this turns out to be
// a big problem we can probably create a system where we pass the
// stat around.
lock ( _lock )
{
// Store previous state
var lastState = state;
// Get a new state
state = ObjectPool<State>.Get();
// Begin a debug marker scope so PIX/RenderDoc show this list
var markerName = string.IsNullOrEmpty( DebugName ) ? "CommandList" : $"CommandList: {DebugName}";
Graphics.Context.BeginPixEvent( markerName );
// Execute all commands
try
{
var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan( _entries );
for ( int i = 0; i < span.Length; i++ )
{
ref var entry = ref span[i];
entry.Execute( ref entry, this );
}
}
catch ( System.Exception e )
{
Log.Warning( e, "Error when executing CommandList" );
}
Graphics.Context.EndPixEvent();
// Reset the state and return to the pool
state.Reset();
ObjectPool<State>.Return( state );
// Restore to the previous state
state = lastState;
}
}
/// <summary>
/// Command buffer flags allow us to skip command buffers if the camera
/// doesn't want a particular thing. Like post processing.
/// </summary>
public enum Flag
{
None = 0,
PostProcess = 2,
Hud = 4,
}
/// <summary>
/// Draws a single model at the given Transform immediately.
/// </summary>
/// <param name="model">The model to draw</param>
/// <param name="transform">Transform to draw the model at</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModel( Model model, Transform transform, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var position = new Vector3( entry.Data1.x, entry.Data1.y, entry.Data1.z );
var scale = new Vector3( entry.Data1.w, entry.Data2.x, entry.Data2.y );
var rotation = new Rotation( entry.Data2.z, entry.Data2.w, entry.Data3.x, entry.Data3.y );
var t = new Transform { Position = position, Scale = scale, Rotation = rotation };
Graphics.DrawModel( (Model)entry.Object1, t, (RenderAttributes)entry.Object2 );
}
AddEntry( &Execute, new Entry
{
Object1 = model,
Object2 = attributes,
Data1 = new Vector4( transform.Position.x, transform.Position.y, transform.Position.z, transform.Scale.x ),
Data2 = new Vector4( transform.Scale.y, transform.Scale.z, transform.Rotation.x, transform.Rotation.y ),
Data3 = new Vector4( transform.Rotation.z, transform.Rotation.w, 0, 0 )
} );
}
/// <summary>
/// Draws multiple instances of a model using GPU instancing, assuming standard implemented shaders.
///
/// Use `GetTransformMatrix( int instance )` in shaders to access the instance transform.
///
/// There is a limit of 1,048,576 transform slots per frame when using this method.
/// </summary>
/// <param name="model">The model to draw</param>
/// <param name="transforms">Instance transform data to draw</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModelInstanced( Model model, Span<Transform> transforms, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawModelInstanced( (Model)entry.Object1, ((Transform[])entry.Object5).AsSpan(), (RenderAttributes)entry.Object2 );
}
// We need to copy the transforms to the heap to make sure they still exist when the action is executed.
// We also discussed using a list/array as parameter, but that could lead to issues if the list/array is modified after the call.
var transformsCopy = transforms.ToArray();
AddEntry( &Execute, new Entry { Object1 = model, Object5 = transformsCopy, Object2 = attributes } );
}
/// <summary>
/// Draws multiple instances of a model using GPU instancing with the number of instances being provided by indirect draw arguments.
/// Use `SV_InstanceID` semantic in shaders to access the rendered instance.
/// </summary>
/// <param name="model">The model to draw</param>
/// <param name="buffer">The GPU buffer containing the DrawIndirectArguments</param>
/// <param name="bufferOffset">Optional offset in the GPU buffer</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModelInstancedIndirect( Model model, GpuBuffer buffer, int bufferOffset = 0, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawModelInstancedIndirect( (Model)entry.Object1, (GpuBuffer)entry.Object2, (int)entry.Data1.x, (RenderAttributes)entry.Object3 );
}
AddEntry( &Execute, new Entry { Object1 = model, Object2 = buffer, Data1 = new Vector4( bufferOffset, 0, 0, 0 ), Object3 = attributes } );
}
/// <summary>
/// Draws multiple instances of a model using GPU instancing.
/// This is similar to <see cref="DrawModelInstancedIndirect(Model, GpuBuffer, int, RenderAttributes)"/>,
/// except the count is provided from the CPU rather than via a GPU buffer.
///
/// Use `SV_InstanceID` semantic in shaders to access the rendered instance.
/// </summary>
/// <param name="model">The model to draw</param>
/// <param name="count">The number of instances to draw</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModelInstanced( Model model, int count, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawModelInstanced( (Model)entry.Object1, (int)entry.Data1.x, (RenderAttributes)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = model, Data1 = new Vector4( count, 0, 0, 0 ), Object2 = attributes } );
}
/// <summary>
/// Draws geometry using a vertex buffer and material.
/// </summary>
/// <typeparam name="T">The vertex type used for vertex layout.</typeparam>
/// <param name="vertexBuffer">The GPU buffer containing vertex data.</param>
/// <param name="material">The material to use for rendering.</param>
/// <param name="startVertex">The starting vertex index for rendering.</param>
/// <param name="vertexCount">The number of vertices to render. If 0, uses all vertices in the buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void Draw<T>( GpuBuffer<T> vertexBuffer, Material material, int startVertex = 0, int vertexCount = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Draw( (GpuBuffer<T>)entry.Object1, (Material)entry.Object2, (int)entry.Data1.x, (int)entry.Data1.y, (RenderAttributes)entry.Object3, (Graphics.PrimitiveType)(int)entry.Data1.z );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = material, Data1 = new Vector4( startVertex, vertexCount, (int)primitiveType, 0 ), Object3 = attributes } );
}
/// <summary>
/// Draws indexed geometry using vertex and index buffers.
/// </summary>
/// <typeparam name="T">The vertex type used for vertex layout.</typeparam>
/// <param name="vertexBuffer">The GPU buffer containing vertex data.</param>
/// <param name="indexBuffer">The GPU buffer containing index data.</param>
/// <param name="material">The material to use for rendering.</param>
/// <param name="startIndex">The starting index for rendering.</param>
/// <param name="indexCount">The number of indices to render. If 0, uses all indices in the buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void DrawIndexed<T>( GpuBuffer<T> vertexBuffer, GpuBuffer indexBuffer, Material material, int startIndex = 0, int indexCount = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Draw( (GpuBuffer<T>)entry.Object1, (GpuBuffer)entry.Object2, (Material)entry.Object3, (int)entry.Data1.x, (int)entry.Data1.y, (RenderAttributes)entry.Object4, (Graphics.PrimitiveType)(int)entry.Data1.z );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = indexBuffer, Object3 = material, Data1 = new Vector4( startIndex, indexCount, (int)primitiveType, 0 ), Object4 = attributes } );
}
/// <summary>
/// Draws instanced geometry using a vertex buffer and indirect draw arguments stored in a GPU buffer.
/// </summary>
/// <typeparam name="T">The vertex type used for vertex layout.</typeparam>
/// <param name="vertexBuffer">The GPU buffer containing vertex data.</param>
/// <param name="material">The material to use for rendering.</param>
/// <param name="indirectBuffer">The GPU buffer containing indirect draw arguments.</param>
/// <param name="bufferOffset">Optional byte offset into the indirect buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void DrawInstancedIndirect<T>( GpuBuffer<T> vertexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawInstancedIndirect( (GpuBuffer<T>)entry.Object1, (Material)entry.Object2, (GpuBuffer)entry.Object3, (uint)entry.Data1.x, (RenderAttributes)entry.Object4, (Graphics.PrimitiveType)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = material, Object3 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, 0, 0 ), Object4 = attributes } );
}
/// <summary>
/// Draws instanced geometry using a vertex buffer and indirect draw arguments stored in a GPU buffer.
/// </summary>
/// <remarks>
/// Vertex data is accessed in shader through buffer attribute and SV_VertexID.
/// </remarks>
/// <param name="material">The material to use for rendering.</param>
/// <param name="indirectBuffer">The GPU buffer containing indirect draw arguments.</param>
/// <param name="bufferOffset">Optional byte offset into the indirect buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void DrawInstancedIndirect( Material material, GpuBuffer indirectBuffer, uint bufferOffset = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawInstancedIndirect( (Material)entry.Object1, (GpuBuffer)entry.Object2, (uint)entry.Data1.x, (RenderAttributes)entry.Object3, (Graphics.PrimitiveType)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = material, Object2 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, 0, 0 ), Object3 = attributes } );
}
/// <summary>
/// Draws instanced indexed geometry using indirect draw arguments stored in a GPU buffer.
/// </summary>
/// <typeparam name="T">The vertex type used for vertex layout.</typeparam>
/// <param name="vertexBuffer">The GPU buffer containing vertex data.</param>
/// <param name="indexBuffer">The GPU buffer containing index data.</param>
/// <param name="material">The material to use for rendering.</param>
/// <param name="indirectBuffer">The GPU buffer containing indirect draw arguments.</param>
/// <param name="bufferOffset">Optional byte offset into the indirect buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void DrawIndexedInstancedIndirect<T>( GpuBuffer<T> vertexBuffer, GpuBuffer indexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawIndexedInstancedIndirect( (GpuBuffer<T>)entry.Object1, (GpuBuffer)entry.Object2, (Material)entry.Object3, (GpuBuffer)entry.Object4, (uint)entry.Data1.x, (RenderAttributes)entry.Object5, (Graphics.PrimitiveType)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = indexBuffer, Object3 = material, Object4 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, 0, 0 ), Object5 = attributes } );
}
/// <summary>
/// Draws instanced indexed geometry using indirect draw arguments stored in a GPU buffer.
/// </summary>
/// <remarks>
/// Vertex data is accessed in shader through buffer attribute and SV_VertexID.
/// </remarks>
/// <param name="indexBuffer">The GPU buffer containing index data.</param>
/// <param name="material">The material to use for rendering.</param>
/// <param name="indirectBuffer">The GPU buffer containing indirect draw arguments.</param>
/// <param name="bufferOffset">Optional byte offset into the indirect buffer.</param>
/// <param name="attributes">Optional render attributes to apply only for this draw call.</param>
/// <param name="primitiveType">The type of primitives to render. Defaults to triangles.</param>
public void DrawIndexedInstancedIndirect( GpuBuffer indexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset = 0, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawIndexedInstancedIndirect( (GpuBuffer)entry.Object1, (Material)entry.Object2, (GpuBuffer)entry.Object3, (uint)entry.Data1.x, (RenderAttributes)entry.Object4, (Graphics.PrimitiveType)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = indexBuffer, Object2 = material, Object3 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, 0, 0 ), Object4 = attributes } );
}
/// <summary>
/// Get a screen sized temporary render target. You should release the returned handle when you're done to return the textures to the pool.
/// </summary>
/// <param name="name">The name of the render target handle.</param>
/// <param name="sizeFactor">Divide the screen size by this factor. 2 would be half screen sized. 1 for full screen sized.</param>
/// <param name="format">The format for the color buffer. If set to default we'll use whatever the current pipeline is using.</param>
/// <param name="numMips">Number of mips you want in this texture. You probably don't want this unless you want to generate mips in a second pass.</param>
/// <returns>A RenderTarget that is ready to render to.</returns>
public RenderTargetHandle GetRenderTarget( string name, ImageFormat format, int numMips = 1, int sizeFactor = 1 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var temp = Sandbox.RenderTarget.GetTemporary( (int)entry.Data1.y, (ImageFormat)(int)entry.Data1.x, depthFormat: ImageFormat.None, numMips: (int)entry.Data1.z );
commandList.state.renderTargets[(string)entry.Object5] = temp;
}
AddEntry( &Execute, new Entry { Object5 = name, Data1 = new Vector4( (int)format, sizeFactor, numMips, 0 ) } );
return new RenderTargetHandle { Name = name };
}
/// <summary>
/// Get a screen sized temporary render target. You should release the returned handle when you're done to return the textures to the pool.
/// </summary>
/// <param name="name">The name of the render target handle.</param>
/// <param name="sizeFactor">Divide the screen size by this factor. 2 would be half screen sized. 1 for full screen sized.</param>
/// <param name="colorFormat">The format for the color buffer. If set to default we'll use whatever the current pipeline is using.</param>
/// <param name="depthFormat">The format for the depth buffer.</param>
/// <param name="msaa">The number of msaa samples you'd like. Msaa render textures are a pain in the ass so you're probably gonna regret trying to use this.</param>
/// <param name="numMips">Number of mips you want in this texture. You probably don't want this unless you want to generate mips in a second pass.</param>
/// <returns>A RenderTarget that is ready to render to.</returns>
public RenderTargetHandle GetRenderTarget( string name, int sizeFactor = 1, ImageFormat colorFormat = ImageFormat.Default, ImageFormat depthFormat = ImageFormat.Default, MultisampleAmount msaa = MultisampleAmount.MultisampleNone, int numMips = 1 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var temp = Sandbox.RenderTarget.GetTemporary( (int)entry.Data1.x, (ImageFormat)(int)entry.Data1.y, (ImageFormat)(int)entry.Data1.z, (MultisampleAmount)(int)entry.Data1.w, (int)entry.Data2.x );
commandList.state.renderTargets[(string)entry.Object5] = temp;
}
AddEntry( &Execute, new Entry { Object5 = name, Data1 = new Vector4( sizeFactor, (int)colorFormat, (int)depthFormat, (int)msaa ), Data2 = new Vector4( numMips, 0, 0, 0 ) } );
return new RenderTargetHandle { Name = name };
}
/// <summary>
/// Get a temporary render target. You should release the returned handle when you're done to return the textures to the pool.
/// </summary>
/// <param name="name">The name of the render target handle.</param>
/// <param name="width">Width of the render target you want.</param>
/// <param name="height">Height of the render target you want.</param>
/// <param name="colorFormat">The format for the color buffer. If set to default we'll use whatever the current pipeline is using.</param>
/// <param name="depthFormat">The format for the depth buffer.</param>
/// <param name="msaa">The number of msaa samples you'd like. Msaa render textures are a pain in the ass so you're probably gonna regret trying to use this.</param>
/// <param name="numMips">Number of mips you want in this texture. You probably don't want this unless you want to generate mips in a second pass.</param>
/// <returns>A RenderTarget that is ready to render to.</returns>
public RenderTargetHandle GetRenderTarget( string name, int width, int height, ImageFormat colorFormat = ImageFormat.Default, ImageFormat depthFormat = ImageFormat.Default, MultisampleAmount msaa = MultisampleAmount.MultisampleNone, int numMips = 1 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var temp = Sandbox.RenderTarget.GetTemporary( (int)entry.Data1.x, (int)entry.Data1.y, (ImageFormat)(int)entry.Data1.z, (ImageFormat)(int)entry.Data1.w, (MultisampleAmount)(int)entry.Data2.x, (int)entry.Data2.y );
commandList.state.renderTargets[(string)entry.Object5] = temp;
}
AddEntry( &Execute, new Entry { Object5 = name, Data1 = new Vector4( width, height, (int)colorFormat, (int)depthFormat ), Data2 = new Vector4( (int)msaa, numMips, 0, 0 ) } );
return new RenderTargetHandle { Name = name };
}
/// <summary>
/// We're no longer using this RT, return it to the pool
/// </summary>
public void ReleaseRenderTarget( RenderTargetHandle handle )
{
static void Execute( ref Entry entry, CommandList commandList )
{
if ( commandList.state.renderTargets.Remove( (string)entry.Object5, out var target ) )
{
target.Dispose();
}
}
AddEntry( &Execute, new Entry { Object5 = handle.Name } );
}
/// <summary>
/// Set the current render target. Setting this will bind the render target and change the viewport to match it.
/// </summary>
public void SetRenderTarget( RenderTargetHandle handle )
{
static void Execute( ref Entry entry, CommandList commandList )
{
if ( !commandList.state.renderTargets.TryGetValue( (string)entry.Object5, out var target ) )
{
Log.Warning( $"[{commandList.DebugName ?? "CommandList"}] Unknown rt: {(string)entry.Object5}" );
return;
}
Graphics.RenderTarget = target;
}
AddEntry( &Execute, new Entry { Object5 = handle.Name } );
}
/// <summary>
/// Set the current render target. Setting this will bind the render target and change the viewport to match it.
/// </summary>
public void SetRenderTarget( RenderTarget target )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.RenderTarget = (RenderTarget)entry.Object1;
}
AddEntry( &Execute, new Entry { Object1 = target } );
}
/// <summary>
/// Set the current render target. Setting this will bind the render target and change the viewport to match it.
/// </summary>
public void ClearRenderTarget()
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.RenderTarget = null;
}
AddEntry( &Execute, default );
}
/// <summary>
/// Set the color texture from this named render target to this attribute
/// </summary>
[Obsolete]
public void Set( StringToken token, RenderTargetHandle.ColorTextureRef buffer, int mip = -1 ) => Attributes.Set( token, buffer, mip );
/// <summary>
/// Set the color texture from this named render target to this attribute
/// </summary>
[Obsolete]
public void SetGlobal( StringToken token, RenderTargetHandle.ColorIndexRef buffer ) => GlobalAttributes.Set( token, buffer );
/// <inheritdoc cref="ComputeShader.Dispatch(int, int, int)"/>
public void DispatchCompute( ComputeShader compute, int threadsX, int threadsY, int threadsZ )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((ComputeShader)entry.Object1).DispatchWithAttributes( Graphics.Attributes, (int)entry.Data1.x, (int)entry.Data1.y, (int)entry.Data1.z );
}
AddEntry( &Execute, new Entry { Object1 = compute, Data1 = new Vector4( threadsX, threadsY, threadsZ, 0 ) } );
}
/// <inheritdoc cref="ComputeShader.DispatchIndirect(GpuBuffer, uint)"/>
public void DispatchComputeIndirect( ComputeShader compute, GpuBuffer indirectBuffer, uint indirectElementOffset = 0 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((ComputeShader)entry.Object1).DispatchIndirectWithAttributes( Graphics.Attributes, (GpuBuffer)entry.Object2, (uint)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = compute, Object2 = indirectBuffer, Data1 = new Vector4( indirectElementOffset, 0, 0, 0 ) } );
}
/// <inheritdoc cref="RayTracingShader.DispatchRaysWithAttributes(RenderAttributes, int, int, int)"/>
internal void DispatchRays( RayTracingShader raytracing, int threadsX, int threadsY, int threadsZ )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((RayTracingShader)entry.Object1).DispatchRaysWithAttributes( Graphics.Attributes, (int)entry.Data1.x, (int)entry.Data1.y, (int)entry.Data1.z );
}
AddEntry( &Execute, new Entry { Object1 = raytracing, Data1 = new Vector4( threadsX, threadsY, threadsZ, 0 ) } );
}
/// <inheritdoc cref="RayTracingShader.DispatchRaysIndirect(GpuBuffer, uint)"/>
internal void DispatchRaysIndirect( RayTracingShader raytracing, GpuBuffer indirectBuffer, uint indirectElementOffset = 0 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((RayTracingShader)entry.Object1).DispatchRaysIndirectWithAttributes( Graphics.Attributes, (GpuBuffer)entry.Object2, (uint)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = raytracing, Object2 = indirectBuffer, Data1 = new Vector4( indirectElementOffset, 0, 0, 0 ) } );
}
/// <summary>
/// A handle to the viewport size
/// </summary>
public RenderTargetHandle.SizeHandle ViewportSize => new RenderTargetHandle.SizeHandle { Name = "$vp" };
/// <summary>
/// Dispatch a compute shader
/// </summary>
public void DispatchCompute( ComputeShader compute, RenderTargetHandle.SizeHandle dimension )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var xyz = commandList.GetDimension( (string)entry.Object5 );
if ( !xyz.HasValue ) return;
((ComputeShader)entry.Object1).DispatchWithAttributes( Graphics.Attributes, xyz.Value.x, xyz.Value.y, xyz.Value.z );
}
AddEntry( &Execute, new Entry { Object1 = compute, Object5 = dimension.Name } );
}
/// <summary>
/// Dispatch a ray tracing shader
/// </summary>
internal void DispatchRays( RayTracingShader raytracing, RenderTargetHandle.SizeHandle dimension )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var xyz = commandList.GetDimension( (string)entry.Object5 );
if ( !xyz.HasValue ) return;
((RayTracingShader)entry.Object1).DispatchRaysWithAttributes( Graphics.Attributes, xyz.Value.x, xyz.Value.y, xyz.Value.z );
}
AddEntry( &Execute, new Entry { Object1 = raytracing, Object5 = dimension.Name } );
}
/// <summary>
/// Called during rendering, convert RenderTargetHandle.SizeHandle to a dimension
/// </summary>
Vector3Int? GetDimension( string name )
{
if ( name == "$vp" ) return new Vector3Int( Graphics.Viewport.Width.CeilToInt(), Graphics.Viewport.Height.CeilToInt(), 1 );
var rt = state.renderTargets.GetValueOrDefault( name );
if ( rt is null ) return default;
return new Vector3Int( rt.Width, rt.Height, 1 );
}
/// <summary>
/// Clear the current drawing context to given color.
/// </summary>
/// <param name="color">Color to clear to.</param>
/// <param name="clearColor">Whether to clear the color buffer at all.</param>
/// <param name="clearDepth">Whether to clear the depth buffer.</param>
/// <param name="clearStencil">Whether to clear the stencil buffer.</param>
public void Clear( Color color, bool clearColor = true, bool clearDepth = true, bool clearStencil = true )
{
static void Execute( ref Entry entry, CommandList commandList )
{
var color = new Color( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w );
var clearColor = ((int)entry.Data2.x != 0);
var clearDepth = ((int)entry.Data2.y != 0);
var clearStencil = ((int)entry.Data2.z != 0);
Graphics.Clear( color, clearColor, clearDepth, clearStencil );
}
AddEntry( &Execute, new Entry { Data1 = new Vector4( color.r, color.g, color.b, color.a ), Data2 = new Vector4( clearColor ? 1 : 0, clearDepth ? 1 : 0, clearStencil ? 1 : 0, 0 ) } );
}
/// <summary>
/// Executes a barrier transition for the given GPU Texture Resource.
/// Transitions the texture resource to a new pipeline stage and access state.
/// </summary>
/// <param name="texture">The texture to transition.</param>
/// <param name="state">The new resource state for the texture.</param>
/// <param name="mip">The mip level to transition (-1 for all mips).</param>
public void ResourceBarrierTransition( Texture texture, ResourceState state, int mip = -1 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.ResourceBarrierTransition( (Texture)entry.Object1, (ResourceState)(int)entry.Data1.x, (int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = texture, Data1 = new Vector4( (int)state, mip, 0, 0 ) } );
}
/// <summary>
/// Executes a barrier transition for the color texture of the given render target handle.
/// </summary>
/// <param name="texture">The render target color handle.</param>
/// <param name="state">The new resource state for the texture.</param>
/// <param name="mip">The mip level to transition (-1 for all mips).</param>
public void ResourceBarrierTransition( RenderTargetHandle.ColorTextureRef texture, ResourceState state, int mip = -1 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
if ( commandList.state.GetRenderTarget( (string)entry.Object5 ) is not { } target )
{
Log.Warning( $"[{commandList.DebugName ?? "CommandList"}] Unknown rt: {(string)entry.Object5}" );
return;
}
Graphics.ResourceBarrierTransition( target.ColorTarget, (ResourceState)(int)entry.Data1.x, (int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object5 = texture.Name, Data1 = new Vector4( (int)state, mip, 0, 0 ) } );
}
/// <summary>
/// Executes a barrier transition for the color texture of the given render target handle.
/// </summary>
/// <param name="handle">The render target handle.</param>
/// <param name="state">The new resource state for the texture.</param>
/// <param name="mip">The mip level to transition (-1 for all mips).</param>
public void ResourceBarrierTransition( RenderTargetHandle handle, ResourceState state, int mip = -1 )
{
ResourceBarrierTransition( handle.ColorTexture, state, mip );
}
/// <summary>
/// Executes a barrier transition for the given GPU Buffer Resource.
/// Transitions the buffer resource to a new pipeline stage and access state.
/// </summary>
/// <param name="buffer">The GPU buffer to transition.</param>
/// <param name="state">The new resource state for the buffer.</param>
public void ResourceBarrierTransition( GpuBuffer buffer, ResourceState state )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.ResourceBarrierTransition( (GpuBuffer)entry.Object1, (ResourceState)(int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Data1 = new Vector4( (int)state, 0, 0, 0 ) } );
}
/// <summary>
/// Executes a barrier transition for the given GPU Buffer Resource.
/// Transitions the buffer resource from a known source state to a specified destination state.
/// </summary>
/// <param name="buffer">The GPU buffer to transition.</param>
/// <param name="before">The current resource state of the buffer.</param>
/// <param name="after">The desired resource state of the buffer after the transition.</param>
public void ResourceBarrierTransition( GpuBuffer buffer, ResourceState before, ResourceState after )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.ResourceBarrierTransition( (GpuBuffer)entry.Object1, (ResourceState)(int)entry.Data1.x, (ResourceState)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Data1 = new Vector4( (int)before, (int)after, 0, 0 ) } );
}
/// <summary>
/// Sneaky way for extensions to add an action. This creates an allocation, so it should be used sparingly.
/// </summary>
private void AddAction( Action a )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((Action)entry.Object1)?.Invoke();
}
AddEntry( &Execute, new Entry { Object1 = a } );
}
/// <summary>
/// Sneaky way for externals to get render target
/// </summary>
internal RenderTarget GetRenderTarget( string name )
{
if ( state.renderTargets != null && state.renderTargets.TryGetValue( name, out var target ) )
return target;
return default;
}
/// <summary>
/// Generates a mip-map chain for the specified render target.
/// This will generate mipmaps for the color texture of the render target.
/// </summary>
public void GenerateMipMaps( RenderTargetHandle handle, Graphics.DownsampleMethod method = Graphics.DownsampleMethod.GaussianBlur )
{
static void Execute( ref Entry entry, CommandList commandList )
{
if ( !commandList.state.renderTargets.TryGetValue( (string)entry.Object5, out var target ) )
{
Log.Warning( $"[{commandList.DebugName ?? "CommandList"}] Unknown rt: {(string)entry.Object5}" );
return;
}
Graphics.GenerateMipMaps( target.ColorTarget, (Graphics.DownsampleMethod)(int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object5 = handle.Name, Data1 = new Vector4( (int)method, 0, 0, 0 ) } );
}
/// <summary>
/// Generates a mip-map chain for the specified render target.
/// This will generate mipmaps for the color texture of the render target.
/// </summary>
public void GenerateMipMaps( RenderTarget target, Graphics.DownsampleMethod method = Graphics.DownsampleMethod.GaussianBlur )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.GenerateMipMaps( ((RenderTarget)entry.Object1).ColorTarget, (Graphics.DownsampleMethod)(int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = target, Data1 = new Vector4( (int)method, 0, 0, 0 ) } );
}
/// <summary>
/// Generates a mip-map chain for the specified texture.
/// This will generate mipmaps for the color texture of the texture.
/// </summary>
public void GenerateMipMaps( Texture texture, Graphics.DownsampleMethod method = Graphics.DownsampleMethod.GaussianBlur )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.GenerateMipMaps( (Texture)entry.Object1, (Graphics.DownsampleMethod)(int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = texture, Data1 = new Vector4( (int)method, 0, 0, 0 ) } );
}
}