NGUI源码解析之UIPanel(一)UIDrawcall 调用

文章说明

本篇文章基于NGUI (3.12.0)版本源码下的代码分析,如果代码和大家自己的不同,可能是版本不同。如果文章中分析有误希望大神看见指点迷津,大家好,我就是一个勤勤恳恳爱偷懒的码农,希望和大家一起学习研究。

今天来给大家分享 NGUI 的 UIPanel 源码,主要分享 UIPanel 中关于 UIDrawcall 调用相关部分,如果你之前了解过 “unity 性能优化相关” 东西的话 ,那么其中与 unity 性能相关之 drawcall 优化的主要部分就是在这里,当然了前提是你们的 UI 底层使用的是 NGUI,如果使用的是UGUI,就要去UGUI 源码中分析如何优化 drawcall 。

源码分析

本篇文章主要分享 和 UIDrawcall 调用相关的源码部分,剩下的比如,裁剪,锚点等,放到后面文章中分享哈。前面关于 UIDrawcall 的源码分享中,我提到过几个思考问题:

谁创建的 UIDrawcall ,什么时候创建?
谁调用的 UIDrawcall ,什么时候调用?
谁移除的 UIDrawcall ,什么时候移除?

今天就从这些问题去源码中去寻找答案。

脚本初始化流程

首先要去 UIPanel 的父类 “UIRect” 中去研究一下它的初始化逻辑,接下来我给大家梳理下,UIPanel 初始化主要分为两种情况:

  • mStarted == false 时:Awake -----> OnEnable ----> Start —> 调用 OnInit 和 OnStart,将 mStarted 置为 true
  • mStarted == true 时:OnEnable ----> 调用 OnInit 和 OnStart

(mStarted 存储在 UIRect 中, 初始化 是 “false”,从源码中看,是用来标记 Start 方法是否执行的标志。)

如下是 UIRect 相关源码:

	protected virtual void Awake ()
	{
		mStarted = false;
		mGo = gameObject;
		mTrans = transform;
	}
	
	protected void Start ()
	{
		mStarted = true;
		OnInit();
		OnStart();
	}
	
	protected virtual void OnEnable ()
	{
#if UNITY_EDITOR
		mEnabled = true;
#endif
		mUpdateFrame = -1;
		
		if (updateAnchors == AnchorUpdate.OnEnable)
		{
			mAnchorsCached = false;
			mUpdateAnchors = true;
		}
		if (mStarted) OnInit();
		mUpdateFrame = -1;
	}

重要属性

UIPanel 中一些重点属性如下:

//UIPanel 中有一个静态变量 list,保存当前项目中创建的所有的 UIPanel 组件。
static public List<UIPanel> list = new List<UIPanel>();  //所有的 UIPanel 都会被放到这个列表中

//这个 UIPanel 管理的 uiwidget 列表,注意 uisprite uitexture 等都是 uiwidget
public List<UIWidget> widgets = new List<UIWidget>();

//这个 UIPanel 创建的 draw call 列表
public List<UIDrawCall> drawCalls = new List<UIDrawCall>();

//缓存的裁剪范围
public Vector4 drawCallClipRange = new Vector4(0f, 0f, 1f, 1f);

UIDrawCall.Clipping mClipping = UIDrawCall.Clipping.None;  //裁剪类型

 //默认的  SoftClip 模式下的坐标(中心x,中心y,宽度,高度) 
Vector4 mClipRange = new Vector4(0f, 0f, 300f, 200f); 

// x表示内矩形距离外矩形左右两边的距离,y表示内矩形距离外矩形上下两边的距离
Vector2 mClipSoftness = new Vector2(4f, 4f);  

int mDepth = 0;  //深度

int mSortingOrder = 0;  //渲染排序

bool mRebuild = false;  //用于是否重建所有的 drawcall

UIPanel 创建

OnInit():每次激活这个 UIPanel 组件时,OnEnable 执行完后,都会调用这个方法,通过执行 “list.Add(this)” 将自己加入到 list 列表中保存起来。

