//---------------------------------------------- // NGUI: Next-Gen UI kit // Copyright © 2011-2015 Tasharen Entertainment //---------------------------------------------- //#define SHOW_HIDDEN_OBJECTS using UnityEngine; using System.Collections.Generic; /// /// This is an internally-created script used by the UI system. You shouldn't be attaching it manually. /// [ExecuteInEditMode] [AddComponentMenu("NGUI/Internal/Draw Call")] public class UIDrawCall : MonoBehaviour { static BetterList mActiveList = new BetterList(); static BetterList mInactiveList = new BetterList(); [System.Obsolete("Use UIDrawCall.activeList")] static public BetterList list { get { return mActiveList; } } /// /// List of active draw calls. /// static public BetterList activeList { get { return mActiveList; } } /// /// List of inactive draw calls. Only used at run-time in order to avoid object creation/destruction. /// static public BetterList inactiveList { get { return mInactiveList; } } public enum Clipping : int { None = 0, TextureMask = 1, // Clipped using a texture rather than math SoftClip = 3, // Alpha-based clipping with a softened edge ConstrainButDontClip = 4, // No actual clipping, but does have an area } [HideInInspector][System.NonSerialized] public int widgetCount = 0; [HideInInspector][System.NonSerialized] public int depthStart = int.MaxValue; [HideInInspector][System.NonSerialized] public int depthEnd = int.MinValue; [HideInInspector][System.NonSerialized] public UIPanel manager; [HideInInspector][System.NonSerialized] public UIPanel panel; [HideInInspector][System.NonSerialized] public Texture2D clipTexture; [HideInInspector][System.NonSerialized] public bool alwaysOnScreen = false; [HideInInspector][System.NonSerialized] public BetterList verts = new BetterList(); [HideInInspector][System.NonSerialized] public BetterList norms = new BetterList(); [HideInInspector][System.NonSerialized] public BetterList tans = new BetterList(); [HideInInspector][System.NonSerialized] public BetterList uvs = new BetterList(); [HideInInspector][System.NonSerialized] public BetterList cols = new BetterList(); Material mMaterial; // Material used by this draw call Texture mTexture; // Main texture used by the material Shader mShader; // Shader used by the dynamically created material int mClipCount = 0; // Number of times the draw call's content is getting clipped Transform mTrans; // Cached transform Mesh mMesh; // First generated mesh MeshFilter mFilter; // Mesh filter for this draw call MeshRenderer mRenderer; // Mesh renderer for this screen Material mDynamicMat; // Instantiated material int[] mIndices; // Cached indices bool mRebuildMat = true; bool mLegacyShader = false; int mRenderQueue = 3000; int mTriangles = 0; /// /// Whether the draw call has changed recently. /// [System.NonSerialized] public bool isDirty = false; [System.NonSerialized] bool mTextureClip = false; public delegate void OnRenderCallback (Material mat); /// /// Callback that will be triggered at OnWillRenderObject() time. /// public OnRenderCallback onRender; /// /// Render queue used by the draw call. /// public int renderQueue { get { return mRenderQueue; } set { if (mRenderQueue != value) { mRenderQueue = value; if (mDynamicMat != null) { mDynamicMat.renderQueue = value; #if UNITY_EDITOR if (mRenderer != null) mRenderer.enabled = isActive; #endif } } } } /// /// Renderer's sorting order, to be used with Unity's 2D system. /// public int sortingOrder { get { return (mRenderer != null) ? mRenderer.sortingOrder : 0; } set { if (mRenderer != null && mRenderer.sortingOrder != value) mRenderer.sortingOrder = value; } } /// /// Final render queue used to draw the draw call's geometry. /// public int finalRenderQueue { get { return (mDynamicMat != null) ? mDynamicMat.renderQueue : mRenderQueue; } } #if UNITY_EDITOR /// /// Whether the draw call is currently active. /// public bool isActive { get { return mActive; } set { if (mActive != value) { mActive = value; if (mRenderer != null) { mRenderer.enabled = value; NGUITools.SetDirty(gameObject); } } } } bool mActive = true; #endif /// /// Transform is cached for speed and efficiency. /// public Transform cachedTransform { get { if (mTrans == null) mTrans = transform; return mTrans; } } /// /// Material used by this screen. /// public Material baseMaterial { get { return mMaterial; } set { if (mMaterial != value) { mMaterial = value; mRebuildMat = true; } } } /// /// Dynamically created material used by the draw call to actually draw the geometry. /// public Material dynamicMaterial { get { return mDynamicMat; } } /// /// Texture used by the material. /// public Texture mainTexture { get { return mTexture; } set { mTexture = value; if (mDynamicMat != null) mDynamicMat.mainTexture = value; } } /// /// Shader used by the material. /// public Shader shader { get { return mShader; } set { if (mShader != value) { mShader = value; mRebuildMat = true; } } } /// /// The number of triangles in this draw call. /// public int triangles { get { return (mMesh != null) ? mTriangles : 0; } } /// /// Whether the draw call is currently using a clipped shader. /// public bool isClipped { get { return mClipCount != 0; } } /// /// Create an appropriate material for the draw call. /// void CreateMaterial () { mTextureClip = false; mLegacyShader = false; mClipCount = panel.clipCount; string shaderName = (mShader != null) ? mShader.name : ((mMaterial != null) ? mMaterial.shader.name : "Unlit/Transparent Colored"); // Figure out the normal shader's name shaderName = shaderName.Replace("GUI/Text Shader", "Unlit/Text"); if (shaderName.Length > 2) { if (shaderName[shaderName.Length - 2] == ' ') { int index = shaderName[shaderName.Length - 1]; if (index > '0' && index <= '9') shaderName = shaderName.Substring(0, shaderName.Length - 2); } } if (shaderName.StartsWith("Hidden/")) shaderName = shaderName.Substring(7); // Legacy functionality const string soft = " (SoftClip)"; shaderName = shaderName.Replace(soft, ""); const string textureClip = " (TextureClip)"; shaderName = shaderName.Replace(textureClip, ""); if (panel.clipping == Clipping.TextureMask) { mTextureClip = true; shader = Shader.Find("Hidden/" + shaderName + textureClip); } else if (mClipCount != 0) { shader = Shader.Find("Hidden/" + shaderName + " " + mClipCount); if (shader == null) shader = Shader.Find(shaderName + " " + mClipCount); // Legacy functionality if (shader == null && mClipCount == 1) { mLegacyShader = true; shader = Shader.Find(shaderName + soft); } } else shader = Shader.Find(shaderName); // Always fallback to the default shader if (shader == null) shader = Shader.Find("Unlit/Transparent Colored"); if (mMaterial != null) { mDynamicMat = new Material(mMaterial); mDynamicMat.name = "[NGUI] " + mMaterial.name; mDynamicMat.hideFlags = HideFlags.DontSave | HideFlags.NotEditable; mDynamicMat.CopyPropertiesFromMaterial(mMaterial); #if !UNITY_FLASH string[] keywords = mMaterial.shaderKeywords; for (int i = 0; i < keywords.Length; ++i) mDynamicMat.EnableKeyword(keywords[i]); #endif // If there is a valid shader, assign it to the custom material if (shader != null) { mDynamicMat.shader = shader; } else if (mClipCount != 0) { Debug.LogError(shaderName + " shader doesn't have a clipped shader version for " + mClipCount + " clip regions"); } } else { mDynamicMat = new Material(shader); mDynamicMat.name = "[NGUI] " + shader.name; mDynamicMat.hideFlags = HideFlags.DontSave | HideFlags.NotEditable; } } /// /// Rebuild the draw call's material. /// Material RebuildMaterial () { // Destroy the old material NGUITools.DestroyImmediate(mDynamicMat); // Create a new material CreateMaterial(); mDynamicMat.renderQueue = mRenderQueue; // Assign the main texture if (mTexture != null) mDynamicMat.mainTexture = mTexture; // Update the renderer if (mRenderer != null) mRenderer.sharedMaterials = new Material[] { mDynamicMat }; return mDynamicMat; } /// /// Update the renderer's materials. /// void UpdateMaterials () { // If clipping should be used, we need to find a replacement shader if (mRebuildMat || mDynamicMat == null || mClipCount != panel.clipCount || mTextureClip != (panel.clipping == Clipping.TextureMask)) { RebuildMaterial(); mRebuildMat = false; } else if (mRenderer.sharedMaterial != mDynamicMat) { #if UNITY_EDITOR Debug.LogError("Hmm... This point got hit!"); #endif mRenderer.sharedMaterials = new Material[] { mDynamicMat }; } } /// /// Set the draw call's geometry. /// public void UpdateGeometry (int widgetCount) { this.widgetCount = widgetCount; int count = verts.size; // Safety check to ensure we get valid values if (count > 0 && (count == uvs.size && count == cols.size) && (count % 4) == 0) { // Cache all components if (mFilter == null) mFilter = gameObject.GetComponent(); if (mFilter == null) mFilter = gameObject.AddComponent(); if (verts.size < 65000) { // Populate the index buffer int indexCount = (count >> 1) * 3; bool setIndices = (mIndices == null || mIndices.Length != indexCount); // Create the mesh if (mMesh == null) { mMesh = new Mesh(); mMesh.hideFlags = HideFlags.DontSave; mMesh.name = (mMaterial != null) ? "[NGUI] " + mMaterial.name : "[NGUI] Mesh"; mMesh.MarkDynamic(); setIndices = true; } #if !UNITY_FLASH // If the buffer length doesn't match, we need to trim all buffers bool trim = (uvs.buffer.Length != verts.buffer.Length) || (cols.buffer.Length != verts.buffer.Length) || (norms.buffer != null && norms.buffer.Length != verts.buffer.Length) || (tans.buffer != null && tans.buffer.Length != verts.buffer.Length); // Non-automatic render queues rely on Z position, so it's a good idea to trim everything if (!trim && panel.renderQueue != UIPanel.RenderQueue.Automatic) trim = (mMesh == null || mMesh.vertexCount != verts.buffer.Length); // NOTE: Apparently there is a bug with Adreno devices: // http://www.tasharen.com/forum/index.php?topic=8415.0 #if !UNITY_ANDROID // If the number of vertices in the buffer is less than half of the full buffer, trim it if (!trim && (verts.size << 1) < verts.buffer.Length) trim = true; #endif mTriangles = (verts.size >> 1); if (trim || verts.buffer.Length > 65000) { if (trim || mMesh.vertexCount != verts.size) { mMesh.Clear(); setIndices = true; } mMesh.vertices = verts.ToArray(); mMesh.uv = uvs.ToArray(); mMesh.colors32 = cols.ToArray(); if (norms != null) mMesh.normals = norms.ToArray(); if (tans != null) mMesh.tangents = tans.ToArray(); } else { if (mMesh.vertexCount != verts.buffer.Length) { mMesh.Clear(); setIndices = true; } mMesh.vertices = verts.buffer; mMesh.uv = uvs.buffer; mMesh.colors32 = cols.buffer; if (norms != null) mMesh.normals = norms.buffer; if (tans != null) mMesh.tangents = tans.buffer; } #else mTriangles = (verts.size >> 1); if (mMesh.vertexCount != verts.size) { mMesh.Clear(); setIndices = true; } mMesh.vertices = verts.ToArray(); mMesh.uv = uvs.ToArray(); mMesh.colors32 = cols.ToArray(); if (norms != null) mMesh.normals = norms.ToArray(); if (tans != null) mMesh.tangents = tans.ToArray(); #endif if (setIndices) { mIndices = GenerateCachedIndexBuffer(count, indexCount); mMesh.triangles = mIndices; } #if !UNITY_FLASH if (trim || !alwaysOnScreen) #endif mMesh.RecalculateBounds(); mFilter.mesh = mMesh; } else { mTriangles = 0; if (mFilter.mesh != null) mFilter.mesh.Clear(); Debug.LogError("Too many vertices on one panel: " + verts.size); } if (mRenderer == null) mRenderer = gameObject.GetComponent(); if (mRenderer == null) { mRenderer = gameObject.AddComponent(); #if UNITY_EDITOR mRenderer.enabled = isActive; #endif } UpdateMaterials(); } else { if (mFilter.mesh != null) mFilter.mesh.Clear(); Debug.LogError("UIWidgets must fill the buffer with 4 vertices per quad. Found " + count); } verts.Clear(); uvs.Clear(); cols.Clear(); norms.Clear(); tans.Clear(); } const int maxIndexBufferCache = 10; #if UNITY_FLASH List mCache = new List(maxIndexBufferCache); #else static List mCache = new List(maxIndexBufferCache); #endif /// /// Generates a new index buffer for the specified number of vertices (or reuses an existing one). /// int[] GenerateCachedIndexBuffer (int vertexCount, int indexCount) { for (int i = 0, imax = mCache.Count; i < imax; ++i) { int[] ids = mCache[i]; if (ids != null && ids.Length == indexCount) return ids; } int[] rv = new int[indexCount]; int index = 0; for (int i = 0; i < vertexCount; i += 4) { rv[index++] = i; rv[index++] = i + 1; rv[index++] = i + 2; rv[index++] = i + 2; rv[index++] = i + 3; rv[index++] = i; } if (mCache.Count > maxIndexBufferCache) mCache.RemoveAt(0); mCache.Add(rv); return rv; } /// /// This function is called when it's clear that the object will be rendered. /// We want to set the shader used by the material, creating a copy of the material in the process. /// We also want to update the material's properties before it's actually used. /// void OnWillRenderObject () { UpdateMaterials(); if (onRender != null) onRender(mDynamicMat ?? mMaterial); if (mDynamicMat == null || mClipCount == 0) return; if (mTextureClip) { Vector4 cr = panel.drawCallClipRange; Vector2 soft = panel.clipSoftness; Vector2 sharpness = new Vector2(1000.0f, 1000.0f); if (soft.x > 0f) sharpness.x = cr.z / soft.x; if (soft.y > 0f) sharpness.y = cr.w / soft.y; mDynamicMat.SetVector(ClipRange[0], new Vector4(-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w)); mDynamicMat.SetTexture("_ClipTex", clipTexture); } else if (!mLegacyShader) { UIPanel currentPanel = panel; for (int i = 0; currentPanel != null; ) { if (currentPanel.hasClipping) { float angle = 0f; Vector4 cr = currentPanel.drawCallClipRange; // Clipping regions past the first one need additional math if (currentPanel != panel) { Vector3 pos = currentPanel.cachedTransform.InverseTransformPoint(panel.cachedTransform.position); cr.x -= pos.x; cr.y -= pos.y; Vector3 v0 = panel.cachedTransform.rotation.eulerAngles; Vector3 v1 = currentPanel.cachedTransform.rotation.eulerAngles; Vector3 diff = v1 - v0; diff.x = NGUIMath.WrapAngle(diff.x); diff.y = NGUIMath.WrapAngle(diff.y); diff.z = NGUIMath.WrapAngle(diff.z); if (Mathf.Abs(diff.x) > 0.001f || Mathf.Abs(diff.y) > 0.001f) Debug.LogWarning("Panel can only be clipped properly if X and Y rotation is left at 0", panel); angle = diff.z; } // Pass the clipping parameters to the shader SetClipping(i++, cr, currentPanel.clipSoftness, angle); } currentPanel = currentPanel.parentPanel; } } else // Legacy functionality { Vector2 soft = panel.clipSoftness; Vector4 cr = panel.drawCallClipRange; Vector2 v0 = new Vector2(-cr.x / cr.z, -cr.y / cr.w); Vector2 v1 = new Vector2(1f / cr.z, 1f / cr.w); Vector2 sharpness = new Vector2(1000.0f, 1000.0f); if (soft.x > 0f) sharpness.x = cr.z / soft.x; if (soft.y > 0f) sharpness.y = cr.w / soft.y; mDynamicMat.mainTextureOffset = v0; mDynamicMat.mainTextureScale = v1; mDynamicMat.SetVector("_ClipSharpness", sharpness); } } static int[] ClipRange = null; static int[] ClipArgs = null; /// /// Set the shader clipping parameters. /// void SetClipping (int index, Vector4 cr, Vector2 soft, float angle) { angle *= -Mathf.Deg2Rad; Vector2 sharpness = new Vector2(1000.0f, 1000.0f); if (soft.x > 0f) sharpness.x = cr.z / soft.x; if (soft.y > 0f) sharpness.y = cr.w / soft.y; if (index < ClipRange.Length) { mDynamicMat.SetVector(ClipRange[index], new Vector4(-cr.x / cr.z, -cr.y / cr.w, 1f / cr.z, 1f / cr.w)); mDynamicMat.SetVector(ClipArgs[index], new Vector4(sharpness.x, sharpness.y, Mathf.Sin(angle), Mathf.Cos(angle))); } } /// /// Cache the property IDs. /// void Awake () { if (ClipRange == null) { ClipRange = new int[] { Shader.PropertyToID("_ClipRange0"), Shader.PropertyToID("_ClipRange1"), Shader.PropertyToID("_ClipRange2"), Shader.PropertyToID("_ClipRange4"), }; } if (ClipArgs == null) { ClipArgs = new int[] { Shader.PropertyToID("_ClipArgs0"), Shader.PropertyToID("_ClipArgs1"), Shader.PropertyToID("_ClipArgs2"), Shader.PropertyToID("_ClipArgs3"), }; } } /// /// The material should be rebuilt when the draw call is enabled. /// void OnEnable () { mRebuildMat = true; } /// /// Clear all references. /// void OnDisable () { depthStart = int.MaxValue; depthEnd = int.MinValue; panel = null; manager = null; mMaterial = null; mTexture = null; clipTexture = null; if (mRenderer != null) mRenderer.sharedMaterials = new Material[] {}; NGUITools.DestroyImmediate(mDynamicMat); mDynamicMat = null; } /// /// Cleanup. /// void OnDestroy () { NGUITools.DestroyImmediate(mMesh); mMesh = null; } /// /// Return an existing draw call. /// static public UIDrawCall Create (UIPanel panel, Material mat, Texture tex, Shader shader) { #if UNITY_EDITOR string name = null; if (tex != null) name = tex.name; else if (shader != null) name = shader.name; else if (mat != null) name = mat.name; return Create(name, panel, mat, tex, shader); #else return Create(null, panel, mat, tex, shader); #endif } /// /// Create a new draw call, reusing an old one if possible. /// static UIDrawCall Create (string name, UIPanel pan, Material mat, Texture tex, Shader shader) { UIDrawCall dc = Create(name); dc.gameObject.layer = pan.cachedGameObject.layer; dc.baseMaterial = mat; dc.mainTexture = tex; dc.shader = shader; dc.renderQueue = pan.startingRenderQueue; dc.sortingOrder = pan.sortingOrder; dc.manager = pan; return dc; } /// /// Create a new draw call, reusing an old one if possible. /// static UIDrawCall Create (string name) { #if SHOW_HIDDEN_OBJECTS && UNITY_EDITOR name = (name != null) ? "_UIDrawCall [" + name + "]" : "DrawCall"; #endif if (mInactiveList.size > 0) { UIDrawCall dc = mInactiveList.Pop(); mActiveList.Add(dc); if (name != null) dc.name = name; NGUITools.SetActive(dc.gameObject, true); return dc; } #if UNITY_EDITOR // If we're in the editor, create the game object with hide flags set right away GameObject go = UnityEditor.EditorUtility.CreateGameObjectWithHideFlags(name, #if SHOW_HIDDEN_OBJECTS HideFlags.DontSave | HideFlags.NotEditable, typeof(UIDrawCall)); #else HideFlags.HideAndDontSave, typeof(UIDrawCall)); #endif UIDrawCall newDC = go.GetComponent(); #else GameObject go = new GameObject(name); DontDestroyOnLoad(go); UIDrawCall newDC = go.AddComponent(); #endif // Create the draw call mActiveList.Add(newDC); return newDC; } /// /// Clear all draw calls. /// static public void ClearAll () { bool playing = Application.isPlaying; for (int i = mActiveList.size; i > 0; ) { UIDrawCall dc = mActiveList[--i]; if (dc) { if (playing) NGUITools.SetActive(dc.gameObject, false); else NGUITools.DestroyImmediate(dc.gameObject); } } mActiveList.Clear(); } /// /// Immediately destroy all draw calls. /// static public void ReleaseAll () { ClearAll(); ReleaseInactive(); } /// /// Immediately destroy all inactive draw calls (draw calls that have been recycled and are waiting to be re-used). /// static public void ReleaseInactive() { for (int i = mInactiveList.size; i > 0; ) { UIDrawCall dc = mInactiveList[--i]; if (dc) NGUITools.DestroyImmediate(dc.gameObject); } mInactiveList.Clear(); } /// /// Count all draw calls managed by the specified panel. /// static public int Count (UIPanel panel) { int count = 0; for (int i = 0; i < mActiveList.size; ++i) if (mActiveList[i].manager == panel) ++count; return count; } /// /// Destroy the specified draw call. /// static public void Destroy (UIDrawCall dc) { if (dc) { dc.onRender = null; if (Application.isPlaying) { if (mActiveList.Remove(dc)) { NGUITools.SetActive(dc.gameObject, false); mInactiveList.Add(dc); } } else { mActiveList.Remove(dc); NGUITools.DestroyImmediate(dc.gameObject); } } } }