823 lines
32 KiB
C#
823 lines
32 KiB
C#
#pragma warning disable 649 // Field `Drawing.GizmoContext.activeTransform' is never assigned to, and will always have its default value `null'. Not used outside of the unity editor.
|
|
using UnityEngine;
|
|
using System.Collections;
|
|
using System;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
#endif
|
|
using System.Collections.Generic;
|
|
using Unity.Jobs;
|
|
using Unity.Mathematics;
|
|
using UnityEngine.Rendering;
|
|
using Unity.Profiling;
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
using UnityEngine.Rendering.Universal;
|
|
using UnityEngine.Profiling;
|
|
#endif
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION
|
|
using UnityEngine.Rendering.HighDefinition;
|
|
#endif
|
|
|
|
namespace Pathfinding.Drawing {
|
|
/// <summary>Info about the current selection in the editor</summary>
|
|
public static class GizmoContext {
|
|
#if UNITY_EDITOR
|
|
static Transform activeTransform;
|
|
#endif
|
|
|
|
static HashSet<Transform> selectedTransforms = new HashSet<Transform>();
|
|
|
|
static internal bool drawingGizmos;
|
|
static internal bool dirty;
|
|
private static int selectionSizeInternal;
|
|
|
|
/// <summary>Number of top-level transforms that are selected</summary>
|
|
public static int selectionSize {
|
|
get {
|
|
Refresh();
|
|
return selectionSizeInternal;
|
|
}
|
|
private set {
|
|
selectionSizeInternal = value;
|
|
}
|
|
}
|
|
|
|
internal static void SetDirty () {
|
|
dirty = true;
|
|
}
|
|
|
|
private static void Refresh () {
|
|
#if UNITY_EDITOR
|
|
if (!drawingGizmos) throw new System.Exception("Can only be used inside the ALINE library's gizmo drawing functions.");
|
|
if (dirty) {
|
|
dirty = false;
|
|
DrawingManager.MarkerRefreshSelectionCache.Begin();
|
|
activeTransform = Selection.activeTransform;
|
|
selectedTransforms.Clear();
|
|
var topLevel = Selection.transforms;
|
|
for (int i = 0; i < topLevel.Length; i++) selectedTransforms.Add(topLevel[i]);
|
|
selectionSize = topLevel.Length;
|
|
DrawingManager.MarkerRefreshSelectionCache.End();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the component is selected.
|
|
/// This is a deep selection: even children of selected transforms are considered to be selected.
|
|
/// </summary>
|
|
public static bool InSelection (Component c) {
|
|
return InSelection(c.transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the transform is selected.
|
|
/// This is a deep selection: even children of selected transforms are considered to be selected.
|
|
/// </summary>
|
|
public static bool InSelection (Transform tr) {
|
|
Refresh();
|
|
var leaf = tr;
|
|
while (tr != null) {
|
|
if (selectedTransforms.Contains(tr)) {
|
|
selectedTransforms.Add(leaf);
|
|
return true;
|
|
}
|
|
tr = tr.parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the component is shown in the inspector.
|
|
/// The active selection is the GameObject that is currently visible in the inspector.
|
|
/// </summary>
|
|
public static bool InActiveSelection (Component c) {
|
|
return InActiveSelection(c.transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the transform is shown in the inspector.
|
|
/// The active selection is the GameObject that is currently visible in the inspector.
|
|
/// </summary>
|
|
public static bool InActiveSelection (Transform tr) {
|
|
#if UNITY_EDITOR
|
|
Refresh();
|
|
return tr.transform == activeTransform;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Every object that wants to draw gizmos should implement this interface.
|
|
/// See: <see cref="Drawing.MonoBehaviourGizmos"/>
|
|
/// </summary>
|
|
public interface IDrawGizmos {
|
|
void DrawGizmos();
|
|
}
|
|
|
|
public enum DetectedRenderPipeline {
|
|
BuiltInOrCustom,
|
|
HDRP,
|
|
URP
|
|
}
|
|
|
|
/// <summary>
|
|
/// Global script which draws debug items and gizmos.
|
|
/// If a Draw.* method has been used or if any script inheriting from the <see cref="Drawing.MonoBehaviourGizmos"/> class is in the scene then an instance of this script
|
|
/// will be created and put on a hidden GameObject.
|
|
///
|
|
/// It will inject drawing logic into any cameras that are rendered.
|
|
///
|
|
/// Usually you never have to interact with this class.
|
|
/// </summary>
|
|
[ExecuteAlways]
|
|
[AddComponentMenu("")]
|
|
public class DrawingManager : MonoBehaviour {
|
|
public DrawingData gizmos;
|
|
static List<GizmoDrawerGroup> gizmoDrawers = new List<GizmoDrawerGroup>();
|
|
static Dictionary<System.Type, int> gizmoDrawerIndices = new Dictionary<System.Type, int>();
|
|
static DrawingManager _instance;
|
|
bool framePassed;
|
|
int lastFrameCount = int.MinValue;
|
|
float lastFrameTime = -float.NegativeInfinity;
|
|
int lastFilterFrame;
|
|
#if UNITY_EDITOR
|
|
bool builtGizmos;
|
|
#endif
|
|
|
|
struct GizmoDrawerGroup {
|
|
public System.Type type;
|
|
public ProfilerMarker profilerMarker;
|
|
public List<IDrawGizmos> drawers;
|
|
public bool enabled;
|
|
}
|
|
|
|
/// <summary>True if OnEnable has been called on this instance and OnDisable has not</summary>
|
|
[SerializeField]
|
|
bool actuallyEnabled;
|
|
|
|
RedrawScope previousFrameRedrawScope;
|
|
|
|
/// <summary>
|
|
/// Allow rendering to cameras that render to RenderTextures.
|
|
/// By default cameras which render to render textures are never rendered to.
|
|
/// You may enable this if you wish.
|
|
///
|
|
/// See: <see cref="Drawing.CommandBuilder.cameraTargets"/>
|
|
/// See: advanced (view in online documentation for working links)
|
|
/// </summary>
|
|
public static bool allowRenderToRenderTextures = false;
|
|
public static bool drawToAllCameras = false;
|
|
|
|
/// <summary>
|
|
/// Multiply all line widths by this value.
|
|
/// This can be used to make lines thicker or thinner.
|
|
///
|
|
/// This is primarily useful when generating screenshots, and you want to render at a higher resolution before scaling down the image.
|
|
///
|
|
/// It is only read when a camera is being rendered. So it cannot be used to change line thickness on a per-item basis.
|
|
/// Use <see cref="Draw.WithLineWidth"/> for that.
|
|
/// </summary>
|
|
public static float lineWidthMultiplier = 1.0f;
|
|
|
|
CommandBuffer commandBuffer;
|
|
|
|
[System.NonSerialized]
|
|
DetectedRenderPipeline detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom;
|
|
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER
|
|
CustomPass hdrpGlobalPass;
|
|
#endif
|
|
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
HashSet<ScriptableRenderer> scriptableRenderersWithPass = new HashSet<ScriptableRenderer>();
|
|
AlineURPRenderPassFeature renderPassFeature;
|
|
#endif
|
|
|
|
private static readonly ProfilerMarker MarkerALINE = new ProfilerMarker("ALINE");
|
|
private static readonly ProfilerMarker MarkerCommandBuffer = new ProfilerMarker("Executing command buffer");
|
|
private static readonly ProfilerMarker MarkerFrameTick = new ProfilerMarker("Frame Tick");
|
|
private static readonly ProfilerMarker MarkerFilterDestroyedObjects = new ProfilerMarker("Filter destroyed objects");
|
|
internal static readonly ProfilerMarker MarkerRefreshSelectionCache = new ProfilerMarker("Refresh Selection Cache");
|
|
private static readonly ProfilerMarker MarkerGizmosAllowed = new ProfilerMarker("GizmosAllowed");
|
|
private static readonly ProfilerMarker MarkerDrawGizmos = new ProfilerMarker("DrawGizmos");
|
|
private static readonly ProfilerMarker MarkerSubmitGizmos = new ProfilerMarker("Submit Gizmos");
|
|
|
|
public static DrawingManager instance {
|
|
get {
|
|
if (_instance == null) Init();
|
|
return _instance;
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
[InitializeOnLoadMethod]
|
|
#endif
|
|
public static void Init () {
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
if (Unity.Jobs.LowLevel.Unsafe.JobsUtility.IsExecutingJob) throw new System.Exception("Draw.* methods cannot be called from inside a job. See the documentation for info about how to use drawing functions from the Unity Job System.");
|
|
#endif
|
|
if (_instance != null) return;
|
|
|
|
// Here one might try to look for existing instances of the class that haven't yet been enabled.
|
|
// However, this turns out to be tricky.
|
|
// Resources.FindObjectsOfTypeAll<T>() is the only call that includes HideInInspector GameObjects.
|
|
// But it is hard to distinguish between objects that are internal ones which will never be enabled and objects that will be enabled.
|
|
// Checking .gameObject.scene.isLoaded doesn't work reliably (object may be enabled and working even if isLoaded is false)
|
|
// Checking .gameObject.scene.isValid doesn't work reliably (object may be enabled and working even if isValid is false)
|
|
|
|
// So instead we just always create a new instance. This is not a particularly heavy operation and it only happens once per game, so why not.
|
|
// The OnEnable call will clean up duplicate managers if there are any.
|
|
|
|
var go = new GameObject("RetainedGizmos") {
|
|
hideFlags = HideFlags.DontSave | HideFlags.NotEditable | HideFlags.HideInInspector | HideFlags.HideInHierarchy
|
|
};
|
|
_instance = go.AddComponent<DrawingManager>();
|
|
if (Application.isPlaying) DontDestroyOnLoad(go);
|
|
}
|
|
|
|
/// <summary>Detects which render pipeline is being used and configures them for rendering</summary>
|
|
void RefreshRenderPipelineMode () {
|
|
var pipelineType = RenderPipelineManager.currentPipeline != null? RenderPipelineManager.currentPipeline.GetType() : null;
|
|
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION
|
|
if (pipelineType == typeof(HDRenderPipeline)) {
|
|
if (detectedRenderPipeline != DetectedRenderPipeline.HDRP) {
|
|
detectedRenderPipeline = DetectedRenderPipeline.HDRP;
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER
|
|
UnityEngine.Assertions.Assert.IsNull(hdrpGlobalPass);
|
|
hdrpGlobalPass = new AlineHDRPCustomPass();
|
|
CustomPassVolume.RegisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass);
|
|
#else
|
|
if (!_instance.gameObject.TryGetComponent<CustomPassVolume>(out CustomPassVolume volume)) {
|
|
volume = _instance.gameObject.AddComponent<CustomPassVolume>();
|
|
volume.isGlobal = true;
|
|
volume.injectionPoint = CustomPassInjectionPoint.AfterPostProcess;
|
|
volume.customPasses.Add(new AlineHDRPCustomPass());
|
|
}
|
|
#endif
|
|
|
|
var asset = GraphicsSettings.defaultRenderPipeline as HDRenderPipelineAsset;
|
|
if (asset != null) {
|
|
if (!asset.currentPlatformRenderPipelineSettings.supportCustomPass) {
|
|
Debug.LogWarning("A*: The current render pipeline has custom pass support disabled. The A* Pathfinding Project will not be able to render anything. Please enable custom pass support on your HDRenderPipelineAsset.", asset);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UNITY_ASSERTIONS && MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER
|
|
var globalPasses = CustomPassVolume.GetGlobalCustomPasses(CustomPassInjectionPoint.AfterPostProcess);
|
|
bool found = false;
|
|
for (int i = 0; i < globalPasses.Count; i++) found |= globalPasses[i].instance == hdrpGlobalPass;
|
|
UnityEngine.Assertions.Assert.IsTrue(found, "Custom pass for gizmos is not registered. Have the custom passes been forcefully removed by another script?");
|
|
#endif
|
|
return;
|
|
}
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER
|
|
if (hdrpGlobalPass != null) {
|
|
CustomPassVolume.UnregisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass);
|
|
hdrpGlobalPass = null;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
if (pipelineType == typeof(UniversalRenderPipeline)) {
|
|
detectedRenderPipeline = DetectedRenderPipeline.URP;
|
|
return;
|
|
}
|
|
#endif
|
|
detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void DelayedDestroy () {
|
|
EditorApplication.update -= DelayedDestroy;
|
|
// Check if the object still exists (it might have been destroyed in some other way already).
|
|
if (gameObject) GameObject.DestroyImmediate(gameObject);
|
|
}
|
|
|
|
void OnPlayModeStateChanged (PlayModeStateChange change) {
|
|
if (change == PlayModeStateChange.ExitingEditMode || change == PlayModeStateChange.ExitingPlayMode) {
|
|
gizmos.OnChangingPlayMode();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void OnEnable () {
|
|
if (_instance == null) _instance = this;
|
|
|
|
// Ensure we don't have duplicate managers
|
|
if (_instance != this) {
|
|
// We cannot destroy the object while it is being enabled, so we need to delay it a bit
|
|
#if UNITY_EDITOR
|
|
// This is only important in the editor to avoid a build-up of old managers.
|
|
// In an actual game at most 1 (though in practice zero) old managers will be laying around.
|
|
// It would be nice to use a coroutine for this instead, but unfortunately they do not work for objects marked with HideAndDontSave.
|
|
EditorApplication.update += DelayedDestroy;
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
actuallyEnabled = true;
|
|
if (gizmos == null) gizmos = new DrawingData();
|
|
gizmos.frameRedrawScope = new RedrawScope(gizmos);
|
|
Draw.builder = gizmos.GetBuiltInBuilder(false);
|
|
Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
|
|
commandBuffer = new CommandBuffer();
|
|
commandBuffer.name = "ALINE Gizmos";
|
|
|
|
detectedRenderPipeline = DetectedRenderPipeline.BuiltInOrCustom;
|
|
|
|
// Callback when rendering with the built-in render pipeline
|
|
Camera.onPostRender += PostRender;
|
|
// Callback when rendering with a scriptable render pipeline
|
|
#if UNITY_2021_1_OR_NEWER
|
|
UnityEngine.Rendering.RenderPipelineManager.beginContextRendering += BeginContextRendering;
|
|
#else
|
|
UnityEngine.Rendering.RenderPipelineManager.beginFrameRendering += BeginFrameRendering;
|
|
#endif
|
|
UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering += BeginCameraRendering;
|
|
UnityEngine.Rendering.RenderPipelineManager.endCameraRendering += EndCameraRendering;
|
|
#if UNITY_EDITOR
|
|
EditorApplication.update += OnEditorUpdate;
|
|
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
|
#endif
|
|
}
|
|
|
|
void BeginContextRendering (ScriptableRenderContext context, List<Camera> cameras) {
|
|
RefreshRenderPipelineMode();
|
|
}
|
|
|
|
void BeginFrameRendering (ScriptableRenderContext context, Camera[] cameras) {
|
|
RefreshRenderPipelineMode();
|
|
}
|
|
|
|
void BeginCameraRendering (ScriptableRenderContext context, Camera camera) {
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
if (detectedRenderPipeline == DetectedRenderPipeline.URP) {
|
|
var data = camera.GetUniversalAdditionalCameraData();
|
|
if (data != null) {
|
|
var renderer = data.scriptableRenderer;
|
|
if (renderPassFeature == null) {
|
|
renderPassFeature = ScriptableObject.CreateInstance<AlineURPRenderPassFeature>();
|
|
}
|
|
renderPassFeature.AddRenderPasses(renderer);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void OnDisable () {
|
|
if (!actuallyEnabled) return;
|
|
actuallyEnabled = false;
|
|
commandBuffer.Dispose();
|
|
commandBuffer = null;
|
|
Camera.onPostRender -= PostRender;
|
|
#if UNITY_2021_1_OR_NEWER
|
|
UnityEngine.Rendering.RenderPipelineManager.beginContextRendering -= BeginContextRendering;
|
|
#else
|
|
UnityEngine.Rendering.RenderPipelineManager.beginFrameRendering -= BeginFrameRendering;
|
|
#endif
|
|
UnityEngine.Rendering.RenderPipelineManager.beginCameraRendering -= BeginCameraRendering;
|
|
UnityEngine.Rendering.RenderPipelineManager.endCameraRendering -= EndCameraRendering;
|
|
#if UNITY_EDITOR
|
|
EditorApplication.update -= OnEditorUpdate;
|
|
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
|
#endif
|
|
// Gizmos can be null here if this GameObject was duplicated by a user in the hierarchy.
|
|
if (gizmos != null) {
|
|
Draw.builder.DiscardAndDisposeInternal();
|
|
Draw.ingame_builder.DiscardAndDisposeInternal();
|
|
gizmos.ClearData();
|
|
}
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
if (renderPassFeature != null) {
|
|
ScriptableObject.DestroyImmediate(renderPassFeature);
|
|
renderPassFeature = null;
|
|
}
|
|
#endif
|
|
#if MODULE_RENDER_PIPELINES_HIGH_DEFINITION_16_0_0_OR_NEWER
|
|
if (hdrpGlobalPass != null) {
|
|
CustomPassVolume.UnregisterGlobalCustomPass(CustomPassInjectionPoint.AfterPostProcess, hdrpGlobalPass);
|
|
hdrpGlobalPass = null;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// When enter play mode = reload scene & reload domain
|
|
// editor => play mode: OnDisable -> OnEnable (same object)
|
|
// play mode => editor: OnApplicationQuit (note: no OnDisable/OnEnable)
|
|
// When enter play mode = reload scene & !reload domain
|
|
// editor => play mode: Nothing
|
|
// play mode => editor: OnApplicationQuit
|
|
// When enter play mode = !reload scene & !reload domain
|
|
// editor => play mode: Nothing
|
|
// play mode => editor: OnApplicationQuit
|
|
// OnDestroy is never really called for this object (unless Unity or the game quits I quess)
|
|
|
|
// TODO: Should run in OnDestroy. OnApplicationQuit runs BEFORE OnDestroy (which we do not want)
|
|
// private void OnApplicationQuit () {
|
|
// Debug.Log("OnApplicationQuit");
|
|
// Draw.builder.DiscardAndDisposeInternal();
|
|
// Draw.ingame_builder.DiscardAndDisposeInternal();
|
|
// gizmos.ClearData();
|
|
// Draw.builder = gizmos.GetBuiltInBuilder(false);
|
|
// Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
|
|
// }
|
|
|
|
const float NO_DRAWING_TIMEOUT_SECS = 10;
|
|
|
|
void OnEditorUpdate () {
|
|
framePassed = true;
|
|
CleanupIfNoCameraRendered();
|
|
}
|
|
|
|
void Update () {
|
|
if (actuallyEnabled) CleanupIfNoCameraRendered();
|
|
}
|
|
|
|
void CleanupIfNoCameraRendered () {
|
|
if (Time.frameCount > lastFrameCount + 1) {
|
|
// More than one frame old
|
|
// It is possible no camera is being rendered at all.
|
|
// Ensure we don't get any memory leaks from drawing items being queued every frame.
|
|
CheckFrameTicking();
|
|
gizmos.PostRenderCleanup();
|
|
|
|
// Note: We do not always want to call the above method here
|
|
// because it is nicer to call it right after the cameras have been rendered.
|
|
// Otherwise drawing items queued before Update/OnEditorUpdate or after Update/OnEditorUpdate may end up
|
|
// in different frames (for the purposes of rendering gizmos)
|
|
}
|
|
|
|
if (Time.realtimeSinceStartup - lastFrameTime > NO_DRAWING_TIMEOUT_SECS) {
|
|
// More than NO_DRAWING_TIMEOUT_SECS seconds since we drew the last frame.
|
|
// In the editor some script could be queuing drawing commands in e.g. EditorWindow.Update without the scene
|
|
// view or any game view being re-rendered. We discard these commands if nothing has been rendered for a long time.
|
|
Draw.builder.DiscardAndDisposeInternal();
|
|
Draw.ingame_builder.DiscardAndDisposeInternal();
|
|
Draw.builder = gizmos.GetBuiltInBuilder(false);
|
|
Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
|
|
lastFrameTime = Time.realtimeSinceStartup;
|
|
RemoveDestroyedGizmoDrawers();
|
|
}
|
|
|
|
// Avoid potential memory leak if gizmos are not being drawn
|
|
if (lastFilterFrame - Time.frameCount > 5) {
|
|
lastFilterFrame = Time.frameCount;
|
|
RemoveDestroyedGizmoDrawers();
|
|
}
|
|
}
|
|
|
|
internal void ExecuteCustomRenderPass (ScriptableRenderContext context, Camera camera) {
|
|
MarkerALINE.Begin();
|
|
commandBuffer.Clear();
|
|
SubmitFrame(camera, new DrawingData.CommandBufferWrapper { cmd = commandBuffer }, true);
|
|
context.ExecuteCommandBuffer(commandBuffer);
|
|
MarkerALINE.End();
|
|
}
|
|
|
|
#if MODULE_RENDER_PIPELINES_UNIVERSAL
|
|
internal void ExecuteCustomRenderGraphPass (DrawingData.CommandBufferWrapper cmd, Camera camera) {
|
|
MarkerALINE.Begin();
|
|
SubmitFrame(camera, cmd, true);
|
|
MarkerALINE.End();
|
|
}
|
|
#endif
|
|
|
|
private void EndCameraRendering (ScriptableRenderContext context, Camera camera) {
|
|
if (detectedRenderPipeline == DetectedRenderPipeline.BuiltInOrCustom) {
|
|
// Execute the custom render pass after the camera has finished rendering.
|
|
// For the HDRP and URP the render pass will already have been executed.
|
|
// However for a custom render pipline we execute the rendering code here.
|
|
// This is only best effort. It's impossible to be compatible with all custom render pipelines.
|
|
// However it should work for most simple ones.
|
|
// For Unity's built-in render pipeline the EndCameraRendering method will never be called.
|
|
ExecuteCustomRenderPass(context, camera);
|
|
}
|
|
}
|
|
|
|
void PostRender (Camera camera) {
|
|
// This method is only called when using Unity's built-in render pipeline
|
|
commandBuffer.Clear();
|
|
SubmitFrame(camera, new DrawingData.CommandBufferWrapper { cmd = commandBuffer }, false);
|
|
MarkerCommandBuffer.Begin();
|
|
Graphics.ExecuteCommandBuffer(commandBuffer);
|
|
MarkerCommandBuffer.End();
|
|
}
|
|
|
|
void CheckFrameTicking () {
|
|
MarkerFrameTick.Begin();
|
|
if (Time.frameCount != lastFrameCount) {
|
|
framePassed = true;
|
|
lastFrameCount = Time.frameCount;
|
|
lastFrameTime = Time.realtimeSinceStartup;
|
|
previousFrameRedrawScope = gizmos.frameRedrawScope;
|
|
gizmos.frameRedrawScope = new RedrawScope(gizmos);
|
|
Draw.builder.DisposeInternal();
|
|
Draw.ingame_builder.DisposeInternal();
|
|
Draw.builder = gizmos.GetBuiltInBuilder(false);
|
|
Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
|
|
} else if (framePassed && Application.isPlaying) {
|
|
// Rendered frame passed without a game frame passing!
|
|
// This might mean the game is paused.
|
|
// Redraw gizmos while the game is paused.
|
|
// It might also just mean that we are rendering with multiple cameras.
|
|
previousFrameRedrawScope.Draw();
|
|
}
|
|
|
|
if (framePassed) {
|
|
gizmos.TickFramePreRender();
|
|
#if UNITY_EDITOR
|
|
builtGizmos = false;
|
|
#endif
|
|
framePassed = false;
|
|
}
|
|
MarkerFrameTick.End();
|
|
}
|
|
|
|
internal void SubmitFrame (Camera camera, DrawingData.CommandBufferWrapper cmd, bool usingRenderPipeline) {
|
|
#if UNITY_EDITOR
|
|
bool isSceneViewCamera = SceneView.currentDrawingSceneView != null && SceneView.currentDrawingSceneView.camera == camera;
|
|
#else
|
|
bool isSceneViewCamera = false;
|
|
#endif
|
|
// Do not include when rendering to a texture unless this is a scene view camera
|
|
bool allowCameraDefault = allowRenderToRenderTextures || drawToAllCameras || camera.targetTexture == null || isSceneViewCamera;
|
|
|
|
CheckFrameTicking();
|
|
|
|
Submit(camera, cmd, usingRenderPipeline, allowCameraDefault);
|
|
|
|
gizmos.PostRenderCleanup();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
static readonly System.Reflection.MethodInfo IsGizmosAllowedForObject = typeof(UnityEditor.EditorGUIUtility).GetMethod("IsGizmosAllowedForObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
|
|
readonly System.Object[] cachedObjectParameterArray = new System.Object[1];
|
|
#endif
|
|
|
|
bool ShouldDrawGizmos (UnityEngine.Object obj) {
|
|
#if UNITY_EDITOR
|
|
// Use reflection to call EditorGUIUtility.IsGizmosAllowedForObject which is an internal method.
|
|
// It is exactly the information we want though.
|
|
// In case Unity has changed its API or something so that the method can no longer be found then just return true
|
|
cachedObjectParameterArray[0] = obj;
|
|
return IsGizmosAllowedForObject == null || (bool)IsGizmosAllowedForObject.Invoke(null, cachedObjectParameterArray);
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static void RemoveDestroyedGizmoDrawers () {
|
|
MarkerFilterDestroyedObjects.Begin();
|
|
for (int i = 0; i < gizmoDrawers.Count; i++) {
|
|
var group = gizmoDrawers[i];
|
|
int j = 0;
|
|
for (int k = 0; k < group.drawers.Count; k++) {
|
|
var v = group.drawers[k];
|
|
if (v as MonoBehaviour) {
|
|
group.drawers[j] = v;
|
|
j++;
|
|
}
|
|
}
|
|
group.drawers.RemoveRange(j, group.drawers.Count - j);
|
|
}
|
|
MarkerFilterDestroyedObjects.End();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void DrawGizmos (bool usingRenderPipeline) {
|
|
GizmoContext.SetDirty();
|
|
MarkerGizmosAllowed.Begin();
|
|
|
|
// Figure out which component types should be rendered
|
|
for (int i = 0; i < gizmoDrawers.Count; i++) {
|
|
var group = gizmoDrawers[i];
|
|
#if UNITY_2022_1_OR_NEWER
|
|
// In Unity 2022.1 we can use a new utility class which is more robust.
|
|
if (GizmoUtility.TryGetGizmoInfo(group.type, out var gizmoInfo)) {
|
|
group.enabled = gizmoInfo.gizmoEnabled;
|
|
} else {
|
|
group.enabled = true;
|
|
}
|
|
#else
|
|
// We take advantage of the fact that IsGizmosAllowedForObject only depends on the type of the object and if it is active and enabled
|
|
// and not the specific object instance.
|
|
// When using a render pipeline the ShouldDrawGizmos method cannot be used because it seems to occasionally crash Unity :(
|
|
// So we need these two separate cases.
|
|
if (!usingRenderPipeline) {
|
|
group.enabled = false;
|
|
for (int j = group.drawers.Count - 1; j >= 0; j--) {
|
|
// Find the first active and enabled drawer
|
|
if ((group.drawers[j] as MonoBehaviour).isActiveAndEnabled) {
|
|
group.enabled = ShouldDrawGizmos((UnityEngine.Object)group.drawers[j]);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
group.enabled = true;
|
|
}
|
|
#endif
|
|
gizmoDrawers[i] = group;
|
|
}
|
|
|
|
MarkerGizmosAllowed.End();
|
|
|
|
// Set the current frame's redraw scope to an empty scope.
|
|
// This is because gizmos are rendered every frame anyway so we never want to redraw them.
|
|
// The frame redraw scope is otherwise used when the game has been paused.
|
|
var frameRedrawScope = gizmos.frameRedrawScope;
|
|
gizmos.frameRedrawScope = default(RedrawScope);
|
|
|
|
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
|
|
var currentStage = StageUtility.GetCurrentStage();
|
|
var isInNonMainStage = currentStage != StageUtility.GetMainStage();
|
|
var currentStageHandle = currentStage.stageHandle;
|
|
#endif
|
|
|
|
// This would look nicer as a 'using' block, but built-in command builders
|
|
// cannot be disposed normally to prevent user error.
|
|
// The try-finally is equivalent to a 'using' block.
|
|
var gizmoBuilder = gizmos.GetBuiltInBuilder();
|
|
// Replace Draw.builder with a custom one just for gizmos
|
|
var debugBuilder = Draw.builder;
|
|
MarkerDrawGizmos.Begin();
|
|
GizmoContext.drawingGizmos = true;
|
|
try {
|
|
Draw.builder = gizmoBuilder;
|
|
|
|
for (int i = gizmoDrawers.Count - 1; i >= 0; i--) {
|
|
var group = gizmoDrawers[i];
|
|
if (group.enabled && group.drawers.Count > 0) {
|
|
group.profilerMarker.Begin();
|
|
for (int j = group.drawers.Count - 1; j >= 0; j--) {
|
|
var mono = group.drawers[j] as MonoBehaviour;
|
|
if (!mono.isActiveAndEnabled || (mono.hideFlags & HideFlags.HideInHierarchy) != 0) continue;
|
|
|
|
#if UNITY_EDITOR && UNITY_2020_1_OR_NEWER
|
|
// True if the scene is in isolation mode (e.g. focusing on a single prefab) and this object is not part of that sub-stage
|
|
var disabledDueToIsolationMode = isInNonMainStage && !currentStageHandle.Contains(mono.gameObject);
|
|
if (disabledDueToIsolationMode) continue;
|
|
#endif
|
|
|
|
try {
|
|
group.drawers[j].DrawGizmos();
|
|
} catch (System.Exception e) {
|
|
Debug.LogException(e, mono);
|
|
}
|
|
}
|
|
group.profilerMarker.End();
|
|
}
|
|
}
|
|
} finally {
|
|
GizmoContext.drawingGizmos = false;
|
|
MarkerDrawGizmos.End();
|
|
// Revert to the original builder
|
|
Draw.builder = debugBuilder;
|
|
gizmoBuilder.DisposeInternal();
|
|
}
|
|
|
|
gizmos.frameRedrawScope = frameRedrawScope;
|
|
|
|
// Schedule jobs that may have been scheduled while drawing gizmos
|
|
JobHandle.ScheduleBatchedJobs();
|
|
}
|
|
#endif
|
|
|
|
/// <summary>Submit a camera for rendering.</summary>
|
|
/// <param name="allowCameraDefault">Indicates if built-in command builders and custom ones without a custom CommandBuilder.cameraTargets should render to this camera.</param>
|
|
void Submit (Camera camera, DrawingData.CommandBufferWrapper cmd, bool usingRenderPipeline, bool allowCameraDefault) {
|
|
#if UNITY_EDITOR
|
|
bool drawGizmos = Handles.ShouldRenderGizmos() || drawToAllCameras;
|
|
// Only build gizmos if a camera actually needs them.
|
|
// This is only done for the first camera that needs them each frame.
|
|
if (drawGizmos && !builtGizmos && allowCameraDefault) {
|
|
RemoveDestroyedGizmoDrawers();
|
|
lastFilterFrame = Time.frameCount;
|
|
builtGizmos = true;
|
|
DrawGizmos(usingRenderPipeline);
|
|
}
|
|
#else
|
|
bool drawGizmos = false;
|
|
#endif
|
|
|
|
MarkerSubmitGizmos.Begin();
|
|
Draw.builder.DisposeInternal();
|
|
Draw.ingame_builder.DisposeInternal();
|
|
gizmos.Render(camera, drawGizmos, cmd, allowCameraDefault);
|
|
Draw.builder = gizmos.GetBuiltInBuilder(false);
|
|
Draw.ingame_builder = gizmos.GetBuiltInBuilder(true);
|
|
MarkerSubmitGizmos.End();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers an object for gizmo drawing.
|
|
/// The DrawGizmos method on the object will be called every frame until it is destroyed (assuming there are cameras with gizmos enabled).
|
|
/// </summary>
|
|
public static void Register (IDrawGizmos item) {
|
|
var tp = item.GetType();
|
|
|
|
int index;
|
|
if (gizmoDrawerIndices.TryGetValue(tp, out index)) {
|
|
} else {
|
|
// Use reflection to figure out if the DrawGizmos method has not been overriden from the MonoBehaviourGizmos class.
|
|
// If it hasn't, then we know that this type will never draw gizmos and we can skip it.
|
|
// This improves performance by not having to keep track of objects and check if they are active and enabled every frame.
|
|
|
|
var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic;
|
|
// Check for a public method first, and then an explicit interface implementation.
|
|
var m = tp.GetMethod("DrawGizmos", flags) ?? tp.GetMethod("Pathfinding.Drawing.IDrawGizmos.DrawGizmos", flags) ?? tp.GetMethod("Drawing.IDrawGizmos.DrawGizmos", flags);
|
|
if (m == null) {
|
|
throw new System.Exception("Could not find the DrawGizmos method in type " + tp.Name);
|
|
}
|
|
var mayDrawGizmos = m.DeclaringType != typeof(MonoBehaviourGizmos);
|
|
if (mayDrawGizmos) {
|
|
index = gizmoDrawerIndices[tp] = gizmoDrawers.Count;
|
|
gizmoDrawers.Add(new GizmoDrawerGroup {
|
|
type = tp,
|
|
enabled = true,
|
|
drawers = new List<IDrawGizmos>(),
|
|
profilerMarker = new ProfilerMarker(ProfilerCategory.Render, "Gizmos for " + tp.Name),
|
|
});
|
|
} else {
|
|
index = gizmoDrawerIndices[tp] = -1;
|
|
}
|
|
}
|
|
if (index == -1) return;
|
|
|
|
gizmoDrawers[index].drawers.Add(item);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get an empty builder for queuing drawing commands.
|
|
///
|
|
/// <code>
|
|
/// // Create a new CommandBuilder
|
|
/// using (var draw = DrawingManager.GetBuilder()) {
|
|
/// // Use the exact same API as the global Draw class
|
|
/// draw.WireBox(Vector3.zero, Vector3.one);
|
|
/// }
|
|
/// </code>
|
|
/// See: <see cref="Drawing.CommandBuilder"/>
|
|
/// </summary>
|
|
/// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.
|
|
/// If false, it will only be rendered in the editor when gizmos are enabled.</param>
|
|
public static CommandBuilder GetBuilder(bool renderInGame = false) => instance.gizmos.GetBuilder(renderInGame);
|
|
|
|
/// <summary>
|
|
/// Get an empty builder for queuing drawing commands.
|
|
///
|
|
/// See: <see cref="Drawing.CommandBuilder"/>
|
|
/// </summary>
|
|
/// <param name="redrawScope">Scope for this command builder. See #GetRedrawScope.</param>
|
|
/// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.
|
|
/// If false, it will only be rendered in the editor when gizmos are enabled.</param>
|
|
public static CommandBuilder GetBuilder(RedrawScope redrawScope, bool renderInGame = false) => instance.gizmos.GetBuilder(redrawScope, renderInGame);
|
|
|
|
/// <summary>
|
|
/// Get an empty builder for queuing drawing commands.
|
|
/// TODO: Example usage.
|
|
///
|
|
/// See: <see cref="Drawing.CommandBuilder"/>
|
|
/// </summary>
|
|
/// <param name="hasher">Hash of whatever inputs you used to generate the drawing data.</param>
|
|
/// <param name="redrawScope">Scope for this command builder. See #GetRedrawScope.</param>
|
|
/// <param name="renderInGame">If true, this builder will be rendered in standalone games and in the editor even if gizmos are disabled.</param>
|
|
public static CommandBuilder GetBuilder(DrawingData.Hasher hasher, RedrawScope redrawScope = default, bool renderInGame = false) => instance.gizmos.GetBuilder(hasher, redrawScope, renderInGame);
|
|
|
|
/// <summary>
|
|
/// A scope which can be used to draw things over multiple frames.
|
|
///
|
|
/// You can use <see cref="GetBuilder(RedrawScope,bool)"/> to get a builder with a given redraw scope.
|
|
/// Everything drawn using the redraw scope will be drawn every frame until the redraw scope is disposed.
|
|
///
|
|
/// <code>
|
|
/// private RedrawScope redrawScope;
|
|
///
|
|
/// void Start () {
|
|
/// redrawScope = DrawingManager.GetRedrawScope();
|
|
/// using (var builder = DrawingManager.GetBuilder(redrawScope)) {
|
|
/// builder.WireSphere(Vector3.zero, 1.0f, Color.red);
|
|
/// }
|
|
/// }
|
|
///
|
|
/// void OnDestroy () {
|
|
/// redrawScope.Dispose();
|
|
/// }
|
|
/// </code>
|
|
/// </summary>
|
|
/// <param name="associatedGameObject">If not null, the scope will only be drawn if gizmos for the associated GameObject are drawn.
|
|
/// This is useful in the unity editor when e.g. opening a prefab in isolation mode, to disable redraw scopes for objects outside the prefab. Has no effect in standalone builds.</param>
|
|
public static RedrawScope GetRedrawScope (GameObject associatedGameObject = null) {
|
|
var scope = new RedrawScope(instance.gizmos);
|
|
scope.DrawUntilDispose(associatedGameObject);
|
|
return scope;
|
|
}
|
|
}
|
|
}
|