源码:

	// 当走过 Start 后,每次调用 OnEnable 都会调用这个 函数
	protected override void OnInit ()
	{
		if (list.Contains(this)) return;
		base.OnInit();
		//查找自己父节点中有没有UIPanel,有就设置为父节点,没有就设置为null
		FindParent();

		// Apparently having a rigidbody helps
#if UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7
		if (rigidbody == null && mParentPanel == null)
#else
		if (GetComponent<Rigidbody>() == null && mParentPanel == null)
#endif
		{
			UICamera uic = (anchorCamera != null) ? mCam.GetComponent<UICamera>() : null;

			if (uic != null)
			{
				if (uic.eventType == UICamera.EventType.UI_3D || uic.eventType == UICamera.EventType.World_3D)
				{
					Rigidbody rb = gameObject.AddComponent<Rigidbody>();
					rb.isKinematic = true;
					rb.useGravity = false;
				}
				// It's unclear if this helps 2D physics or not, so leaving it disabled for now.
				// Note that when enabling this, the 'if (rigidbody == null)' statement above should be adjusted as well.
				//else
				//{
				//    Rigidbody2D rb = gameObject.AddComponent<Rigidbody2D>();
				//    rb.isKinematic = true;
				//}
			}
		}
		//重建 drawcall,每次重新激活 UIPanel 都会重建所有的 drawcall
		mRebuild = true;
		mAlphaFrameID = -1;
		mMatrixFrame = -1;

		//每次创建一个新的uipanel,都会被添加到这个静态数组中,并且进行所有的 panel 排序
		list.Add(this);
		//panel 按照 mDepth 深度按照升序排列
		list.Sort(CompareFunc);
	}

	protected override void OnEnable ()
	{
		mRebuild = true;    //每次重新激活这个对象后,会重置 drawcall
		mAlphaFrameID = -1;
		mMatrixFrame = -1;
		OnStart();
		base.OnEnable();
		mMatrixFrame = -1;
	}

UIPanel 更新

这里面是整个 UIPanel 创建和调用 UIDrawcall 的核心代码,我们一个一个分析:

LateUpdate() : 每一帧调用 list 更新所有正在使用的 UIPanel, 做两件事:

  • UpdateSelf 更新 panel,所有它的所有 uiwidget ,设置 drawcall 相关信息
  • UpdateDrawCalls 设置渲染循序

源码:

	void LateUpdate ()
	{
#if UNITY_EDITOR && !UNITY_5_5_OR_NEWER
		if (mUpdateFrame != Time.frameCount || !Application.isPlaying)
#else
		if (mUpdateFrame != Time.frameCount)
#endif
		{
			mUpdateFrame = Time.frameCount;

			// Update each panel in order
			//调用每个panel 的 UpdateSelf 方法,更新自己信息
			for (int i = 0, imax = list.Count; i < imax; ++i)
				list[i].UpdateSelf();

			int rq = 3000;

			// Update all draw calls, making them draw in the right order
			// 更新所有 panel 中的 drawcall 绘制调用,使它们以正确的顺序绘制,ui的起点都是 3000
			// 主要设置渲染顺序
			for (int i = 0, imax = list.Count; i < imax; ++i)
			{
				UIPanel p = list[i];

				if (p.renderQueue == RenderQueue.Automatic)
				{
					p.startingRenderQueue = rq;
					p.UpdateDrawCalls(i);
					rq += p.drawCalls.Count;
				}
				else if (p.renderQueue == RenderQueue.StartAt)
				{
					p.UpdateDrawCalls(i);
					if (p.drawCalls.Count != 0)
						rq = Mathf.Max(rq, p.startingRenderQueue + p.drawCalls.Count);
				}
				else // Explicit
				{
					p.UpdateDrawCalls(i);
					if (p.drawCalls.Count != 0)
						rq = Mathf.Max(rq, p.startingRenderQueue + 1);
				}
			}
		}
	}

	/// 更新 panel,所有它的所有 uiwidget 和 drawcall
	void UpdateSelf ()
	{
		// 每一帧需要,判断物体是否是一个静态物体,如果是,这个就是false,如果是运动的,为true
		mHasMoved = cachedTransform.hasChanged; 
		//更新矩阵变化
		UpdateTransformMatrix();
		//更新所有layer
		UpdateLayers();
		//更新所有的widget
		UpdateWidgets();

		//是否重建,重建是重建所有panel下的widget的drawcall
		if (mRebuild)
		{
			//重建所有的DrawCall (这里就是性能优化重点!减少不必要的重建)
			mRebuild = false;
			FillAllDrawCalls();
		}
		else
		{
			//判断哪个drawcall 被污染了,就需要重新去调用这个drawcall绘制图像
			for (int i = 0; i < drawCalls.Count; )
			{
				UIDrawCall dc = drawCalls[i];
				//如果这个 drawcall isDirty 为true,就需要调用FillDrawCall调用当前drawcall去重绘图形
				if (dc.isDirty && !FillDrawCall(dc))
				{
					UIDrawCall.Destroy(dc);
					drawCalls.RemoveAt(i);
					continue;
				}
				++i;
			}
		}

		//每添加一个 uiwidget 就要更新一次
		if (mUpdateScroll)
		{
			mUpdateScroll = false;
			UIScrollView sv = GetComponent<UIScrollView>();
			if (sv != null) sv.UpdateScrollbars();
		}

		if (mHasMoved)
		{
			mHasMoved = false;
			mTrans.hasChanged = false;
		}
	}
	

