using NativeEngine; using System.Runtime.CompilerServices; namespace Sandbox; /// /// Used to render to the screen using your Graphics Card, or whatever you /// kids are using in your crazy future computers. Whatever it is I'm sure /// it isn't fungible and everyone has free money and no-one has to ever work. /// public static partial class Graphics { public enum PrimitiveType { Points = NativeEngine.RenderPrimitiveType.RENDER_PRIM_POINTS, Lines = NativeEngine.RenderPrimitiveType.RENDER_PRIM_LINES, LinesWithAdjacency = NativeEngine.RenderPrimitiveType.RENDER_PRIM_LINES_WITH_ADJACENCY, LineStrip = NativeEngine.RenderPrimitiveType.RENDER_PRIM_LINE_STRIP, LineStripWithAdjacency = NativeEngine.RenderPrimitiveType.RENDER_PRIM_LINE_STRIP_WITH_ADJACENCY, Triangles = NativeEngine.RenderPrimitiveType.RENDER_PRIM_TRIANGLES, TrianglesWithAdjacency = NativeEngine.RenderPrimitiveType.RENDER_PRIM_TRIANGLES_WITH_ADJACENCY, TriangleStrip = NativeEngine.RenderPrimitiveType.RENDER_PRIM_TRIANGLE_STRIP, TriangleStripWithAdjacency = NativeEngine.RenderPrimitiveType.RENDER_PRIM_TRIANGLE_STRIP_WITH_ADJACENCY, } /// /// If true then we're currently rendering and /// you are safe to use the contents of this class /// public static bool IsActive => _state.active; /// /// The current layer type. This is useful to tell whether you're meant to be drawing opaque, transparent or shadow. You mainly /// don't need to think about this, but when you do, it's here. /// public static SceneLayerType LayerType => _state.layerType; struct RenderState { public bool active; public IRenderContext renderContext; public ISceneLayer sceneLayer; public ISceneView sceneView; public SceneLayerType layerType; public RenderAttributes attributes; internal SceneSystemPerFrameStats_t stats; public ImageFormat colorFormat; public MultisampleAmount msaaLevel; public Transform cameraTransform; internal RenderAttributes frameAttributes; internal RenderTarget renderTarget; internal float defaultMinZ; internal float defaultMaxZ; } [ThreadStatic] private static RenderState _state; internal static IRenderContext Context => _state.renderContext; internal static ISceneLayer SceneLayer => _state.sceneLayer; internal static ISceneView SceneView => _state.sceneView; internal static ImageFormat IdealColorFormat => _state.colorFormat; internal static MultisampleAmount IdealMsaaLevel => _state.msaaLevel; internal static SceneSystemPerFrameStats_t Stats => _state.stats; /// /// When Frame grabbing, we store the result in this pooled render target. It stays checked out /// until the end of this render scope. /// [ThreadStatic] static HashSet grabbedTextures; /// /// In pixel size, where are we rendering to? /// public static Rect Viewport { get => Context.GetViewport().Rect; set => Context.SetViewport( value ); } /// /// Access to the current render context's attributes. These will be used /// to set attributes in materials/shaders. This is cleared at the end of the render block. /// public static RenderAttributes Attributes => _state.attributes; /// /// Access to the current frame's attributes. /// These will live until the end of the frame. /// internal static RenderAttributes FrameAttributes => _state.frameAttributes; /// /// The camera transform of the currently rendering view /// public static Transform CameraTransform => _state.cameraTransform; /// /// The camera position of the currently rendering view /// public static Vector3 CameraPosition => CameraTransform.Position; /// /// The camera rotation of the currently rendering view /// public static Rotation CameraRotation => CameraTransform.Rotation; /// /// The field of view of the currently rendering camera view, in degrees. /// public static float FieldOfView => _state.sceneView.GetFrustum().GetCameraFOV(); /// /// The frustum of the currently rendering camera view. /// public static Frustum Frustum { get { AssertRenderBlock(); var cf = _state.sceneView.GetFrustum(); // Extract planes from native CFrustum // Plane indices: RIGHT=0, LEFT=1, TOP=2, BOTTOM=3, NEAR=4, FAR=5 cf.GetPlane( 0, out var rn, out var rd ); cf.GetPlane( 1, out var ln, out var ld ); cf.GetPlane( 2, out var tn, out var td ); cf.GetPlane( 3, out var bn, out var bd ); cf.GetPlane( 4, out var nn, out var nd ); cf.GetPlane( 5, out var fn, out var fd ); return new Frustum( right: new Plane( ln, ld ), left: new Plane( ln, rd ), top: new Plane( tn, td ), bottom: new Plane( bn, bd ), near: new Plane( nn, nd ), far: new Plane( fn, fd ) ); } } internal static int RenderMultiSampleToNum( RenderMultisampleType msaaLevel ) { switch ( msaaLevel ) { case RenderMultisampleType.RENDER_MULTISAMPLE_NONE: return 1; case RenderMultisampleType.RENDER_MULTISAMPLE_2X: return 2; case RenderMultisampleType.RENDER_MULTISAMPLE_4X: return 4; case RenderMultisampleType.RENDER_MULTISAMPLE_6X: return 6; case RenderMultisampleType.RENDER_MULTISAMPLE_8X: return 8; case RenderMultisampleType.RENDER_MULTISAMPLE_16X: return 16; default: throw new System.Exception( "Unknown multisample amount" ); } } /// /// Creates a scope where Graphics is safe to use. /// internal ref struct Scope { RenderState _previous; public Scope( in ManagedRenderSetup_t setup ) { _previous = _state; _state = new RenderState(); var frustum = setup.sceneView.GetFrustum(); _state.active = true; _state.sceneLayer = setup.sceneLayer; _state.sceneView = setup.sceneView; _state.renderContext = setup.renderContext; _state.layerType = setup.sceneLayer.LayerEnum; _state.colorFormat = setup.colorImageFormat; _state.msaaLevel = setup.msaaLevel.FromEngine(); _state.cameraTransform = new Transform( frustum.GetCameraPosition(), frustum.GetCameraAngles() ); _state.stats = setup.stats; _state.attributes = ObjectPool.Get(); _state.attributes.Set( setup.renderContext.GetAttributesPtrForModify() ); _state.frameAttributes = ObjectPool.Get(); _state.frameAttributes.Set( setup.sceneView.GetRenderAttributesPtr() ); } public void Dispose() { if ( _state.attributes is not null ) { _state.attributes.Set( default( CRenderAttributes ) ); ObjectPool.Return( _state.attributes ); } if ( _state.frameAttributes is not null ) { _state.frameAttributes.Set( default( CRenderAttributes ) ); ObjectPool.Return( _state.frameAttributes ); } _state = _previous; if ( grabbedTextures is not null ) { // return to the pool foreach ( var tex in grabbedTextures ) { tex.Dispose(); } grabbedTextures.Clear(); } } } [MethodImpl( MethodImplOptions.AggressiveInlining )] internal static void AssertRenderBlock() { if ( !IsActive ) throw new System.Exception( "Tried to render outside of rendering block" ); } /// /// Setup the lighting attributes for this current object. Place them in the targetAttributes /// public static void SetupLighting( SceneObject obj, RenderAttributes targetAttributes = null ) { targetAttributes ??= Attributes; Assert.NotNull( targetAttributes ); Assert.IsValid( obj ); NativeEngine.CSceneSystem.SetupPerObjectLighting( targetAttributes.Get(), obj, SceneLayer ); } /// /// Grabs the current viewport's color texture and stores it in targetName on renderAttributes. /// public static RenderTarget GrabFrameTexture( string targetName = "FrameTexture", RenderAttributes renderAttributes = null, DownsampleMethod downsampleMethod = DownsampleMethod.None ) { renderAttributes ??= Attributes; AssertRenderBlock(); bool withMips = downsampleMethod != DownsampleMethod.None; var numMips = withMips ? (int)Math.Log2( Math.Max( Viewport.Width, Viewport.Height ) ) : 1; // Grab a new one - which may very well be the one we just returned var frameTexture = RenderTarget.GetTemporary( 1, ImageFormat.Default, ImageFormat.None, msaa: MultisampleAmount.MultisampleNone, numMips: numMips, targetName ); RenderTools.ResolveFrameBuffer( Context, frameTexture.ColorTarget.native, Viewport ); // Generate a mip chain if we want one if ( withMips ) GenerateMipMaps( frameTexture.ColorTarget, downsampleMethod ); grabbedTextures ??= new(); grabbedTextures.Add( frameTexture ); renderAttributes.Set( targetName, frameTexture.ColorTarget ); return frameTexture; } [Obsolete( "Use GrabFrameTexture with DownsampleMethod instead" )] public static void GrabFrameTexture( string targetName, RenderAttributes renderAttributes, bool withMips ) { GrabFrameTexture( targetName, renderAttributes, withMips ? DownsampleMethod.GaussianBlur : DownsampleMethod.None ); } /// /// Grabs the current depth texture and stores it in targetName on renderAttributes. /// public static RenderTarget GrabDepthTexture( string targetName = "DepthTexture", RenderAttributes renderAttributes = null ) { renderAttributes ??= Attributes; AssertRenderBlock(); // Grab a new one - which may very well be the one we just returned var grabbedTexture = RenderTarget.GetTemporary( 1, ImageFormat.None, ImageFormat.Default, msaa: MultisampleAmount.MultisampleScreen, targetName: targetName ); RenderTools.ResolveDepthBuffer( Context, grabbedTexture.DepthTarget.native, Viewport ); grabbedTextures ??= new(); grabbedTextures.Add( grabbedTexture ); renderAttributes.Set( targetName, grabbedTexture.DepthTarget ); return grabbedTexture; } /// /// Get or set the current render target. Setting this will bind the render target and change the viewport to match it. /// public static RenderTarget RenderTarget { get => _state.renderTarget; set { if ( _state.renderTarget == value ) return; // Going from default render target to custom render target if ( _state.renderTarget == null ) { var viewport = Context.GetViewport(); // Save off min/max Z values here, so we can restore them later. _state.defaultMinZ = viewport.MinZ; _state.defaultMaxZ = viewport.MaxZ; } _state.renderTarget = value; // Resetting to default render target if ( _state.renderTarget == null ) { // alex: if we don't restore min/max Z values properly when setting back to // the default render target, we get a lot of weird depth issues. // This mainly only applies to things like worldpanels when we render filtered // elements, but could probably happen in other places too? var viewport = new RenderViewport( SceneLayer.m_viewport.Rect, _state.defaultMinZ, _state.defaultMaxZ ); Context.SetViewport( viewport ); Context.RestoreRenderTargets( SceneLayer ); return; } Context.BindRenderTargets( _state.renderTarget.ColorTarget?.native ?? default, _state.renderTarget.DepthTarget?.native ?? default, SceneLayer ); Viewport = new Rect( 0, 0, _state.renderTarget.Width, _state.renderTarget.Height ); } } /// /// Clear the current drawing context to given color. /// /// Color to clear to. /// Whether to clear the color buffer at all. /// Whether to clear the depth buffer. /// Whether to clear the stencil buffer. public static void Clear( Color color, bool clearColor = true, bool clearDepth = true, bool clearStencil = true ) { Context.Clear( color, clearColor, clearDepth, clearStencil ); } /// /// Clear the current drawing context to given color. /// /// Whether to clear the color buffer to transparent color. /// Whether to clear the depth buffer. public static void Clear( bool clearColor = true, bool clearDepth = true ) { Clear( Color.Transparent, clearColor, clearDepth, false ); } /// /// Render this camera to the specified texture target /// [Obsolete( "Use CameraComponent.RenderToTexture" )] public static bool RenderToTexture( SceneCamera camera, Texture target ) { return false; } /// /// Copies pixel data from one texture to another on the GPU. /// This does not automatically resize or scale the texture, format and size should be equal. /// public static void CopyTexture( Texture srcTexture, Texture dstTexture ) { if ( srcTexture == null ) throw new ArgumentNullException( nameof( srcTexture ) ); if ( dstTexture == null ) throw new ArgumentNullException( nameof( dstTexture ) ); if ( srcTexture.ImageFormat != dstTexture.ImageFormat ) throw new ArgumentException( "Source and destination texture format must match!" ); if ( srcTexture.Size != dstTexture.Size ) throw new ArgumentException( "Source and destination texture size must match!" ); RenderTools.CopyTexture( Context, srcTexture.native, dstTexture.native, default, 0, 0, 0, 0, 0, 0 ); } [Obsolete( "Use the CopyTexture overload without 'srcMipLevels' and 'dstMipLevels' parameters instead." )] public static void CopyTexture( Texture srcTexture, Texture dstTexture, int srcMipSlice = 0, int srcArraySlice = 0, int srcMipLevels = 1, int dstMipSlice = 0, int dstArraySlice = 0, int dstMipLevels = 1 ) { CopyTexture( srcTexture, dstTexture, srcMipSlice, srcArraySlice, dstMipSlice, dstArraySlice ); } /// /// Copies pixel data from one texture to another on the GPU. /// This does not automatically resize or scale the texture, format and size should be equal. /// This one lets you copy to/from arrays / specific mips. /// public static void CopyTexture( Texture srcTexture, Texture dstTexture, int srcMipSlice = 0, int srcArraySlice = 0, int dstMipSlice = 0, int dstArraySlice = 0 ) { if ( srcTexture == null ) throw new ArgumentNullException( nameof( srcTexture ) ); if ( dstTexture == null ) throw new ArgumentNullException( nameof( dstTexture ) ); if ( srcTexture.ImageFormat != dstTexture.ImageFormat ) throw new ArgumentException( "Source and destination texture format must match!" ); if ( srcMipSlice < 0 || srcMipSlice >= srcTexture.Mips ) throw new ArgumentException( $"{nameof( srcMipSlice )} out of bounds" ); if ( dstMipSlice < 0 || dstMipSlice >= dstTexture.Mips ) throw new ArgumentException( $"{nameof( dstMipSlice )} out of bounds" ); var srcDesc = srcTexture.Desc; var dstDesc = dstTexture.Desc; if ( srcArraySlice < 0 || srcArraySlice >= srcDesc.ArrayCount ) throw new ArgumentException( $"{nameof( srcArraySlice )} out of bounds" ); if ( dstArraySlice < 0 || dstArraySlice >= dstDesc.ArrayCount ) throw new ArgumentException( $"{nameof( dstArraySlice )} out of bounds" ); var srcMipWidth = Math.Max( 1, srcTexture.Width >> srcMipSlice ); var srcMipHeight = Math.Max( 1, srcTexture.Height >> srcMipSlice ); var dstMipWidth = Math.Max( 1, dstTexture.Width >> dstMipSlice ); var dstMipHeight = Math.Max( 1, dstTexture.Height >> dstMipSlice ); if ( srcMipWidth != dstMipWidth || srcMipHeight != dstMipHeight ) throw new ArgumentException( "Source and destination texture mip level sizes must match!" ); RenderTools.CopyTexture( Context, srcTexture.native, dstTexture.native, default, 0, 0, (uint)srcMipSlice, (uint)srcArraySlice, (uint)dstMipSlice, (uint)dstArraySlice ); } }