Files
sbox-public/engine/Sandbox.Engine/Systems/Render/Graphics.Draw.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

320 lines
12 KiB
C#

using NativeEngine;
using Sandbox.Rendering;
using System.Runtime.InteropServices;
namespace Sandbox;
public static partial class Graphics
{
const int maxDynamicVertexBuffer = 512 * 1024;
/// <summary>
/// This is our entry point into the engine for all draws
/// </summary>
internal static unsafe void DrawInternal<T>( T* vertices, int vertCount, ushort* indices, int indexCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles ) where T : unmanaged
{
ArgumentNullException.ThrowIfNull( material, nameof( material ) );
attributes ??= Attributes;
AssertRenderBlock();
if ( !SceneLayer.IsValid ) return;
// Get the layout for this vertex. This will create if not found.
var vertexType = VertexLayout.Get<T>();
if ( !vertexType.IsValid ) return;
// Set the material etc
if ( !RenderTools.SetRenderState( Context, attributes.Get(), material.native.GetMode( SceneLayer ), vertexType, Graphics.Stats ) )
return;
var totalSize = sizeof( T ) * vertCount;
if ( indices is not null || totalSize < maxDynamicVertexBuffer )
{
RenderTools.Draw( Context, (NativeEngine.RenderPrimitiveType)primitiveType, vertexType, (IntPtr)vertices, vertCount, (IntPtr)indices, indexCount, Graphics.Stats );
return;
}
int verticesPerChunk = maxDynamicVertexBuffer / sizeof( T );
// if we're drawing triangles, and have no indices, round to chunks of 3 so we don't
// get geometry split between chunks.. because that would be a disaster
if ( primitiveType == PrimitiveType.Triangles && indices is null )
{
verticesPerChunk = verticesPerChunk.SnapToGrid( 3 );
}
//
// todo - for lines chunk size needs to be multiple of 2, for triangles multiple of 3
//
for ( int i = 0; i < vertCount; i += verticesPerChunk )
{
var chunkCount = Math.Min( verticesPerChunk, vertCount - i );
RenderTools.Draw( Context, (NativeEngine.RenderPrimitiveType)primitiveType, vertexType, (IntPtr)(vertices + i), chunkCount, default, default, Graphics.Stats );
}
}
/// <summary>
/// Draw a bunch of vertices
/// </summary>
public static unsafe void Draw( Span<Vertex> vertices, int vertCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles )
{
fixed ( Vertex* ptr = vertices )
{
DrawInternal( ptr, vertCount, default, default, material, attributes, primitiveType );
}
}
/// <summary>
/// Draw a bunch of vertices
/// TODO: make this public
/// TODO: Is this safe to be public
/// TODO: Is VertexLayoutManager.Get Safe
/// </summary>
internal static unsafe void Draw<T>( Span<T> vertices, int vertCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles ) where T : unmanaged
{
fixed ( T* ptr = vertices )
{
DrawInternal( ptr, vertCount, default, default, material, attributes, primitiveType );
}
}
/// <summary>
/// Draw a bunch of vertices
/// </summary>
public static unsafe void Draw( List<Vertex> vertices, int vertCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles )
{
Draw( CollectionsMarshal.AsSpan( vertices ), vertCount, material, attributes, primitiveType );
}
/// <summary>
/// Draw a bunch of vertices
/// TODO: make this public
/// TODO: Is this safe to be public
/// TODO: Is VertexLayoutManager.Get Safe
/// </summary>
internal static unsafe void Draw<T>( List<T> vertices, int vertCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles ) where T : unmanaged
{
Draw<T>( CollectionsMarshal.AsSpan( vertices ), vertCount, material, attributes, primitiveType );
}
/// <summary>
/// Draw a bunch of vertices
/// </summary>
public static unsafe void Draw( Span<Vertex> vertices, int vertCount, Span<ushort> indices, int indexCount, Material material, RenderAttributes attributes = null, PrimitiveType primitiveType = PrimitiveType.Triangles )
{
fixed ( Vertex* vptr = vertices )
fixed ( ushort* iptr = indices )
{
DrawInternal( vptr, vertCount, iptr, indexCount, material, attributes, primitiveType );
}
}
static Vertex[] screenQuad;
/// <summary>
/// Draw a screen space quad using the passed material. Your material should be using a
/// screenspace shader so it will actually render to the screen and not in worldspace at 0,0,0
/// </summary>
public static void Blit( Material material, RenderAttributes attributes = null )
{
if ( screenQuad == null )
{
screenQuad = new Vertex[4];
screenQuad[0].Position = new Vector3( -1, -1, 0.5f );
screenQuad[0].Normal = new Vector3( 0, 0, -1 );
screenQuad[0].TexCoord0 = new Vector4( 0, 1, 0, 0 );
screenQuad[1].Position = new Vector3( 1, -1, 0.5f );
screenQuad[1].Normal = new Vector3( 0, 0, -1 );
screenQuad[1].TexCoord0 = new Vector4( 1, 1, 1, 0 );
screenQuad[2].Position = new Vector3( -1, 1, 0.5f );
screenQuad[2].Normal = new Vector3( 0, 1, -1 );
screenQuad[2].TexCoord0 = new Vector4( 0, 0, 0, 1 );
screenQuad[3].Position = new Vector3( 1, 1, 0.5f );
screenQuad[3].Normal = new Vector3( 0, 0, -1 );
screenQuad[3].TexCoord0 = new Vector4( 1, 0, 1, 1 );
}
Draw( screenQuad.AsSpan(), 4, material, attributes, PrimitiveType.TriangleStrip );
}
/// <summary>
/// Render a SceneObject
/// </summary>
public static void Render( SceneObject obj, Transform? transform = null, Color? color = null, Material material = null )
{
AssertRenderBlock();
if ( !SceneLayer.IsValid ) return;
if ( !obj.IsValid() ) return;
var tx = transform ?? obj.Transform;
var cl = color ?? Color.White;
var mat = material?.native ?? default;
var attributes = Attributes;
RenderTools.DrawSceneObject( Context, SceneLayer, obj, tx, cl, mat, attributes.Get() );
}
/// <summary>
/// Draw a quad in screenspace
/// </summary>
public static unsafe void DrawQuad( in Rect rect, in Material material, in Color color, RenderAttributes attributes = null )
{
attributes ??= Attributes;
var color32 = color.ToColor32();
var vertices = stackalloc Vertex[4]
{
new Vertex( new Vector2( rect.Left, rect.Top ), new Vector2( 0, 0 ), color32 ),
new Vertex( new Vector2( rect.Right, rect.Top ), new Vector2( 1, 0 ), color32 ),
new Vertex( new Vector2( rect.Left, rect.Bottom ), new Vector2( 0, 1 ), color32 ),
new Vertex( new Vector2( rect.Right, rect.Bottom ), new Vector2( 1, 1 ), color32 )
};
DrawInternal( vertices, 4, default, default, material, attributes, PrimitiveType.TriangleStrip );
}
/// <summary>
/// Draw a rotated quad in screenspace
/// </summary>
internal static unsafe void DrawQuad( in Rect rect, in float angle, in Material material, in Color color, RenderAttributes attributes = null )
{
attributes ??= Attributes;
var color32 = color.ToColor32();
var rads = angle.DegreeToRadian();
var cos = MathF.Cos( rads );
var sin = MathF.Sin( rads );
var origin = rect.Center;
var addX = new Vector2( cos, sin ) * rect.Width * 0.5f;
var addY = new Vector2( -sin, cos ) * rect.Height * 0.5f;
var vertices = stackalloc Vertex[4]
{
new Vertex( origin - addX - addY, new Vector2( 0, 0 ), color32 ),
new Vertex( origin + addX - addY, new Vector2( 1, 0 ), color32 ),
new Vertex( origin - addX + addY, new Vector2( 0, 1 ), color32 ),
new Vertex( origin + addX + addY, new Vector2( 1, 1 ), color32 )
};
DrawInternal( vertices, 4, default, default, material, attributes, PrimitiveType.TriangleStrip );
}
/// <summary>
/// Draws a text quad in screenspace using the Material.UI.Text material.
/// </summary>
public static Rect DrawText( in Rect position, string text, Color color, string fontFamily = "Roboto", float fontSize = 20.0f, float fontWeight = 450, TextFlag flags = TextFlag.Center )
{
return DrawText( position, new TextRendering.Scope( text, color, fontSize, fontFamily, (int)fontWeight ), flags );
}
/// <summary>
/// Draws a text quad in screenspace using the Material.UI.Text material.
/// </summary>
public static Rect DrawText( in Rect position, in TextRendering.Scope scope, TextFlag flags = TextFlag.Center )
{
var texture = TextRendering.GetOrCreateTexture( scope, flag: flags );
Attributes.Set( "TextureIndex", texture.Index );
Attributes.Set( "SamplerIndex", SamplerState.GetBindlessIndex( new SamplerState() { Filter = scope.FilterMode } ) );
var rect = position.Align( texture.Size, flags );
DrawQuad( rect.Floor(), Material.UI.Text, Color.White );
return rect;
}
/// <summary>
/// Draws a rotated text quad in screenspace using the Material.UI.Text material.
/// </summary>
internal static void DrawText( in Rect position, float angle, in TextRendering.Scope scope, TextFlag flags = TextFlag.Center )
{
var texture = TextRendering.GetOrCreateTexture( scope, flag: flags );
Attributes.Set( "TextureIndex", texture.Index );
Attributes.Set( "SamplerIndex", SamplerState.GetBindlessIndex( new SamplerState() { Filter = scope.FilterMode } ) );
var rect = position.Align( texture.Size, flags );
DrawQuad( rect, angle, Material.UI.Text, Color.White );
}
/// <summary>
/// Draws a text quad in screenspace using the Material.UI.Text material.
/// </summary>
public static Rect DrawText( in Vector2 position, string text, Color color, string fontFamily = "Roboto", float fontSize = 20.0f, float fontWeight = 450 )
{
if ( string.IsNullOrEmpty( text ) ) text = null;
return DrawText( new Rect( position, 4096 ), text, color, fontFamily, fontSize, fontWeight, TextFlag.LeftTop );
}
/// <summary>
/// Measure how big some text will be, without having to render it
/// </summary>
public static Rect MeasureText( in Rect position, string text, string fontFamily = "Roboto", float fontSize = 20.0f, float fontWeight = 450, TextFlag flags = TextFlag.Center )
{
return MeasureText( position, new TextRendering.Scope( text, Color.White, fontSize, fontFamily, (int)fontWeight ), flags );
}
/// <summary>
/// Measure how big some text will be, without having to render it
/// </summary>
public static Rect MeasureText( in Rect position, in TextRendering.Scope scope, TextFlag flags = TextFlag.Center )
{
var block = TextRendering.GetOrCreateTexture( scope, position.Size, flags );
var rect = new Rect( position.Position, block.Size );
return rect;
}
/// <summary>
/// Calls DrawText with "Material Icons" font. You can get a list of icons here https://fonts.google.com/icons?selected=Material+Icons
/// </summary>
public static Rect DrawIcon( Rect rect, string iconName, Color color, float fontSize = 20.0f, TextFlag alignment = TextFlag.Center )
{
if ( string.IsNullOrEmpty( iconName ) )
iconName = "people";
return DrawText( rect, iconName?.ToLower(), color, "Material Icons", fontSize, flags: alignment );
}
/// <summary>
/// Draw a rounded rectangle, with optional border, in Material.UI.Box
/// </summary>
public static unsafe void DrawRoundedRectangle( in Rect rect, in Color color, in Vector4 cornerRadius = default, in Vector4 borderWidth = default, in Color borderColor = default )
{
Attributes.Set( "BoxPosition", new Vector2( rect.Left, rect.Top ) );
Attributes.Set( "BoxSize", new Vector2( rect.Width, rect.Height ) );
Attributes.Set( "BorderRadius", cornerRadius );
Attributes.SetCombo( "D_BACKGROUND_IMAGE", 0 );
if ( !borderWidth.IsNearZeroLength )
{
Attributes.Set( "HasBorder", 1 );
Attributes.SetCombo( "D_BORDER_IMAGE", 0 );
Attributes.Set( "BorderSize", borderWidth );
Attributes.Set( "BorderColorL", borderColor );
Attributes.Set( "BorderColorT", borderColor );
Attributes.Set( "BorderColorR", borderColor );
Attributes.Set( "BorderColorB", borderColor );
}
else
{
Attributes.Set( "HasBorder", 0 );
Attributes.SetCombo( "D_BORDER_IMAGE", 0 );
}
DrawQuad( rect, Material.UI.Box, color );
}
}