UpdateTransformMatrix():更新矩阵,这个主要和裁剪相关,需要计算一下裁剪的区域,在看源码前先看下它的设计原理

在这里插入图片描述
再来看源码就全部理解了:

	void UpdateTransformMatrix ()
	{
		int fc = Time.frameCount;

		//每一帧或者运动的物体要更新
		if (mHasMoved || mMatrixFrame != fc)
		{
			mMatrixFrame = fc;
			//将当前挂载这个组件的点,从世界坐标转化成局部坐标的矩阵
			worldToLocal = cachedTransform.worldToLocalMatrix;

			//* 0.5 是为了计算显示区域的一半宽度,一般高度
			Vector2 size = GetViewSize() * 0.5f;
			//计算裁剪区域的中心坐标
			float x = mClipOffset.x + mClipRange.x;
			float y = mClipOffset.y + mClipRange.y;

			mMin.x = x - size.x;  //计算左边中心点的坐标
			mMin.y = y - size.y;  //计算下边中心点的坐标
			mMax.x = x + size.x;  //计算右边中心点的坐标
			mMax.y = y + size.y;  //计算上边中心点的坐标
		}
	}

UpdateLayers():如果panel 的 layer 发生变化就要更新所有的 wideget 的layers

源码:

	void UpdateLayers ()
	{
		// Always move widgets to the panel's layer
		if (mLayer != cachedGameObject.layer)
		{
			mLayer = mGo.layer;

			for (int i = 0, imax = widgets.Count; i < imax; ++i)
			{
				UIWidget w = widgets[i];
				//保证 widget 的 layer 永远 == panel 的 layer
				if (w && w.parent == this) w.gameObject.layer = mLayer;
			}

			ResetAnchors();
			//更新所有 drawcall 的layer
			for (int i = 0; i < drawCalls.Count; ++i)
				drawCalls[i].gameObject.layer = mLayer;
		}
	}

UpdateWidgets():更新所有属于这个 panel 中的 widget 相关信息

源码:

	void UpdateWidgets()
	{
		bool changed = false;
		bool forceVisible = false;
		bool clipped = hasCumulativeClipping;

		if (!cullWhileDragging)
		{
			//对于scrolliew中在拖动时不触发选中 widget 的情况下,走这里
			for (int i = 0; i < UIScrollView.list.size; ++i)
			{
				UIScrollView sv = UIScrollView.list[i];
				if (sv.panel == this && sv.isDragging) forceVisible = true;
			}
		}

		if (mForced != forceVisible)
		{
			mForced = forceVisible;
			mResized = true;
		}

		// Update all widgets
		//更新所有的 widgets
		int frame = Time.frameCount;
		for (int i = 0, imax = widgets.Count; i < imax; ++i)
		{
			UIWidget w = widgets[i];

			// If the widget is visible, update it
			if (w.panel == this && w.enabled)
			{
#if UNITY_EDITOR
				// When an object is dragged from Project view to Scene view, its Z is...
				// odd, to say the least. Force it if possible.
				// 当一个对象从 Project 视图拖动到场景视图时,它的Z是旧的,在编辑器模式下,要做一些调整
				if (!Application.isPlaying)
				{
					Transform t = w.cachedTransform;

					if (t.hideFlags != HideFlags.HideInHierarchy)
					{
						t = (t.parent != null && t.parent.hideFlags == HideFlags.HideInHierarchy) ?
							t.parent : null;
					}

					if (t != null)
					{
						for (; ; )
						{
							if (t.parent == null) break;
							if (t.parent.hideFlags == HideFlags.HideInHierarchy) t = t.parent;
							else break;
						}

						if (t != null)
						{
							Vector3 pos = t.localPosition;
							pos.x = Mathf.Round(pos.x);
							pos.y = Mathf.Round(pos.y);
							pos.z = 0f;

							if (Vector3.SqrMagnitude(t.localPosition - pos) > 0.0001f)
								t.localPosition = pos;
						}
					}
				}
#endif
				// First update the widget's transform
				if (w.UpdateTransform(frame) || mResized || (mHasMoved && !alwaysOnScreen))
				{
					// Only proceed to checking the widget's visibility if it actually moved
					bool vis = forceVisible || (w.CalculateCumulativeAlpha(frame) > 0.001f);
					w.UpdateVisibility(vis, forceVisible || alwaysOnScreen || ((clipped || w.hideIfOffScreen) ? IsVisible(w) : true));
				}
				
				// Update the widget's geometry if necessary
				//更新这个 widget 里面的几何信息
				if (w.UpdateGeometry(frame))
				{
					changed = true;

					if (!mRebuild)
					{
						// Find an existing draw call, if possible
						//在widget 变化后,检查这个widget 是否有 drawcall ,如果有就置 isDirty 为true,如果没有就要调用 FindDrawCall 去找一个drawcall
						if (w.drawCall != null) w.drawCall.isDirty = true;
						else FindDrawCall(w);
					}
				}
			}
		}

		// Inform the changed event listeners
		if (changed && onGeometryUpdated != null) onGeometryUpdated();
		mResized = false;
	}

