淺談 window 桌面 GUI 技術及圖像渲染性能測試實踐

      網(wǎng)友投稿 1025 2022-05-30

      Windows的圖形界面架構

      window圖像渲染的基本流程

      window桌面程序UI自動化測試技術

      淺談 window 桌面 GUI 技術及圖像渲染性能測試實踐

      Win32程序

      WPF程序

      UIAutomation

      Windows的圖形界面架構

      window圖像渲染的基本流程

      window桌面程序UI自動化測試技術

      Win32程序

      WPF程序

      UIAutomation

      桌面程序圖像渲染性能測試實踐

      普通PC桌面WPF應用

      大屏幕可視化WPF應用

      Windows的圖形界面架構

      從Windows Vista之后,Desktop composition的部分就由Desktop Window Manager完成了(當然是啟用Aero的情況下,Windows 8起DWM是必須開啟的

      如上圖,應用程序畫完了界面,告訴DWM把它放到桌面上去

      DWM本身是基于Direct3D的,D3D下面是WDDM驅(qū)動

      至于應用程序,絕大多數(shù)win桌面應用都是基于GDI的,很老的圖形庫 (從某個版本起GDI也是跑在D3D之上了,于是顯卡廠家就不用寫GDI驅(qū)動了),D3D(比如基于WPF的應用,今天主要介紹的應用),OpenGL(現(xiàn)在的Windows的圖形架構是以DirectX為主,OpenGL支持需要OpenGL installable client driver)

      window圖像渲染的基本流程

      從程序中提交一個Draw,數(shù)據(jù)需要經(jīng)過:

      A

      p

      p

      ?

      >

      D

      X

      r

      u

      n

      t

      i

      m

      e

      ?

      >

      U

      s

      e

      r

      m

      o

      d

      e

      d

      r

      i

      v

      e

      r

      ?

      >

      d

      x

      g

      k

      r

      n

      l

      ?

      >

      K

      e

      r

      n

      e

      l

      m

      o

      d

      e

      d

      r

      i

      v

      e

      r

      ?

      >

      G

      P

      U

      App->DX runtime->User mode driver->dxgkrnl->Kernel mode driver->GPU

      App?>DXruntime?>Usermodedriver?>dxgkrnl?>Kernelmodedriver?>GPU

      在到達GPU之前,全都是在CPU上執(zhí)行的,所以從程序本身是無法獲取渲染結果

      到這里就為我們做window桌面程序圖像渲染性能測試帶來兩個問題:

      怎么檢查圖像渲染的質(zhì)量?

      怎么獲取圖像渲染的響應時間?

      由于需要桌面UI自動化測試的技術,所以下面我們介紹window桌面程序UI自動化測試技術

      window桌面程序UI自動化測試技術

      Win32程序

      使用 Win32 API 來創(chuàng)建的程序成為Win32程序。

      提供 Win32 API的dll被加載到應用程序的進程中,應用程序通過這些API來創(chuàng)建線程、窗口和控件。

      Win32程序中,所有窗口和控件都是一個窗口類的實例,都擁有一個窗口句柄,窗口對象屬于內(nèi)核對象,由Windows子系統(tǒng)來維護。

      Windows子系統(tǒng)為標準控件定義了窗口類,并使用GDI來繪制這些標準控件。

      Win32程序采用消息循環(huán)機制:

      WPF程序

      WPF的控件不再是通過Win32 API來創(chuàng)建窗口,使用Win32 API并不能查找和操作WPF控件

      WPF所有控件和動畫都是使用DirectX 繪制

      WPF控件不直接支持MSAA,而是通過 UIA 用橋轉(zhuǎn)換技術來支持MSAA

      WPF用AutomationPeer類支持自動化,每一種控件都有對應的 AutomationPeer類。AutomationPeer不直接暴露給測試客戶端,而是通過UIA來使用。UIA向應用程序窗口發(fā)送WM_GetObject消息,獲得由AutomationPeer實現(xiàn)的UIA Server端Provider。AutomationPeer由控件創(chuàng)建(OnCreateAutomationPeer)

      UIAutomation

      UIAutomation是微軟從Windows Vista開始推出的一套全新UI自動化測試技術, 簡稱UIA。

      UIA定義了全新的、針對UI自動化的接口和模式。測試程序可以通過這些接口來查找和操作控件。

      遍歷和條件化查詢:TreeWalker/FindAll

      UI元素屬性的UIA Property, 包括Name、 ID、Type、ClassName、Location、 Visibility等等。

      UIA Pattern(控件的行為模式), 比如Select、Expand、Resize、 Check、Value等等。

      UIA的兩種實現(xiàn)方法:

      Server-Side Provider:

      由被測程序?qū)崿F(xiàn)UIA定義的接口,返回給測試程序。WPF程序通過這種方式來支持UIA。

      Client-Side Provider:

      測試程序沒有實現(xiàn)UIA定義的接口。由UIA Runtime或測試程序自己來實現(xiàn)。比如Win32和WinForm程序,UIA Runtime通過MSAA來實現(xiàn)UIA定義的接口。UIA定義了全新的、針對UI自動化的接口和模式。測試程序可以通過這些接口來查找和操作控件。

      遍歷和條件化查詢:TreeWalker/FindAll

      UI元素屬性的UIA Property, 包括Name、 ID、Type、ClassName、Location、 Visibility等等。

      UIA Pattern(控件的行為模式), 比如Select、Expand、Resize、 Check、Value等等。

      UIA驅(qū)動計算器示例:

      using System.Windows.Automation; PropertyCondition conditionName = new PropertyCondition(AutomationElement.NameProperty, "計算器"); AutomationElement calcWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, conditionName); //Button 1 PropertyCondition conditionBtn1 = new PropertyCondition(AutomationElement.AutomationIdProperty, "131"); AutomationElement button1 = calcWindow.FindFirst(TreeScope.Descendants, conditionBtn1); //點擊Button1 InvokePattern invokePatternBtn1 = (InvokePattern)button1.GetCurrentPattern(InvokePattern.Pattern); invokePatternBtn1.Invoke();

      桌面程序圖像渲染性能測試實踐

      因為我們的性能測試是基于部分UI自動化測試技術落地的,在此介紹一下我們的UI自動化測試解決方案

      測試解決方案應至少包括5個項目,其中前兩個是和其他測試解決方案共享的。5個項目均為類庫,不能直接執(zhí)行。

      AI.Robot為UI驅(qū)動框架。 AI.Utilities項目里是一些輔助類,如數(shù)據(jù)庫讀寫、圖片對比等(性能測試需用到)。

      AI.App.UIObjects項目里放置UI對象。把UI對象集中放置到此項目中是為了減少界面更改帶來的維護工作量。

      AI.App.BusinessLogic項目里放置可重復用到的界面元素操作的集合,通常是為了完成一項特定的業(yè)務的步驟的集合。

      AI.App.TestCases里放置測試用例。并按照MSTest單元測試框架組織測試類和測試方法。包含測試類和測試方法的.net類庫稱為測試程序集。

      今天討論的桌面程序圖像渲染性能測試主要應用于以下兩種應用:

      普通PC桌面WPF應用(分辨率<2K)

      大屏幕可視化WPF應用(分辨率>8K)

      普通PC桌面WPF應用

      首先,回到之前的兩個問題:

      怎么檢查圖像渲染的質(zhì)量?

      怎么獲取圖像渲染的響應時間?

      首先將正常渲染完的控件輸出成圖片

      // 將控件uiElement輸出到圖片aa.bmp uiElement.CaptureBitmap(@"D:\aa.bmp");

      使用測試工具驅(qū)動啟動被測應用并開始計時,在渲染過程中快速截圖,實時比較兩幅圖片是否完全相等,如果相等并結束計時并寫入響應時間。

      // 比較兩幅圖片是否完全相同(所有像素點都相同) bool isEqual = ImageHelper.IsEqual(@"D:\image1.bmp", @"D:\image2.bmp");

      判斷兩幅圖是否完全相同

      ///

      /// 判斷兩幅圖是否完全相同 /// /// 待比較的第一幅圖 /// 待比較的第二幅圖 /// 如果兩幅圖完全相等,則返回true,否則返回false public static bool IsEqual(string imageFile1, string imageFile2) { float similarity = Compare(imageFile1, imageFile2); return similarity == 1; }

      影響圖片輸出的因素:

      1.顯卡,不同顯卡輸出文字和漸變色的時候有細微的差別,所以不同機器上顯示的控件和輸出的圖片通常不完全相同,特別是當控件上有文字的時候。

      2.DPI設置,將機器的DPI設置為120%時,100x100大小的控件將顯示為120x120像素

      3.當在遠程桌面上運行測試時,遠程連接的選項“字體平滑”會影響控件顯示和輸出的圖片

      大屏幕可視化WPF應用

      由于大屏幕的分辨率8K起步,也就不適應上面的截圖判斷方法了,為什么呢?

      我們簡單來計算8K圖片的大小吧

      分辨率:

      7680

      ×

      4320

      =

      33177600

      像素

      95

      M

      B

      分辨率:7680×4320=33177600像素≈95MB

      分辨率:7680×4320=33177600像素≈95MB

      我們常見顯示器用256種狀態(tài)標識屏幕上某種顏色的灰度,而屏幕采用三基色紅綠藍(RGB),不壓縮的情況下一個像素需要占用24bit(3字節(jié)),這個就是常說的24位真彩色。

      近100MB的圖片實時截圖并進行判斷,本身兩個動作就會對機器的計算資源消耗巨大,會嚴重影響性能測試準確性。

      這里我們折中使用實時判斷標志位RGB像素點的方法來判斷圖片渲染的結果

      首先,我們會使用取色器采樣幾個最后圖像渲染完成的坐標像素點RGB值

      原理其實很簡單,只需要兩步:

      鼠標移動的時候獲取鼠標光標的位置

      鼠標單擊獲取當前鼠標光標的位置的RGB顏色值到粘貼板

      涉及HookManager技術

      namespace GetColor { public partial class Form1 : Form { //顯示設備上下文環(huán)境的句柄 private IntPtr hdc = IntPtr.Zero; private int maxY, maxX; public Form1() { InitializeComponent(); this.BackColor = Color.Brown; this.TransparencyKey = Color.Brown; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; this.WindowState = FormWindowState.Maximized; this.TopMost = true; this.Cursor = Cursors.Cross; HookManager.MouseMove += OnMouseMove; this.KeyPress += OnKeyPress; this.MouseClick += OnMouseDown; Size screenSize = Screen.PrimaryScreen.Bounds.Size; this.maxX = screenSize.Width - this.label1.Width; this.maxY = screenSize.Height - this.label1.Height; this.SetLabel(Control.MousePosition.X, Control.MousePosition.Y); } //鼠標移動,實時獲取鼠標位置 private void OnMouseMove(object sender, MouseEventArgs e) { this.SetLabel(Control.MousePosition.X, Control.MousePosition.Y); } private void SetLabel(int x, int y) { if(this.hdc == IntPtr.Zero) { this.hdc = GetDC(IntPtr.Zero); this.label1.Location = new Point(Math.Min(this.maxX, x + 10), Math.Min(this.maxY, y + 10)); int color = GetPixel(this.hdc, x, y); this.label1.Text = string.Format("X={0},Y={1}\r\nColor:#{2}\r\n{3}", x, y, Convert.ToString(color, 16).PadLeft(6, '0'), color); this.Update(); ReleaseDC(IntPtr.Zero, this.hdc); DeleteDC(this.hdc); this.hdc = IntPtr.Zero; } } private void OnKeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Escape) { UnHook(); Application.Exit(); } } private void OnMouseDown(object sender, MouseEventArgs e) { //檢索一指定窗口的客戶區(qū)或整個屏幕的顯示設備上下文環(huán)境的句柄 this.hdc = GetDC(IntPtr.Zero); //指定坐標點的像素的RGB顏色值。 int color = GetPixel(this.hdc, e.X, e.Y); //鼠標單擊拷貝值 if (e.Button == MouseButtons.Left) { Clipboard.SetText(string.Format("", e.X, e.Y, color)); } ReleaseDC(IntPtr.Zero, this.hdc); DeleteDC(this.hdc); UnHook(); Application.Exit(); } private void UnHook() { HookManager.MouseMove -= OnMouseMove; } [DllImport("user32")] public static extern IntPtr GetDC(IntPtr hwnd); [DllImport("gdi32")] public static extern int GetPixel(IntPtr hdc, int x, int y); [DllImport("user32.dll")] public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc); [DllImport("gdi32.dll")] public static extern IntPtr DeleteDC(IntPtr hDc); } }

      小程序截圖:

      把圖像渲染結果采樣點填入測試工具的XML配置文件后,我們使用測試工具啟動程序開始計時并實判斷采樣標志位像素點的RGB值,如果全部通過結束計時并寫入渲染響應時間

      public void ValidateStage(Screen screen) { bool allReady = false; foreach (var stage in screen.ValidationStages) { stage.IsReady = false; } DateTime now = DateTime.Now; while (!allReady && (DateTime.Now - now).TotalSeconds < this.Config.Timeout) { allReady = true; foreach (var stage in screen.ValidationStages) { if(!stage.IsReady) { stage.IsReady = this.ValidatePointsOneTime(stage.ValidatePoints); if (stage.IsReady) { TimeSpan cost = DateTime.Now - now; this.logger.LogInfo(string.Format("{0}耗時{1}", stage.Name, cost.TotalSeconds)); this.logData += string.Format(",{0}", cost.TotalSeconds); } else { allReady = false; } } } Thread.Sleep(this.Config.TryInterval); } foreach (var stage in screen.ValidationStages) { if(!stage.IsReady) { foreach (ValidatePoint point in stage.ValidatePoints) { int color = Root.GetPointColor(point.X, point.Y); this.logger.LogInfo(string.Format("點({0}, {1})的顏色為{2}", point.X, point.Y, color)); } this.logger.LogInfo(string.Format("{0}秒內(nèi){1}未繪制完", this.Config.Timeout.ToString(), stage.Name)); this.logData += string.Format(",>{0}", this.Config.Timeout); } } } public void ValidateStage(Screen screen, ValidationStage stage) { DateTime now = DateTime.Now; bool screenReady = this.ValidatePoints(stage.ValidatePoints); if (screenReady) { TimeSpan cost = DateTime.Now - now; this.logger.LogInfo(string.Format("{0}耗時{1}", stage.Name, cost.TotalSeconds)); this.logData += string.Format(",{0}", cost.TotalSeconds); } else { this.logger.LogInfo(string.Format("{0}秒內(nèi){1}未繪制完", this.Config.Timeout.ToString(), stage.Name)); this.logData += string.Format(",>{0}", this.Config.Timeout); } } public bool ValidatePointsOneTime(List validationPoints) { foreach (ValidatePoint point in validationPoints) { int color = Root.GetPointColor(point.X, point.Y); if (!Common.IsSimilarColor(color, point.Color)) { return false; } } return true; } public bool ValidatePoints(List validationPoints) { DateTime endTime = DateTime.Now + TimeSpan.FromSeconds(this.Config.Timeout); bool finished = false; while (DateTime.Now < endTime) { bool allPointReady = true; foreach (ValidatePoint point in validationPoints) { int color = Root.GetPointColor(point.X, point.Y); //this.logger.LogInfo(color.ToString() + " " + point.Color.ToString()); if (!Common.IsSimilarColor(color, point.Color)) { allPointReady = false; break; } } if (allPointReady) { finished = true; break; } else { System.Threading.Thread.Sleep(this.Config.TryInterval); } } if(!finished) { foreach (ValidatePoint point in validationPoints) { int color = Root.GetPointColor(point.X, point.Y); this.logger.LogInfo(string.Format("點({0}, {1})的顏色為{2}", point.X, point.Y, color)); } } return finished; }

      實際效果:

      參考資料:

      [1]:Windows Display Driver Model (WDDM) Architecture (Windows Drivers)

      [2]:The Desktop Window Manager (Windows)

      API GUI Windows 云性能測試服務 CPTS 渲染

      版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。

      上一篇:mxGraph教程-開發(fā)入門指南
      下一篇:nginx--??圖解及代碼實現(xiàn)正向代理、反向代理及負載均衡(非常實用,建議收藏??)
      相關文章
      国产成人亚洲综合| 亚洲精品色播一区二区| 亚洲va中文字幕| 亚洲人成网站色在线观看| 亚洲国产精品久久久久秋霞影院| 亚洲电影一区二区三区| 亚洲精品自产拍在线观看| 亚洲三区在线观看无套内射| 亚洲XX00视频| 亚洲成人影院在线观看| 色天使色婷婷在线影院亚洲| 久久久久久亚洲精品无码| 久久久亚洲精华液精华液精华液| 亚洲丰满熟女一区二区哦| 亚洲国产成人AV在线播放| 自拍偷自拍亚洲精品偷一| 亚洲不卡无码av中文字幕| 另类图片亚洲校园小说区| 亚洲国产成人久久综合碰| 亚洲午夜福利精品久久| 国产亚洲精品a在线观看| 亚洲尤码不卡AV麻豆| 亚洲人成精品久久久久| 亚洲va中文字幕无码久久| 亚洲国产精品一区二区久久| 91天堂素人精品系列全集亚洲| 亚洲综合亚洲国产尤物| 亚洲人成日本在线观看| 国产AV旡码专区亚洲AV苍井空| 亚洲欧洲日产国码久在线| 亚洲精品无码专区| 日韩亚洲人成在线综合| 亚洲精品成人片在线观看| 亚洲愉拍99热成人精品热久久| 久久精品国产亚洲| 亚洲最新中文字幕| 亚洲AV成人一区二区三区在线看| 亚洲hairy多毛pics大全| 亚洲国产综合久久天堂| 国产亚洲精品va在线| 亚洲精品福利视频|