WorldWind中顯示圖像的是一個繼承自Control的WorldWindow控件,代碼:
public class WorldWindow : Control, IGlobe
初始化代碼:
public WorldWindow()
{ this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true); // The m_Device3d can't be created unless the control is at least 1 x 1 pixels in size this.Size = new Size(1,1); try { if(!IsInDesignMode()) this.InitializeGraphics(); ...... } catch (InvalidCallException caught) { throw new InvalidCallException( "Unable to locate a compatible graphics adapter.", caught ); } catch (NotAvailableException caught) { throw new NotAvailableException( "Unable to locate a compatible graphics adapter.", caught ); } } |
初始化代碼中主要的是:
(1) 設置窗體的樣式:
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
(2) 初始化DirectX,具體的關於DirectX的初始化參見《WorldWind-Direct3D的初始化》 :
if(!IsInDesignMode()) this.InitializeGraphics();
WorldWindow控件的 OnPaint() 方法:
protected override void OnPaint(PaintEventArgs e) { try { if(m_Device3d==null) { e.Graphics.Clear(SystemColors.Control); return; } Render(); m_Device3d.Present(); } catch(DeviceLostException) { try { AttemptRecovery(); Render(); m_Device3d.Present(); } catch(DirectXException){ } } } |
從代碼中可以看出,主要的渲染操作都存在於 Render() 方法中。
Render()方法:
public void Render()
{ try { this.drawArgs.BeginRender();
System.Drawing.Color backgroundColor = System.Drawing.Color.Black; m_Device3d.Clear(ClearFlags.Target | ClearFlags.ZBuffer, backgroundColor, 1.0f, 0); if (m_World == null) { m_Device3d.BeginScene(); m_Device3d.EndScene(); m_Device3d.Present(); Thread.Sleep(25); return; } // 用於對圖像進行邏輯處理的線程 if (m_WorkerThread == null) { m_WorkerThreadRunning = true; m_WorkerThread = new Thread(new ThreadStart(WorkerThreadFunc)); m_WorkerThread.Name = "WorldWindow.WorkerThreadFunc"; m_WorkerThread.IsBackground = true; if (World.Settings.UseBelowNormalPriorityUpdateThread) { m_WorkerThread.Priority = ThreadPriority.BelowNormal; } else { m_WorkerThread.Priority = ThreadPriority.Normal; } m_WorkerThread.Start(); } m_Device3d.BeginScene();
// 渲染當前星球,這是主要的渲染過程 m_World.Render(this.drawArgs); // 渲染位置信息 RenderPositionInfo(); // 渲染菜單欄 _menuBar.Render(drawArgs); m_FpsGraph.Render(drawArgs); // 渲染屏幕消息 if (m_World.OnScreenMessages != null) { try { foreach (OnScreenMessage dm in m_World.OnScreenMessages) { int xPos = (int)Math.Round(dm.X * this.Width); int yPos = (int)Math.Round(dm.Y * this.Height); Rectangle posRect = new Rectangle(xPos, yPos, this.Width, this.Height); this.drawArgs.defaultDrawingFont.DrawText(null, dm.Message, posRect, DrawTextFormat.NoClip | DrawTextFormat.WordBreak, Color.White); } } catch (Exception){} } m_Device3d.EndScene(); } catch (Exception ex){ Log.Write(ex);} finally{ this.drawArgs.EndRender();} drawArgs.UpdateMouseCursor(this); } |
圖像邏輯處理線程方法:
private void WorkerThreadFunc()
{ const int refreshIntervalMs = 150; // 更新頻率:Max 6 updates per second while(m_WorkerThreadRunning) { try { long startTicks = 0; PerformanceTimer.QueryPerformanceCounter(ref startTicks);
m_World.Update(this.drawArgs);
long endTicks = 0; PerformanceTimer.QueryPerformanceCounter(ref endTicks);
float elapsedMilliSeconds = 1000*(float)(endTicks - startTicks)/PerformanceTimer.TicksPerSecond; float remaining = refreshIntervalMs - elapsedMilliSeconds; if(remaining > 0) Thread.Sleep((int)remaining); } catch(Exception caught){ Log.Write(caught);} } } |
WorldWind渲染循環:
/// <summary>
/// The world render loop. Borrowed from FlightGear and Tom Miller's blog /// </summary> public void OnApplicationIdle(object sender, EventArgs e) { // Sleep will always overshoot by a bit so under-sleep by 2ms in the hopes of never oversleeping. const float SleepOverHeadSeconds = 2e-3f; // Overhead associated with displaying the frame const float PresentOverheadSeconds = 0;//3e-4f; try { if (Parent.Focused && !Focused) Focus(); while (IsAppStillIdle) { Render(); if (World.Settings.ThrottleFpsHz > 0) { // optionally throttle the frame rate (to get consistent frame rates or reduce CPU usage. float frameSeconds = 1.0f / World.Settings.ThrottleFpsHz - PresentOverheadSeconds; // Sleep for remaining period of time until next render float sleepSeconds = frameSeconds - SleepOverHeadSeconds - DrawArgs.SecondsSinceLastFrame; if(sleepSeconds > 0) { // Don't sleep too long. We don't know the accuracy of Thread.Sleep Thread.Sleep((int) (1000 * sleepSeconds)); // Burn off what little time still remains at 100% CPU load while(DrawArgs.SecondsSinceLastFrame < frameSeconds) { // Patience } } } drawArgs.Present(); } } catch(DeviceLostException){ AttemptRecovery();} catch(Exception caught){ Log.Write(caught);} } |
IsAppStillIdle屬性
/// <summary> /// Determine whether any window messages is queued. /// </summary> private static bool IsAppStillIdle { get { NativeMethods.Message msg; return !NativeMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); } } |
WorldWind的渲染循環是在 Application Idle 時進行的:
Application.ThreadException +=
new ThreadExceptionEventHandler(Application_ThreadException);
MainApplication app = new MainApplication(); Application.Idle += new EventHandler(app.WorldWindow.OnApplicationIdle); Application.Run(app); |