FillAllDrawCalls():重建所有 drawcall,这个就是最重要的部分,整个创建 UIDrawcall 的核心代码,性能优化之 drawcall 相关的部分主要就是这部分内容。

核心原理是:先给所有的 UIWidget 组件排序,按照深度,材质,等方式排好顺序后,开始创建 drawcall,如果材质,贴图,shader 相同的相邻 UIWidget 会使用同一个 drawcall ,否则就会重新创建一个新的 drawcall 加入到列表中。

这里先看一下 UIWidget 的排序规则,这段代码写在 “UIWidget” 类中,代码如下:

	//1.比较层级,从小到大
	//2.层级相同,比较材质,有材质的在前面
	//3.层级相同,都有材质,并且材质不同,比较预制的id,从小到大
	//因为:list 比较大小,按照升序来排列。0表示相等,-1表示小于,1表示大于
	//所以:如果 left > right reutrn 1, 表示 left 和 right 要交换,就是按照从小到大的顺序排列
	//		如果 left > right return -1, 表示 left 和 right 不交换,那么就是按照从大到小的顺序排列

	[System.Diagnostics.DebuggerHidden]
	[System.Diagnostics.DebuggerStepThrough]
	static public int PanelCompareFunc (UIWidget left, UIWidget right)
	{
		if (left.mDepth < right.mDepth) return -1;
		if (left.mDepth > right.mDepth) return 1;

		Material leftMat = left.material;
		Material rightMat = right.material;

		if (leftMat == rightMat) return 0;
		if (leftMat == null) return 1;
		if (rightMat == null) return -1;

		return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
	}

