Files
sbox-public/engine/Sandbox.Engine/Systems/Render/CommandList/CommandList.cs

1558 lines
67 KiB
C#

using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Sandbox.Rendering;
public sealed unsafe partial class CommandList
{
readonly Lock _lock = new Lock();
private string _debugName;
private string _markerName = "CommandList";
public string DebugName
{
get => _debugName;
set
{
_debugName = value;
_markerName = string.IsNullOrEmpty( value ) ? "CommandList" : string.Concat( "CommandList: ", value );
}
}
public bool Enabled { get; set; }
public Flag Flags { get; set; }
public CommandList()
{
Enabled = true;
Attributes = new AttributeAccess( this, GetLocalAttributes );
}
public CommandList( string debugName ) : this()
{
DebugName = debugName;
}
/// <summary>
/// Holds the function and state data for a single command.
/// Target: fit within 2 cache lines (128 bytes).
/// </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 StringToken Token;
public Vector4 Data1;
public Vector4 Data2;
public Vector4 Data3;
public Vector4 Data4;
}
/// <summary>
/// An ordered list of entries that will execute on the render thread. All access (record and
/// execute) is serialized by <see cref="_lock"/> - recording and executing the same list from
/// two threads at once is caller misuse, but the lock guarantees it can't crash or corrupt state.
/// </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;
lock ( _lock )
_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()
{
// Serialize against execution: clearing the entry list or the render-target cache while
// another thread is mid-execute would corrupt them. The lock makes that safe.
lock ( _lock )
{
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( entry.Token, entry.Data1.x );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, new Vector2( entry.Data1.x, entry.Data1.y ) );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, new Vector3( entry.Data1.x, entry.Data1.y, entry.Data1.z ) );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, entry.Data1 );
}
AddEntry( &Execute, new Entry { Token = token, Data1 = vector4 } );
}
[Obsolete]
public void Set( StringToken token, int i )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( entry.Token, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, Unsafe.As<Vector4, Matrix>( ref entry.Data1 ) );
}
var e = new Entry { Token = token };
Unsafe.As<Vector4, Matrix>( ref e.Data1 ) = matrix;
AddEntry( &Execute, e );
}
[Obsolete]
public void Set( StringToken token, GpuBuffer buffer )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( entry.Token, (GpuBuffer)entry.Object2 );
}
AddEntry( &Execute, new Entry { Token = token, Object2 = buffer } );
}
[Obsolete]
public void Set( StringToken token, Texture texture )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.Set( entry.Token, (Texture)entry.Object2 );
}
AddEntry( &Execute, new Entry { Token = token, Object2 = texture } );
}
[Obsolete]
public void SetCombo( StringToken token, int value )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetCombo( entry.Token, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Token = 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.SetCombo( entry.Token, (int)entry.Data1.x );
}
var intValue = Unsafe.SizeOf<T>() switch
{
1 => Unsafe.As<T, byte>( ref t ),
2 => (int)Unsafe.As<T, short>( ref t ),
8 => (int)Unsafe.As<T, long>( ref t ),
_ => Unsafe.As<T, int>( ref t )
};
AddEntry( &Execute, new Entry { Token = token, Data1 = new Vector4( intValue, 0, 0, 0 ) } );
}
[Obsolete]
public void SetConstantBuffer<T>( StringToken token, T data ) where T : unmanaged
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.Attributes.SetData( entry.Token, (T)entry.Object2 );
}
AddEntry( &Execute, new Entry { Token = token, Object2 = data } );
}
[Obsolete]
public void SetGlobal( StringToken token, GpuBuffer buffer )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( entry.Token, (GpuBuffer)entry.Object2 );
}
AddEntry( &Execute, new Entry { Token = token, Object2 = buffer } );
}
[Obsolete]
public void SetGlobal( StringToken token, int i )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( entry.Token, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, (int)entry.Data1.x != 0 );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, entry.Data1.x );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, new Vector2( entry.Data1.x, entry.Data1.y ) );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, new Vector3( entry.Data1.x, entry.Data1.y, entry.Data1.z ) );
}
AddEntry( &Execute, new Entry { Token = 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( entry.Token, entry.Data1 );
}
AddEntry( &Execute, new Entry { Token = token, Data1 = vector4 } );
}
[Obsolete]
public void SetGlobal( StringToken token, Matrix matrix )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( entry.Token, Unsafe.As<Vector4, Matrix>( ref entry.Data1 ) );
}
var e = new Entry { Token = token };
Unsafe.As<Vector4, Matrix>( ref e.Data1 ) = matrix;
AddEntry( &Execute, e );
}
[Obsolete]
public void SetGlobal( StringToken token, Texture texture )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.FrameAttributes.Set( entry.Token, (Texture)entry.Object2 );
}
AddEntry( &Execute, new Entry { Token = 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 )
{
var other = (CommandList)entry.Object1;
if ( !other.Enabled )
return;
// Propagate state from parent so child entries can access renderTargets etc.
var previousState = other.state;
other.state = commandList.state;
// Lock the inserted list while we iterate it, so it can't be recorded/reset out from
// under us on another thread.
lock ( other._lock )
{
for ( int i = 0; i < other._entries.Count; i++ )
{
var e = other._entries[i];
e.Execute( ref e, other );
}
}
other.state = previousState;
}
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
Graphics.Context.BeginPixEvent( _markerName );
// GPU profiler timing scope, closed after execution below. The profiler nests this under its
// containing layer by GPU-timestamp containment in the summary - no parent passed here.
var perfScope = NativeEngine.CSceneSystem.BeginManagedPerfMarker( Graphics.Context, _debugName ?? "CommandList" );
// 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();
NativeEngine.CSceneSystem.EndManagedPerfMarker( Graphics.Context, perfScope );
// 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 at a specific LOD level.
///
/// 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="lodLevel">LOD level to render (0 = highest detail)</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModelInstanced( Model model, Span<Transform> transforms, int lodLevel, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawModelInstanced( (Model)entry.Object1, ((Transform[])entry.Object5).AsSpan(), (int)entry.Data1.x, (RenderAttributes)entry.Object2 );
}
var transformsCopy = transforms.ToArray();
AddEntry( &Execute, new Entry { Object1 = model, Object5 = transformsCopy, Data1 = new Vector4( lodLevel, 0, 0, 0 ), Object2 = attributes } );
}
/// <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 instances of a model using GPU instancing, with per-instance transforms read from
/// <paramref name="transformBuffer"/> and the instance count provided by indirect draw arguments.
/// Feeds the standard `GetTransformMatrix()` path, so normal/custom material shaders render unchanged.
/// </summary>
/// <param name="model">The model to draw</param>
/// <param name="transformBuffer">Per-instance transforms, indexed 0..count-1</param>
/// <param name="indirectArgs">Buffer containing the DrawIndexedInstancedArguments</param>
/// <param name="argsOffset">Optional byte offset into the indirect args buffer</param>
/// <param name="lodLevel">LOD level to render (0 = highest detail)</param>
/// <param name="attributes">Optional attributes to apply only for this draw call</param>
public void DrawModelInstancedIndirect( Model model, GpuBuffer transformBuffer, GpuBuffer indirectArgs, int argsOffset = 0, int lodLevel = 0, RenderAttributes attributes = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawModelInstancedIndirect( (Model)entry.Object1, (GpuBuffer)entry.Object2, (GpuBuffer)entry.Object3, (int)entry.Data1.x, (int)entry.Data1.y, (RenderAttributes)entry.Object4 );
}
AddEntry( &Execute, new Entry { Object1 = model, Object2 = transformBuffer, Object3 = indirectArgs, Data1 = new Vector4( argsOffset, lodLevel, 0, 0 ), Object4 = 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 element 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
=> DrawInstancedIndirect( vertexBuffer, material, indirectBuffer, bufferOffset, attributes, primitiveType, 1 );
/// <summary>
/// Draws instanced geometry using a vertex buffer, executing one or more 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">Element offset into the indirect buffer.</param>
/// <param name="attributes">Render attributes to apply only for this draw call, or null.</param>
/// <param name="primitiveType">The type of primitives to render.</param>
/// <param name="drawCount">Number of draw argument structs to read and execute.</param>
/// <param name="stride">Byte stride between draw argument structs. Use this when your indirect buffer packs extra per-draw userdata. Must be a multiple of 4 and at least the size of the draw argument struct. 0 uses the natural size.</param>
public void DrawInstancedIndirect<T>( GpuBuffer<T> vertexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset, RenderAttributes attributes, Graphics.PrimitiveType primitiveType, uint drawCount, uint stride = 0 ) 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, (uint)entry.Data1.z, (uint)entry.Data1.w );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = material, Object3 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, drawCount, stride ), Object4 = attributes } );
}
/// <summary>
/// Draws instanced 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="material">The material to use for rendering.</param>
/// <param name="indirectBuffer">The GPU buffer containing indirect draw arguments.</param>
/// <param name="bufferOffset">Optional element 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 )
=> DrawInstancedIndirect( material, indirectBuffer, bufferOffset, attributes, primitiveType, 1 );
/// <summary>
/// Draws instanced geometry, executing one or more 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">Element offset into the indirect buffer.</param>
/// <param name="attributes">Render attributes to apply only for this draw call, or null.</param>
/// <param name="primitiveType">The type of primitives to render.</param>
/// <param name="drawCount">Number of draw argument structs to read and execute.</param>
/// <param name="stride">Byte stride between draw argument structs. Use this when your indirect buffer packs extra per-draw userdata. Must be a multiple of 4 and at least the size of the draw argument struct. 0 uses the natural size.</param>
public void DrawInstancedIndirect( Material material, GpuBuffer indirectBuffer, uint bufferOffset, RenderAttributes attributes, Graphics.PrimitiveType primitiveType, uint drawCount, uint stride = 0 )
{
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, (uint)entry.Data1.z, (uint)entry.Data1.w );
}
AddEntry( &Execute, new Entry { Object1 = material, Object2 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, drawCount, stride ), 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 element 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
=> DrawIndexedInstancedIndirect( vertexBuffer, indexBuffer, material, indirectBuffer, bufferOffset, attributes, primitiveType, 1 );
/// <summary>
/// Draws instanced indexed geometry, executing one or more 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">Element offset into the indirect buffer.</param>
/// <param name="attributes">Render attributes to apply only for this draw call, or null.</param>
/// <param name="primitiveType">The type of primitives to render.</param>
/// <param name="drawCount">Number of draw argument structs to read and execute.</param>
/// <param name="stride">Byte stride between draw argument structs. Use this when your indirect buffer packs extra per-draw userdata. Must be a multiple of 4 and at least the size of the draw argument struct. 0 uses the natural size.</param>
public void DrawIndexedInstancedIndirect<T>( GpuBuffer<T> vertexBuffer, GpuBuffer indexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset, RenderAttributes attributes, Graphics.PrimitiveType primitiveType, uint drawCount, uint stride = 0 ) 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, (uint)entry.Data1.z, (uint)entry.Data1.w );
}
AddEntry( &Execute, new Entry { Object1 = vertexBuffer, Object2 = indexBuffer, Object3 = material, Object4 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, drawCount, stride ), 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 element 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 )
=> DrawIndexedInstancedIndirect( indexBuffer, material, indirectBuffer, bufferOffset, attributes, primitiveType, 1 );
/// <summary>
/// Draws instanced indexed geometry, executing one or more 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">Element offset into the indirect buffer.</param>
/// <param name="attributes">Render attributes to apply only for this draw call, or null.</param>
/// <param name="primitiveType">The type of primitives to render.</param>
/// <param name="drawCount">Number of draw argument structs to read and execute.</param>
/// <param name="stride">Byte stride between draw argument structs. Use this when your indirect buffer packs extra per-draw userdata. Must be a multiple of 4 and at least the size of the draw argument struct. 0 uses the natural size.</param>
public void DrawIndexedInstancedIndirect( GpuBuffer indexBuffer, Material material, GpuBuffer indirectBuffer, uint bufferOffset, RenderAttributes attributes, Graphics.PrimitiveType primitiveType, uint drawCount, uint stride = 0 )
{
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, (uint)entry.Data1.z, (uint)entry.Data1.w );
}
AddEntry( &Execute, new Entry { Object1 = indexBuffer, Object2 = material, Object3 = indirectBuffer, Data1 = new Vector4( bufferOffset, (int)primitiveType, drawCount, stride ), Object4 = attributes } );
}
/// <summary>
/// Draws indexed geometry with instancing. Each instance shares the same index buffer.
/// </summary>
public void DrawIndexedInstanced( GpuBuffer indexBuffer, Material material, int instanceCount, RenderAttributes attributes = null, Graphics.PrimitiveType primitiveType = Graphics.PrimitiveType.Triangles )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.DrawIndexedInstanced( (GpuBuffer)entry.Object1, (Material)entry.Object2, (int)entry.Data1.x, (RenderAttributes)entry.Object3, (Graphics.PrimitiveType)(int)entry.Data1.y );
}
AddEntry( &Execute, new Entry { Object1 = indexBuffer, Object2 = material, Data1 = new Vector4( instanceCount, (int)primitiveType, 0, 0 ), Object3 = 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 )
{
// Pass the name as the pool's targetName so this handle maps to a stable physical texture
// across frames. Without it, any RT of matching dimensions/format shares one pool bucket and
// the name->texture mapping can shuffle frame-to-frame (breaking temporal/history buffers).
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, targetName: (string)entry.Object5 );
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, targetName: (string)entry.Object5 );
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 );
/// <summary>
/// Binds the given render target's color texture to a stable, pipeline-level bindless slot
/// for this frame. This is how full-screen pipeline resources (ambient occlusion, screen-space
/// reflections) are published to the rest of the pipeline: consumers read a fixed descriptor
/// binding rather than a per-view render attribute. Because procedural layers build their
/// command lists on threaded jobs, writing the result index into the shared frame attributes
/// would race - this fixed slot is resolved single-threaded at submit, so it doesn't.
/// </summary>
internal void SetPipelineTexture( PipelineTextureSlot slot, RenderTargetHandle.ColorTextureRef buffer )
{
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;
}
NativeEngine.CSceneSystem.SetPipelineTextureIndex( (int)entry.Data1.x, target.ColorTarget.Index );
}
// Dont write out of bounds of the pipeline slots
if ( slot < 0 || slot >= PipelineTextureSlot.Count )
{
Log.Warning( $"[{DebugName ?? "CommandList"}] Invalid pipeline texture slot: {(int)slot}" );
return;
}
AddEntry( &Execute, new Entry { Object5 = buffer.Name, Data1 = new Vector4( (int)slot, 0, 0, 0 ) } );
}
/// <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>
/// A handle to the viewport size divided by a factor. Useful for dispatching at half or quarter resolution.
/// </summary>
public RenderTargetHandle.SizeHandle ViewportSizeScaled( int divisor ) => new RenderTargetHandle.SizeHandle { Name = "$vp", Divisor = Math.Max( 1, divisor ) };
/// <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, (int)entry.Data1.x );
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, Data1 = new Vector4( dimension.Divisor, 0, 0, 0 ) } );
}
/// <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, (int)entry.Data1.x );
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, Data1 = new Vector4( dimension.Divisor, 0, 0, 0 ) } );
}
/// <summary>
/// Called during rendering, convert RenderTargetHandle.SizeHandle to a dimension
/// </summary>
Vector3Int? GetDimension( string name, int divisor = 0 )
{
Vector3Int result;
if ( name == "$vp" )
{
result = new Vector3Int( Graphics.Viewport.Width.CeilToInt(), Graphics.Viewport.Height.CeilToInt(), 1 );
}
else
{
var rt = state.renderTargets.GetValueOrDefault( name );
if ( rt is null ) return default;
result = new Vector3Int( rt.Width, rt.Height, 1 );
}
if ( divisor > 1 )
{
result.x = Math.Max( 1, result.x / divisor );
result.y = Math.Max( 1, result.y / divisor );
}
return result;
}
/// <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>
/// Clears the given texture to a solid color.
/// </summary>
/// <param name="texture">The texture to clear.</param>
/// <param name="color">The color to clear to. Defaults to transparent black.</param>
public void Clear( Texture texture, Color color = default )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((Texture)entry.Object1).Clear( new Color( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w ) );
}
AddEntry( &Execute, new Entry { Object1 = texture, Data1 = new Vector4( color.r, color.g, color.b, color.a ) } );
}
/// <summary>
/// Clears the color texture of the given render target handle to a solid color.
/// </summary>
/// <param name="handle">The render target handle whose color texture to clear.</param>
/// <param name="color">The color to clear to. Defaults to transparent black.</param>
public void Clear( RenderTargetHandle handle, Color color = default )
{
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;
}
target.ColorTarget.Clear( new Color( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w ) );
}
AddEntry( &Execute, new Entry { Object5 = handle.ColorTexture.Name, Data1 = new Vector4( color.r, color.g, color.b, color.a ) } );
}
/// <summary>
/// Fills the given GPU buffer with a repeated uint32 value.
/// </summary>
/// <param name="buffer">The buffer to clear.</param>
/// <param name="value">The uint32 value to fill with. Defaults to zero.</param>
public void Clear( GpuBuffer buffer, uint value = 0 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((GpuBuffer)entry.Object1).Clear( (uint)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Data1 = new Vector4( value, 0, 0, 0 ) } );
}
/// <summary>
/// Resets the hidden append/structured-buffer counter of <paramref name="buffer"/> to <paramref name="value"/>.
/// </summary>
/// <param name="buffer">An <see cref="GpuBuffer.UsageFlags.Append"/> or structured buffer.</param>
/// <param name="value">The counter value to set. Defaults to zero.</param>
public void SetCounterValue( GpuBuffer buffer, uint value = 0 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((GpuBuffer)entry.Object1).SetCounterValue( (uint)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Data1 = new Vector4( value, 0, 0, 0 ) } );
}
/// <summary>
/// Copies the hidden append-buffer counter of <paramref name="buffer"/> into <paramref name="destBuffer"/>.
/// Useful for feeding a survivor count into an indirect draw/dispatch argument buffer.
/// </summary>
/// <param name="buffer">The <see cref="GpuBuffer.UsageFlags.Append"/> buffer to read the counter from.</param>
/// <param name="destBuffer">The buffer to write the counter into.</param>
/// <param name="destBufferOffset">Byte offset into <paramref name="destBuffer"/> to write at.</param>
public void CopyStructureCount( GpuBuffer buffer, GpuBuffer destBuffer, int destBufferOffset = 0 )
{
static void Execute( ref Entry entry, CommandList commandList )
{
((GpuBuffer)entry.Object1).CopyStructureCount( (GpuBuffer)entry.Object2, (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Object2 = destBuffer, Data1 = new Vector4( destBufferOffset, 0, 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 depth texture of the given render target handle.
/// </summary>
/// <param name="texture">The render target depth 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.DepthTextureRef 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.DepthTarget, (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>
/// Issues a UAV barrier for the given texture, ensuring writes from prior shader invocations
/// are visible to subsequent ones without changing the resource layout.
/// </summary>
/// <param name="texture">The texture to barrier.</param>
public void UavBarrier( Texture texture )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.UavBarrier( (Texture)entry.Object1 );
}
AddEntry( &Execute, new Entry { Object1 = texture } );
}
/// <summary>
/// Issues a UAV barrier for the color texture of the given render target handle, ensuring writes
/// from prior shader invocations are visible to subsequent ones without changing the resource layout.
/// Use this for a UAV (RWTexture) that is written in one pass and read back as a UAV in a later pass,
/// where the layout doesn't change and a plain transition wouldn't emit a barrier.
/// </summary>
/// <param name="texture">The render target color handle.</param>
public void UavBarrier( RenderTargetHandle.ColorTextureRef texture )
{
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.UavBarrier( target.ColorTarget );
}
AddEntry( &Execute, new Entry { Object5 = texture.Name } );
}
/// <summary>
/// Issues a UAV barrier for the color texture of the given render target handle.
/// </summary>
/// <param name="handle">The render target handle.</param>
public void UavBarrier( RenderTargetHandle handle ) => UavBarrier( handle.ColorTexture );
/// <summary>
/// Issues a UAV barrier for the given GPU buffer, ensuring writes from prior shader invocations
/// are visible to subsequent ones.
/// </summary>
/// <param name="buffer">The buffer to barrier.</param>
public void UavBarrier( GpuBuffer buffer )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.UavBarrier( (GpuBuffer)entry.Object1 );
}
AddEntry( &Execute, new Entry { Object1 = buffer } );
}
/// <summary>
/// Sets up per-object lighting for the given scene object. Writes lighting state
/// into <see cref="Graphics.Attributes"/> at execution time.
/// </summary>
internal void SetupLighting( SceneObject obj, RenderAttributes attr = null )
{
static void Execute( ref Entry entry, CommandList commandList )
{
Graphics.SetupLighting( (SceneObject)entry.Object1, (RenderAttributes)entry.Object2 );
}
AddEntry( &Execute, new Entry { Object1 = obj, Object2 = attr } );
}
/// <summary>
/// Uploads data from an array to a GPU buffer at execution time.
/// Zero-copy: the array reference is stored directly without copying.
/// The caller must ensure the array contents remain stable until the command list
/// has finished executing (i.e. until the next <see cref="Reset"/> call).
/// </summary>
internal void SetBufferData<T>( GpuBuffer<T> buffer, T[] data, int sourceOffset = 0, int count = -1, int elementOffset = 0 ) where T : unmanaged
{
if ( count < 0 ) count = data.Length - sourceOffset;
static void Execute( ref Entry entry, CommandList commandList )
{
var buf = (GpuBuffer<T>)entry.Object1;
var arr = (T[])entry.Object2;
int srcOffset = (int)entry.Data1.z;
int length = (int)entry.Data1.y;
buf.SetData( arr.AsSpan( srcOffset, length ), (int)entry.Data1.x );
}
AddEntry( &Execute, new Entry { Object1 = buffer, Object2 = data, Data1 = new Vector4( elementOffset, count, sourceOffset, 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 ) } );
}
/// <summary>
/// Draws text within a rectangle using a prepared <see cref="TextRendering.Scope"/>.
/// </summary>
/// <param name="scope">The text rendering scope.</param>
/// <param name="rect">The rectangle to draw the text in.</param>
/// <param name="flags">Text alignment flags (optional).</param>
/// <param name="angleDegrees">Rotation angle in degrees (optional).</param>
public void DrawText( TextRendering.Scope scope, Rect rect, TextFlag flags = TextFlag.LeftTop, float angleDegrees = 0f )
{
// Resolve the TextBlock at entry-add time so we store a class reference instead of
// boxing the Scope struct and TextFlag enum into object fields.
var tb = TextRendering.GetOrCreateTextBlock( scope, flags, 8096 );
if ( tb is null ) return;
static void Execute( ref Entry entry, CommandList commandList )
{
var position = new Rect( entry.Data1.x, entry.Data1.y, entry.Data1.z, entry.Data1.w );
var flags = (TextFlag)(int)entry.Data2.x;
var angle = entry.Data2.y;
var tb = (TextRendering.TextBlock)entry.Object1;
// MakeReady resets TimeSinceUsed, preventing Tick() from evicting this block
tb.MakeReady();
Graphics.Attributes.Set( "Texture", tb.Texture );
Graphics.Attributes.Set( "SamplerIndex", SamplerState.GetBindlessIndex( new SamplerState() { Filter = tb.FilterMode } ) );
var rect = position.Align( tb.Texture.Size, flags );
if ( angle == 0f )
Graphics.DrawQuad( rect.Floor(), Material.UI.Text, Color.White );
else
Graphics.DrawQuad( rect.Floor(), angle, Material.UI.Text, Color.White );
}
AddEntry( &Execute, new Entry
{
Object1 = tb,
Data1 = new Vector4( rect.Left, rect.Top, rect.Width, rect.Height ),
Data2 = new Vector4( (float)(int)flags, angleDegrees, 0, 0 )
} );
}
}