创建 drawcall 源码:

	void FillAllDrawCalls ()
	{
		//将上一次的所有 drawcall 信息删除
		for (int i = 0; i < drawCalls.Count; ++i)
			UIDrawCall.Destroy(drawCalls[i]);
		drawCalls.Clear();

		Material mat = null;  //缓存上一个 材质
		Texture tex = null;  //缓存上一个 贴图
		Shader sdr = null;   //缓存上一个 shader
		UIDrawCall dc = null;  //缓存上一个 drawcall
		int count = 0;

		//给所有 widget 排序
		if (mSortWidgets) SortWidgets();

		//注意这里是排好序的widget
		for (int i = 0; i < widgets.Count; ++i)
		{
			UIWidget w = widgets[i];

			//只重建激活的并且有顶点的 uiwidget,比如创建一个空节点,只挂在一个uiwidget 组件,并不会重建 drawcall 
			if (w.isVisible && w.hasVertices)
			{
				//这里是创建整个drawcall的核心代码
				Material mt = w.material;
				
				if (onCreateMaterial != null) mt = onCreateMaterial(w, mt);

				Texture tx = w.mainTexture;
				Shader sd = w.shader;

				//如果材质 ,贴图,shader 任意一个和上一个不同,就要将旧的dc添加进列表,并且创建一个新的drawcall
				//注意这里是比较连续的 widget
				if (mat != mt || tex != tx || sdr != sd)
				{
					//这里需要把上一个drawcall 添加进来,然后将 dc 置为null,方便下面创建一个新的dc
					if (dc != null && dc.verts.Count != 0)
					{
						drawCalls.Add(dc);
						dc.UpdateGeometry(count);
						dc.onRender = mOnRender;
						mOnRender = null;
						count = 0;
						dc = null;
					}

					mat = mt;
					tex = tx;
					sdr = sd;
				}

				if (mat != null || sdr != null || tex != null)
				{
					//这里判断是否需要创建一个新的drawcall
					if (dc == null)
					{
						dc = UIDrawCall.Create(this, mat, tex, sdr);
						dc.depthStart = w.depth;
						dc.depthEnd = dc.depthStart;
						dc.panel = this;
						dc.onCreateDrawCall = onCreateDrawCall;
					}
					else
					{
						//如果多个widget 共用同一个drawcall 那么需要更新这个drawcall depth 的范围
						int rd = w.depth;
						if (rd < dc.depthStart) dc.depthStart = rd;
						if (rd > dc.depthEnd) dc.depthEnd = rd;
					}
					//设置 widget 的drawcall
					w.drawCall = dc;

					++count;
					//将顶点,uv,颜色,法线等信息放入缓冲区
					if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans, generateUV2 ? dc.uv2 : null);
					else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null, generateUV2 ? dc.uv2 : null);

					if (w.mOnRender != null)
					{
						if (mOnRender == null) mOnRender = w.mOnRender;
						else mOnRender += w.mOnRender;
					}
				}
			}
			else w.drawCall = null;
		}

		//将最后一个创建的 drawcall 添加进来
		if (dc != null && dc.verts.Count != 0)
		{
			drawCalls.Add(dc);
			dc.UpdateGeometry(count);
			dc.onRender = mOnRender;
			mOnRender = null;
		}
	}

这里需要补充一个知识点:WriteToBuffers()

看了源码我们应该都知道,UIDrawcall 中开始准备绘制的核心代码接口就是 “UpdateGeometry” (核心方法,通过顶点,uv,颜色,贴图等信息绘制ui图形),那么这个方法中所需要的 顶点,UV,颜色等信息都是从哪里来的呢?

没错,就是从 “WriteToBuffers” 这个方法中来的。

“WriteToBuffers” 方法放在 “UIGeometry” 类中,这个类是一个辅助类,主要用于临时缓存一些几何信息,每次绘制 UI 时,都会将这些需要绘制的顶点等相关信息,通过这个接口设置到 UIDrawcall 的缓冲区中,然后调用 “UpdateGeometry” 设置,设置完成后,缓冲区又会将这些数据清楚掉,供下一次调用。

其他一些重要源码如下:

	///  为指定的绘制调用填充几何图形。
	public bool FillDrawCall (UIDrawCall dc)
	{
		if (dc != null)
		{
			dc.isDirty = false;
			int count = 0;  // 记录需要刷新的widget的个数
			//遍历所有 widget,寻找和dc 调用相同的 drawcall 
			for (int i = 0; i < widgets.Count; )
			{
				UIWidget w = widgets[i];

				if (w == null)
				{
#if UNITY_EDITOR
					Debug.LogError("This should never happen");
#endif
					widgets.RemoveAt(i);
					continue;
				}

				if (w.drawCall == dc)
				{
					if (w.isVisible && w.hasVertices)
					{
						++count;
						//重新将顶点,uv,颜色,法线等信息放入缓冲区
						if (generateNormals) w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, dc.norms, dc.tans, generateUV2 ? dc.uv2 : null);
						else w.WriteToBuffers(dc.verts, dc.uvs, dc.cols, null, null, generateUV2 ? dc.uv2 : null);

						if (w.mOnRender != null)
						{
							if (mOnRender == null) mOnRender = w.mOnRender;
							else mOnRender += w.mOnRender;
						}
					}
					//如果 widget 被 false 或者没有顶点信息后,将 dc 置为null
					else w.drawCall = null;
				}
				++i;
			}

			if (dc.verts.Count != 0)
			{
				//绘制ui图形
				dc.UpdateGeometry(count);
				dc.onRender = mOnRender;
				mOnRender = null;
				return true;
			}
		}
		return false;
	}

	//查找drawcall,如果一个 widget 没有drawcall 就要调用这个给它找一个,如果找不到,就要重建整个drawcall
	public UIDrawCall FindDrawCall (UIWidget w)
	{
		var mat = w.material;
		var tex = w.mainTexture;
		var shader = w.shader;
		var depth = w.depth;

		for (int i = 0; i < drawCalls.Count; ++i)
		{
			UIDrawCall dc = drawCalls[i];
			int dcStart = (i == 0) ? int.MinValue : drawCalls[i - 1].depthEnd + 1;
			int dcEnd = (i + 1 == drawCalls.Count) ? int.MaxValue : drawCalls[i + 1].depthStart - 1;
			//从这里可以看出,widget 的深度,材质,shader, texture 都会影响到是否需要重建
			if (dcStart <= depth && dcEnd >= depth)
			{
				if (dc.baseMaterial == mat && dc.shader == shader && dc.mainTexture == tex)
				{
					if (w.isVisible)
					{
						w.drawCall = dc;
						if (w.hasVertices) dc.isDirty = true;
						return dc;
					}
				}
				else mRebuild = true;
				return null;
			}
		}
		mRebuild = true;
		return null;
	}
	
	//添加一个 widget
	public void AddWidget (UIWidget w)
	{
		mUpdateScroll = true;

		if (widgets.Count == 0)
		{
			widgets.Add(w);
		}
		else if (mSortWidgets)
		{
			widgets.Add(w);
			SortWidgets();
		}
		else if (UIWidget.PanelCompareFunc(w, widgets[0]) == -1)
		{
			widgets.Insert(0, w);
		}
		else
		{
			for (int i = widgets.Count; i > 0; )
			{
				if (UIWidget.PanelCompareFunc(w, widgets[--i]) == -1) continue;
				widgets.Insert(i+1, w);
				break;
			}
		}
		FindDrawCall(w);
	}

	//删除一个 widget 
	public void RemoveWidget (UIWidget w)
	{
		if (widgets.Remove(w) && w.drawCall != null)
		{
			int depth = w.depth;
			//删除 widget 时,depth 会影响是否会被 重建全部 drawcall
			if (depth == w.drawCall.depthStart || depth == w.drawCall.depthEnd)
				mRebuild = true;

			w.drawCall.isDirty = true;
			w.drawCall = null;
		}
	}

UIPanel 销毁

OnDisable():每次禁用(false) 当前 UIPanel 对象时调用,通过执行 “list.Remove(this)” 将自己从 list 列表中删除,并且清空相关 UIDrawcall 列表。

源码:

	// false 掉当前节点对象
	protected override void OnDisable ()
	{
		//如果对象被 false 就会清除掉所有的 drawcall,等到下次对象被激活的时候在重建 drawcall
		for (int i = 0, imax = drawCalls.Count; i < imax; ++i)
		{
			UIDrawCall dc = drawCalls[i];
			if (dc != null) UIDrawCall.Destroy(dc);
		}
		
		drawCalls.Clear();
		//删除自己的 uipanel
		list.Remove(this);

		mAlphaFrameID = -1;
		mMatrixFrame = -1;
		
		if (list.Count == 0)
		{
			UIDrawCall.ReleaseAll();
			mUpdateFrame = -1;
		}
		base.OnDisable();
	}

结论

1.回答之前的思考题:

  1. 谁创建的 UIDrawcall,什么时候创建?
    答:UIPanel 创建的;
    每次在重建所有 UIDrawcall 的时候创建的,在 FillAllDrawCalls 方法中重建所有 Drawcall。

  2. 谁调用的 UIDrawcall,什么时候调用?
    答:UIPanel 调用的;
    UIPanel 通过调用 UIDrawcall 的 UpdateGeometry 方法,去重绘 UI 信息,主要在两种情况中调用:当重建所有的 UIDrawcall 的时候调用;UpdateSelf 中如果某个 Drawcall 的 isDirty 被设置为 true 时,就需要去检查是否需要重绘,如果需要,会调用 UpdateGeometry ;

  3. 谁移除的,什么时候移除?
    答:UIPanel 移除的;
    每次重建所有的 Drawcall 之前会先将原来的清除掉;
    禁用 当前 UIPanel 组件的时候,调用 OnDisable 中会清除当前 UIPanel 中创建的所有 Drawcall ;
    在 UpdateSelf 中更新的时候,如果某个 Drawcall 中 isDirty 为 true 时,并且没有需要重绘的信息,就将其移除。

2.思考:

思考:如何优化 UIDrawcall 的调用次数,减少 drawcall,优化性能?

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值