فهرست منبع

UpdateF, dx reinit

Brian Fiete 3 سال پیش
والد
کامیت
e87bf5b029

+ 37 - 10
BeefLibs/Beefy2D/src/BFApp.bf

@@ -45,7 +45,8 @@ namespace Beefy
 #endif
     {
         public delegate void UpdateDelegate(bool batchStart);
-        public delegate void DrawDelegate();
+		public delegate void UpdateFDelegate(float updatePct);
+        public delegate void DrawDelegate(bool forceDraw);
 
         public static BFApp sApp;
         public int32 mUpdateCnt;
@@ -106,7 +107,7 @@ namespace Beefy
         static extern void BFApp_Shutdown();
 
         [CallingConvention(.Stdcall), CLink]
-        static extern void BFApp_SetCallbacks(void* updateDelegate, void* drawDelegate);
+        static extern void BFApp_SetCallbacks(void* updateDelegate, void* updateFDelegate, void* drawDelegate);
 
         [CallingConvention(.Stdcall), CLink]
         static extern char8* BFApp_GetInstallDir();
@@ -132,7 +133,11 @@ namespace Beefy
 		[CallingConvention(.Stdcall), CLink]
 		public static extern void* BFApp_GetSoundManager();
 
+		[CallingConvention(.Stdcall), CLink]
+		public static extern int BFApp_GetCriticalThreadId(int32 idx);
+
         UpdateDelegate mUpdateDelegate ~ delete _;
+		UpdateFDelegate mUpdateFDelegate ~ delete _;
         DrawDelegate mDrawDelegate ~ delete _;
 		
 #if STUDIO_CLIENT
@@ -165,9 +170,9 @@ namespace Beefy
 		}
 #endif
         
-        static void Static_Draw()
+        static void Static_Draw(bool forceDraw)
         {
-            sApp.Draw();
+            sApp.Draw(forceDraw);
         }
         
         static void Static_Update(bool batchStart)
@@ -175,6 +180,11 @@ namespace Beefy
             sApp.Update(batchStart);
         }
 
+		static void Static_UpdateF(float updatePct)
+		{
+		    sApp.UpdateF(updatePct);
+		}
+
         float mLastUpdateDelta; // In seconds
 
         public this()
@@ -198,9 +208,10 @@ namespace Beefy
             BFApp_SetRefreshRate(mRefreshRate);
 			
 			mUpdateDelegate = new => Static_Update;
-			mDrawDelegate = new => Static_Draw;			
+			mUpdateFDelegate = new => Static_UpdateF;
+			mDrawDelegate = new => Static_Draw;
 #endif
-            BFApp_SetCallbacks(mUpdateDelegate.GetFuncPtr(), mDrawDelegate.GetFuncPtr());
+            BFApp_SetCallbacks(mUpdateDelegate.GetFuncPtr(), mUpdateFDelegate.GetFuncPtr(),  mDrawDelegate.GetFuncPtr());
         }
 
 #if STUDIO_CLIENT
@@ -515,6 +526,14 @@ namespace Beefy
                 structuredData.Load(resFileName);
                 mResourceManager.ParseConfigData(structuredData);
             }
+
+			for (int32 i = 0; true; i++)
+			{
+				int threadId = BFApp_GetCriticalThreadId(i);
+				if (threadId == 0)
+					break;
+				GC.ExcludeThreadId(threadId);
+			}
         }
 
         public void InitGraphics()
@@ -671,6 +690,14 @@ namespace Beefy
             //Utils.BFRT_CPP("gBFGC.MutatorSectionExit()");
         }        
 
+		public virtual void UpdateF(float updatePct)
+		{
+			for (int32 windowIdx = 0; windowIdx < mWindows.Count; windowIdx++)
+			{
+			    mWindows[windowIdx].UpdateF(updatePct);
+			}
+		}
+
         public virtual void DoDraw()
         {
         }
@@ -695,12 +722,11 @@ namespace Beefy
         }
 #endif
         
-        public virtual void Draw()
+        public virtual void Draw(bool forceDraw)
         {
 #if STUDIO_CLIENT            
             
-#endif            
-
+#endif
             PerfTimer.ZoneStart("BFApp.Draw");
             PerfTimer.Message("Client Draw Start");
 
@@ -716,7 +742,8 @@ namespace Beefy
 
             for (BFWindow window in mWindows)
             {
-                if ((window.mVisible) && ((window.mIsDirty) || (mAutoDirty)))
+                if ((window.mVisible) &&
+					((window.mIsDirty) || (mAutoDirty) || (forceDraw)))
                 {
                     window.PreDraw(mGraphics);
 					if (mColorMatrix != null)

+ 5 - 0
BeefLibs/Beefy2D/src/BFWindow.bf

@@ -664,6 +664,11 @@ namespace Beefy
         public virtual void Update()
         {            
         }
+
+		public virtual void UpdateF(float updatePct)
+		{
+
+		}
     }
 #else
     public class BFWindow : BFWindowBase, IStudioClientWindow

+ 31 - 5
BeefLibs/Beefy2D/src/theme/dark/DarkListView.bf

@@ -18,7 +18,12 @@ namespace Beefy.theme.dark
         public bool mIsOpen;
         public bool mAllowOpen = true;
 		public bool mIsReversed;
-        
+
+		public this()
+		{
+			mAlwaysUpdateF = true;
+		}
+
         public override void Draw(Graphics g)
         {
             base.Draw(g);
@@ -52,9 +57,9 @@ namespace Beefy.theme.dark
                 mRot = mIsOpen ? (Math.PI_f / 2) : 0;
         }
 
-        public override void Update()
+        public override void UpdateF(float updatePct)
         {
-            base.Update();
+            base.UpdateF(updatePct);
 
             int childCount = mItem.mChildItems.Count;
 
@@ -62,15 +67,17 @@ namespace Beefy.theme.dark
 
             if ((mIsOpen) && (mRot < Math.PI_f / 2))
             {
-                mRot = Math.Min(Math.PI_f / 2, mRot + rotSpeed);
+                mRot = Math.Min(Math.PI_f / 2, mRot + rotSpeed * updatePct);
                 mItem.mListView.mListSizeDirty = true;
 				MarkDirty();
+				mWidgetWindow.mTempWantsUpdateF = true;
             }
             else if ((!mIsOpen) && (mRot > 0))
             {
-                mRot = (float)Math.Max(0, mRot - rotSpeed);
+                mRot = (float)Math.Max(0, mRot - rotSpeed * updatePct);
                 mItem.mListView.mListSizeDirty = true;
 				MarkDirty();
+				mWidgetWindow.mTempWantsUpdateF = true;
             }
 
             float x;
@@ -876,6 +883,25 @@ namespace Beefy.theme.dark
 				}
 			}
 		}
+
+		public override void UpdateFAll(float updatePct)
+		{
+			if (mVisible)
+			{
+				base.UpdateFAll(updatePct);
+
+				if (mChildItems != null)
+				{
+					for (int32 anIdx = 0; anIdx < mChildItems.Count; anIdx++)
+					{
+					    Widget child = mChildItems[anIdx];
+					    Debug.Assert(child.mParent == this);
+					    Debug.Assert(child.mWidgetWindow == mWidgetWindow);
+					    child.UpdateFAll(updatePct);
+					}
+				}
+			}
+		}
     }
 
     public class DarkListView : ListView

+ 2 - 2
BeefLibs/Beefy2D/src/utils/SmoothValue.bf

@@ -37,9 +37,9 @@ namespace Beefy.utils
             get { return mPct != 1.0f; }
         }
 
-        public void Update()
+        public void Update(float updatePct = 1.0f)
         {
-            mPct = Math.Min(1.0f, mPct + mSpeed * mSpeedScale);
+            mPct = Math.Min(1.0f, mPct + mSpeed * mSpeedScale * updatePct);
         }
 
         public void Set(double val, bool immediate = false)

+ 7 - 2
BeefLibs/Beefy2D/src/widgets/ListView.bf

@@ -788,10 +788,15 @@ namespace Beefy.widgets
         public override void UpdateAll()
         {
             base.UpdateAll();
-
             UpdateListSize();
         }
 
+		public override void UpdateFAll(float updatePct)
+		{
+		    base.UpdateFAll(updatePct);
+		    UpdateListSize();
+		}
+
         public virtual float GetListWidth()
         {
             float columnWidths = 0;
@@ -802,7 +807,7 @@ namespace Beefy.widgets
 
         public void UpdateListSize()
         {
-            // Do this in UpdateAll to give children a change to resize items
+            // Do this in UpdateAll to give children a chance to resize items
             if (mListSizeDirty)
             {
 				float listWidth = GetListWidth();

+ 16 - 7
BeefLibs/Beefy2D/src/widgets/ScrollableWidget.bf

@@ -50,6 +50,7 @@ namespace Beefy.widgets
             mScrollContentContainer.mClipGfx = true;
             mScrollContentContainer.mClipMouse = true;
             AddWidget(mScrollContentContainer);
+			mAlwaysUpdateF = true;
         }
 
 		public ~this()
@@ -248,15 +249,23 @@ namespace Beefy.widgets
         {
             base.Update();
 
-            if ((mHorzPos.IsMoving) || (mVertPos.IsMoving))
-            {
-                mHorzPos.Update();
-                mVertPos.Update();
-                UpdateContentPosition();
-				MarkDirty();
-            }
+            
         }
 
+		public override void UpdateF(float updatePct)
+		{
+			base.UpdateF(updatePct);
+
+			if ((mHorzPos.IsMoving) || (mVertPos.IsMoving))
+			{
+				mWidgetWindow.mTempWantsUpdateF = true;
+			    mHorzPos.Update(updatePct);
+			    mVertPos.Update(updatePct);
+			    UpdateContentPosition();
+				MarkDirty();
+			}
+		}
+
         public override void MouseWheel(float x, float y, float deltaX, float deltaY)
         {
             base.MouseWheel(x, y, deltaX, deltaY);

+ 35 - 0
BeefLibs/Beefy2D/src/widgets/Widget.bf

@@ -39,6 +39,7 @@ namespace Beefy.widgets
         public float mWidth;
         public float mHeight;
         public int32 mUpdateCnt;
+		public double mUpdateCntF;
         public String mIdStr ~ delete _;
         public List<Widget> mChildWidgets;
         public MouseFlag mMouseFlags;
@@ -56,6 +57,7 @@ namespace Beefy.widgets
 		public bool mDeferredDelete;
         public Insets mMouseInsets ~ delete _;
         public bool mAutoFocus;
+		public bool mAlwaysUpdateF;
         
         public float X { get { return mX; } set { mX = value; } }        
         public float Y { get { return mY; } set { mY = value; } }
@@ -455,8 +457,16 @@ namespace Beefy.widgets
         public virtual void Update()
         {
             mUpdateCnt++;
+			if ((mAlwaysUpdateF) && (mWidgetWindow != null))
+				UpdateF((float)(mUpdateCnt - mUpdateCntF));
+			mUpdateCntF = mUpdateCnt;
         }
 
+		public virtual void UpdateF(float updatePct)
+		{
+		    mUpdateCntF += updatePct;
+		}
+
 		public void DeferDelete()
 		{
 			mDeferredDelete = true;
@@ -487,6 +497,31 @@ namespace Beefy.widgets
             }
         }
 
+		public virtual void UpdateFAll(float updatePct)
+		{
+		    UpdateF(updatePct);
+			if (mDeferredDelete)
+			{
+				delete this;
+				return;
+			}
+
+		    // Removed self?
+		    if (mWidgetWindow == null)
+		        return;
+
+		    if (mChildWidgets != null)
+		    {
+		        for (int32 anIdx = 0; anIdx < mChildWidgets.Count; anIdx++)
+		        {
+		            Widget child = mChildWidgets[anIdx];
+		            Debug.Assert(child.mParent == this);
+		            Debug.Assert(child.mWidgetWindow == mWidgetWindow);
+		            child.UpdateFAll(updatePct);
+		        }
+		    }
+		}
+
         public virtual void Resize(float x, float y, float width, float height)
         {
 			Debug.Assert(width >= 0);

+ 12 - 0
BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf

@@ -62,6 +62,8 @@ namespace Beefy.widgets
         public bool mHasMouseInside;
         public bool mHasProxyMouseInside;
         public bool mIsKeyDownHandled;
+		public bool mWantsUpdateF;
+		public bool mTempWantsUpdateF;
 
         public int32 mContentClientWidth;
         public int32 mContentClientHeight;
@@ -153,8 +155,18 @@ namespace Beefy.widgets
 				return;
             base.Update();
             RehupMouse(false);
+			mTempWantsUpdateF = false;
             mRootWidget.UpdateAll();
         }
+
+		public override void UpdateF(float updatePct)
+		{
+			if (mRootWidget == null)
+				return;
+			base.Update();
+			if (mWantsUpdateF || mTempWantsUpdateF)
+				mRootWidget.UpdateFAll(updatePct);
+		}
         
         public override int32 CloseQuery()
         {

+ 3 - 0
BeefLibs/corlib/src/GC.bf

@@ -133,6 +133,8 @@ namespace System
 		public extern static void SetMaxPausePercentage(int maxPausePercentage); // 0 = disabled. Defaults to 20.
 		[CallingConvention(.Cdecl)]
 		extern static void AddPendingThread(void* internalThread);
+		[CallingConvention(.Cdecl)]
+		public extern static void ExcludeThreadId(int thereadId);
 #else
 		public static void Collect(bool async = true) {}
 		private static void MarkAllStaticMembers() {}
@@ -144,6 +146,7 @@ namespace System
 		public static void SetCollectFreeThreshold(int freeBytes) {}
 		public static void SetMaxPausePercentage(int maxPausePercentage) {}
 		static void AddPendingThread(void* internalThreadInfo) {}
+		public static void ExcludeThreadId(int thereadId) {}
 #endif
 
 		static void MarkDerefedObject(Object* obj)

+ 22 - 2
BeefRT/dbg/gc.cpp

@@ -1429,6 +1429,9 @@ bool BFGC::ScanThreads()
 			thread = mThreadList[threadIdx++];
 		}
 
+		if (thread->mExcluded)
+			continue;
+
 		if (!thread->mRunning)
 		{
 			AutoCrit autoCrit(mCritSect);
@@ -2367,11 +2370,12 @@ void BFGC::SuspendThreads()
 	auto curThreadId = GetCurrentThreadId();
 	for (auto thread : mThreadList)
 	{
-		if ((thread->mThreadId != curThreadId) && (thread->mRunning) && (thread->WantsSuspend()))
+		if ((thread->mThreadId != curThreadId) && (!thread->mExcluded) && (thread->mRunning) && (thread->WantsSuspend()))
 		{
 			// We must lock this before suspending so we can access mStackMarkableObjects
 			//  Otherwise we could deadlock
 			thread->mCritSect.Lock();
+			thread->mSuspended = true;
 
 			BfpThreadResult result;
 			BfpThread_Suspend(thread->mThreadHandle, &result);
@@ -2386,11 +2390,12 @@ void BFGC::ResumeThreads()
 	auto curThreadId = GetCurrentThreadId();
 	for (auto thread : mThreadList)
 	{
-		if ((thread->mThreadId != curThreadId) && (thread->mRunning) && (thread->WantsSuspend()))
+		if ((thread->mThreadId != curThreadId) && (thread->mSuspended) && (thread->mRunning) && (thread->WantsSuspend()))
 		{
 			// Previously locked in SuspendThreads
 			thread->mCritSect.Unlock();
 
+			thread->mSuspended = false;
 			BfpThread_Resume(thread->mThreadHandle, NULL);
 		}
 	}
@@ -2743,6 +2748,16 @@ void BFGC::SetMaxRawDeferredObjectFreePercentage(intptr maxPercentage)
 	mMaxRawDeferredObjectFreePercentage = maxPercentage;
 }
 
+void BFGC::ExcludeThreadId(intptr threadId)
+{
+	Beefy::AutoCrit autoCrit(mCritSect);
+	for (auto thread : mThreadList)
+	{
+		if (thread->mThreadId == threadId)
+			thread->mExcluded = true;
+	}
+}
+
 using namespace bf::System;
 
 void GC::Run()
@@ -2832,6 +2847,11 @@ BFRT_EXPORT void bf::System::GC::SetMaxRawDeferredObjectFreePercentage(intptr ma
 	gBFGC.SetMaxRawDeferredObjectFreePercentage(maxPercentage);
 }
 
+BFRT_EXPORT void bf::System::GC::ExcludeThreadId(intptr threadId)
+{
+	gBFGC.ExcludeThreadId(threadId);
+}
+
 #else // BF_GC_SUPPORTED
 
 void* BfObjectAllocate(intptr size, bf::System::Type* type)

+ 7 - 1
BeefRT/dbg/gc.h

@@ -181,6 +181,8 @@ public:
 		intptr mStackStart;
 		intptr mLastStackPtr;
 		bool mRunning;
+		bool mExcluded;
+		bool mSuspended;
 		Beefy::Array<bf::System::Object*> mStackMarkableObjects;
 
 		ThreadInfo()
@@ -192,6 +194,8 @@ public:
 			mTEB = NULL;
 			mStackStart = NULL;
 			mRunning = true;
+			mExcluded = false;
+			mSuspended = false;
 		}
 
 		~ThreadInfo();
@@ -424,6 +428,7 @@ public:
 	void SetCollectFreeThreshold(int freeBytes);
 	void SetMaxPausePercentage(int maxPausePercentage);
 	void SetMaxRawDeferredObjectFreePercentage(intptr maxPercentage);
+	void ExcludeThreadId(intptr threadId);
 };
 
 extern BFGC gBFGC;
@@ -466,7 +471,7 @@ namespace bf
 			BFRT_EXPORT static void StopCollecting();
 			BFRT_EXPORT static void AddStackMarkableObject(Object* obj);
 			BFRT_EXPORT static void RemoveStackMarkableObject(Object* obj);
-			BFRT_EXPORT static void AddPendingThread(void* internalThreadInfo);
+			BFRT_EXPORT static void AddPendingThread(void* internalThreadInfo);			
 			
 		public:
 			BFRT_EXPORT static void Shutdown();			
@@ -488,6 +493,7 @@ namespace bf
 			BFRT_EXPORT static void SetCollectFreeThreshold(intptr freeBytes);
 			BFRT_EXPORT static void SetMaxPausePercentage(intptr maxPausePercentage);
 			BFRT_EXPORT static void SetMaxRawDeferredObjectFreePercentage(intptr maxPercentage);			
+			BFRT_EXPORT static void ExcludeThreadId(intptr threadId);
 		};
 	}
 }

+ 116 - 74
BeefySysLib/BFApp.cpp

@@ -19,10 +19,11 @@ BFApp::BFApp()
 	mTitle = "Beefy Application";
 	mRefreshRate = 60;	
 	mLastProcessTick = BFTickCount();
-	mFrameTimeAcc = 0;
+	mPhysFrameTimeAcc = 0;
 	mDrawEnabled = true;
 	
 	mUpdateFunc = NULL;
+	mUpdateFFunc = NULL;
 	mDrawFunc = NULL;
 	
 	gBFApp = this;
@@ -43,6 +44,12 @@ BFApp::BFApp()
 	mRunning = false;
 	mRenderDevice = NULL;
 	mVSynched = false;
+	mVSyncActive = false;
+	mForceNextDraw = false;
+
+	mUpdateCnt = 0;
+	mUpdateCntF = 0;
+	mClientUpdateCntF = 0;
 }
 
 BFApp::~BFApp()
@@ -90,10 +97,16 @@ void BFApp::Update(bool batchStart)
 	mPendingWindowDeleteList.clear();
 }
 
+void BFApp::UpdateF(float updatePct)
+{
+	mUpdateFFunc(updatePct);
+}
+
 void BFApp::Draw()
 {    
 	gPerfManager->ZoneStart("BFApp::Draw");
-	mDrawFunc();	
+	mDrawFunc(mForceNextDraw);	
+	mForceNextDraw = false;
 	gPerfManager->ZoneEnd();
 }
 
@@ -103,90 +116,88 @@ void BFApp::Process()
 {
     //Beefy::DebugTimeGuard suspendTimeGuard(30, "BFApp::Process");
     
+	RenderWindow* headRenderWindow = NULL;
+
+ 	float physRefreshRate = 0;
+	if (!mRenderDevice->mRenderWindowList.IsEmpty())
+	{
+		headRenderWindow = mRenderDevice->mRenderWindowList[0];
+		physRefreshRate = headRenderWindow->GetRefreshRate();
+	}
+
+	if (physRefreshRate <= 0)
+		physRefreshRate = 60.0f;
+
+	float ticksPerFrame = 1;
+	float physTicksPerFrame = 1000.0f / physRefreshRate;
+
 	if (mInProcess)
 		return; // No reentry
 	mInProcess = true;
-
-	int updates;
 	
 	uint32 tickNow = BFTickCount();
 	const int vSyncTestingPeriod = 250;
+		
+	bool didVBlankWait = false;
+		
+	if (mVSyncActive)
+	{		
+		// Have a time limit in the cases we miss the vblank
+		if (mVSyncEvent.WaitFor((int)(physTicksPerFrame + 1)))
+			didVBlankWait = true;
+	}
 
-	if (mRefreshRate != 0)
-	{
-		float ticksPerFrame = 1000.0f / mRefreshRate;
-		int ticksSinceLastProcess = tickNow - mLastProcessTick;
+	if (mRefreshRate > 0)
+		ticksPerFrame = 1000.0f / mRefreshRate;
+	int ticksSinceLastProcess = tickNow - mLastProcessTick;
 
-        mUpdateSampleCount++;
-        mUpdateSampleTimes += ticksSinceLastProcess;
-        //TODO: Turn off mVSynched based on error calculations - (?)
+    mUpdateSampleCount++;
+    mUpdateSampleTimes += ticksSinceLastProcess;
+    //TODO: Turn off mVSynched based on error calculations - (?)
 
-		// Two VSync failures in a row means we set mVSyncFailed and permanently disable it
-		if (mUpdateSampleTimes >= vSyncTestingPeriod)
+	// Two VSync failures in a row means we set mVSyncFailed and permanently disable it
+	if (mUpdateSampleTimes >= vSyncTestingPeriod)
+	{
+		int expectedFrames = (int)(mUpdateSampleTimes / ticksPerFrame);
+		if (mUpdateSampleCount > expectedFrames * 1.5)			
 		{
-			int expectedFrames = (int)(mUpdateSampleTimes / ticksPerFrame);
-			if (mUpdateSampleCount > expectedFrames * 1.5)			
-			{
-				if (!mVSynched)
-					mVSyncFailed = true;				
-				mVSynched = false;
-			}
-			else
-				if (!mVSyncFailed)
-					mVSynched = true;
-			
-			mUpdateSampleCount = 0;
-			mUpdateSampleTimes = 0;
+			if (!mVSynched)
+				mVSyncFailed = true;				
+			mVSynched = false;
 		}
-        
-		mFrameTimeAcc += tickNow - mLastProcessTick;			
-        
-		bool vSynched = mVSynched;
-
-		if (vSynched)
+		else
 		{
-			// For the startup, try not to go hyper during those first samplings
-			if (mUpdateSampleTimes <= vSyncTestingPeriod)
-			{
-				if (ticksSinceLastProcess < ticksPerFrame / 1.5)
-					vSynched = false;
-			}
+			if (!mVSyncFailed)
+				mVSynched = true;
 		}
-
-        if (vSynched)
-        {
-       		updates = std::max(1, (int)(mFrameTimeAcc / ticksPerFrame + 0.5f));
-            mFrameTimeAcc = std::max(0.0f, mFrameTimeAcc - ticksPerFrame * updates);
-        }
-        else
-        {
-            updates = std::max(0, (int)(mFrameTimeAcc / ticksPerFrame));
-            mFrameTimeAcc = mFrameTimeAcc - ticksPerFrame * updates;
-        }
-        
-		if (updates > mRefreshRate)
+			
+		mUpdateSampleCount = 0;
+		mUpdateSampleTimes = 0;
+	}
+        		
+	mPhysFrameTimeAcc += tickNow - mLastProcessTick;
+        	
+	if (didVBlankWait)
+	{
+		// Try to keep time synced with vblank
+		if (mPhysFrameTimeAcc < physTicksPerFrame * 2)
 		{
-			// If more than 1 second of updates is queued, just re-sync
-			updates = 1;
-			mFrameTimeAcc = 0;
+			float timeAdjust = physTicksPerFrame - mPhysFrameTimeAcc + 0.001f;
+			mPhysFrameTimeAcc += timeAdjust;				
 		}
+	}
 
-		// Compensate for "slow start" by limiting the number of catchup-updates we can do when starting the app
-		int maxUpdates = BF_MIN(mNumPhysUpdates + 1, mMaxUpdatesPerDraw);
+    /*if (updates > 2)
+        OutputDebugStrF("Updates: %d  TickDelta: %d\n", updates, tickNow - mLastProcessTick);*/	
+    
+	// Compensate for "slow start" by limiting the number of catchup-updates we can do when starting the app
+	int maxUpdates = BF_MIN(mNumPhysUpdates + 1, mMaxUpdatesPerDraw);
 
-		updates = BF_MIN(updates, maxUpdates);
-        
-        /*if (updates > 2)
-            OutputDebugStrF("Updates: %d  TickDelta: %d\n", updates, tickNow - mLastProcessTick);*/
+	while (mPhysFrameTimeAcc >= physTicksPerFrame)
+	{
+		mPhysFrameTimeAcc -= physTicksPerFrame;
+		mUpdateCntF += physTicksPerFrame / ticksPerFrame;
 	}
-	else
-		updates = 1; // RefreshRate of 0 means to update as fast as possible
-    
-    if (updates == 0)
-    {
-        // Yield
-        BfpThread_Sleep(1);
-    }
     	
 	static uint32 lastUpdate = BFTickCount();	
 		
@@ -202,17 +213,46 @@ void BFApp::Process()
 	}
 #endif
 
-	if (updates > 0)
-		mNumPhysUpdates++;
+		
+	int didUpdateCnt = 0;
+	
+	if (mClientUpdateCntF - mUpdateCntF > physRefreshRate / 2)
+	{
+		// Too large of a difference, just sync
+		mClientUpdateCntF = mUpdateCntF - 1;
+	}
 
-	for (int updateNum = 0; updateNum < updates; updateNum++)
+	while ((int)mClientUpdateCntF < (int)mUpdateCntF)
 	{
-		if (!mRunning)
+		Update(didUpdateCnt == 0);
+		didUpdateCnt++;		
+		mClientUpdateCntF = (int)mClientUpdateCntF + 1.000001;
+		if (didUpdateCnt >= maxUpdates)
 			break;
-		Update(updateNum == 0);
+	}
+
+	// Only attempt UpdateF updates if our rates aren't nearl) the same
+	if ((mRefreshRate != 0) && (fabs(physRefreshRate - mRefreshRate) / (float)mRefreshRate > 0.1f))
+	{
+		float updateFAmt = (float)(mUpdateCntF - mClientUpdateCntF);
+		if ((updateFAmt > 0.05f) && (updateFAmt < 1.0f) && (didUpdateCnt < maxUpdates))
+		{
+			UpdateF(updateFAmt);
+			didUpdateCnt++;
+			mClientUpdateCntF = mUpdateCntF;
+		}
+	}
+
+	if (didUpdateCnt > 0)
+		mNumPhysUpdates++;
+
+	if ((mRunning) && (didUpdateCnt == 0))
+	{
+		BfpThread_Sleep(1);				
 	}
 	
-	if ((mRunning) && (updates > 0))
+	if ((mRunning) && 
+		((didUpdateCnt != 0) || (mForceNextDraw)))
 		Draw();
 
 #ifdef PERIODIC_PERF_TIMING
@@ -229,6 +269,8 @@ void BFApp::Process()
 
 void BFApp::RemoveWindow(BFWindow* window)
 {
+	AutoCrit autoCrit(mCritSect);
+
 	auto itr = std::find(mWindowList.begin(), mWindowList.end(), window);
 	if (itr == mWindowList.end()) // Allow benign failure (double removal)
 		return; 

+ 15 - 3
BeefySysLib/BFApp.h

@@ -1,12 +1,14 @@
 #pragma once
 
 #include "Common.h"
+#include "util/CritSect.h"
 #include <list>
 
 NS_BF_BEGIN;
 
 typedef void (*BFApp_UpdateFunc)(bool batchStart);
-typedef void (*BFApp_DrawFunc)();
+typedef void (*BFApp_UpdateFFunc)(float updatePct);
+typedef void (*BFApp_DrawFunc)(bool forceDraw);
 
 class BFApp;
 class BFSoundManager;
@@ -50,12 +52,15 @@ public:
 class BFApp
 {
 public:
+	CritSect				mCritSect;
 	String					mTitle;
 	String					mInstallDir;
 	String					mDataDir;
 	bool					mDrawEnabled;
 	float					mRefreshRate;	
-	int						mMaxUpdatesPerDraw;
+	int						mMaxUpdatesPerDraw;	
+	double					mUpdateCntF;
+	double					mClientUpdateCntF;
 
 	bool					mInProcess;
 	bool					mRunning;
@@ -63,16 +68,20 @@ public:
 	int						mSysDialogCnt;
 	int						mUpdateCnt;
 	int						mNumPhysUpdates;
+	SyncEvent				mVSyncEvent;
+	volatile bool			mVSyncActive;
 	bool                    mVSynched;
 	bool					mVSyncFailed;
+	bool					mForceNextDraw;
 
 	int                     mUpdateSampleCount;
 	int                     mUpdateSampleTimes;
 
 	uint32 mLastProcessTick;
-	float mFrameTimeAcc;
+	float mPhysFrameTimeAcc;
 
 	BFApp_UpdateFunc		mUpdateFunc;
+	BFApp_UpdateFFunc		mUpdateFFunc;
 	BFApp_DrawFunc			mDrawFunc;
 	int						mCursor;
 
@@ -81,6 +90,7 @@ public:
 
 public:
 	virtual void			Update(bool batchStart);
+	virtual void			UpdateF(float updatePct);
 	virtual void			Draw();
 	virtual void			Process();
 	virtual void			PhysSetCursor() = 0;
@@ -114,6 +124,8 @@ public:
 	virtual FileStream*		OpenBinaryFile(const StringImpl& fileName);
 
 	virtual BFSoundManager* GetSoundManager() { return NULL; }
+
+	virtual intptr			GetCriticalThreadId(int idx) { return 0; }
 };
 
 extern BFApp* gBFApp;

+ 7 - 1
BeefySysLib/BeefySysLib.cpp

@@ -210,9 +210,10 @@ BF_EXPORT const char* BF_CALLTYPE BFApp_GetDataDir()
 	return gBFApp->mDataDir.c_str();
 }
 
-BF_EXPORT void BF_CALLTYPE BFApp_SetCallbacks(BFApp_UpdateFunc updateFunc, BFApp_DrawFunc drawFunc)
+BF_EXPORT void BF_CALLTYPE BFApp_SetCallbacks(BFApp_UpdateFunc updateFunc, BFApp_UpdateFFunc updateFFunc, BFApp_DrawFunc drawFunc)
 {
 	gBFApp->mUpdateFunc = updateFunc;
+	gBFApp->mUpdateFFunc = updateFFunc;
 	gBFApp->mDrawFunc = drawFunc;
 	//public delegate void UpdateProc();
 }
@@ -276,6 +277,11 @@ BF_EXPORT BFSoundManager* BF_CALLTYPE BFApp_GetSoundManager()
 	return gBFApp->GetSoundManager();
 }
 
+BF_EXPORT intptr BF_CALLTYPE BFApp_GetCriticalThreadId(int idx)
+{
+	return gBFApp->GetCriticalThreadId(idx);
+}
+
 ///
 
 BF_EXPORT void BF_CALLTYPE BFWindow_SetCallbacks(BFWindow* window, BFWindow_MovedFunc movedFunc, BFWindow_CloseQueryFunc closeQueryFunc, BFWindow_ClosedFunc closedFunc, 

+ 1 - 0
BeefySysLib/gfx/RenderDevice.cpp

@@ -62,6 +62,7 @@ RenderDevice::RenderDevice() :
 	mCurRenderTarget = NULL;		
 	mCurDrawLayer = NULL;
 	mPhysRenderWindow = NULL;	
+	mApp = NULL;
 }
 
 RenderDevice::~RenderDevice()

+ 12 - 1
BeefySysLib/gfx/RenderDevice.h

@@ -76,6 +76,8 @@ public:
 	virtual void			SetAsTarget() = 0;
 	virtual void			Resized() = 0;
 	virtual void			Present() = 0;
+	virtual float			GetRefreshRate() { return 60.0f; }
+	virtual bool			WaitForVBlank() { return false; }
 };
 
 const int DRAWBUFFER_IDXBUFFER_SIZE = 8*1024;
@@ -175,6 +177,14 @@ public:
 		mElementData = NULL;
 		mNumElements = 0;
 	}
+
+	VertexDefinition(VertexDefinition* src)
+	{
+		mElementData = new VertexDefData[src->mNumElements];
+		mNumElements = src->mNumElements;
+		memcpy(mElementData, src->mElementData, sizeof(VertexDefData) * mNumElements);
+	}
+
 	virtual ~VertexDefinition()
 	{
 		delete [] mElementData;
@@ -262,6 +272,7 @@ class RenderDevice
 public:	
 	Array<DrawBatch*>		mDrawBatchPool;	
 	
+	BFApp*					mApp;
 	RenderWindow*			mPhysRenderWindow;
 	RenderState*			mPhysRenderState;
 	int						mResizeCount;
@@ -283,7 +294,7 @@ public:
 public:
 	RenderDevice();
 	virtual ~RenderDevice();
-	virtual bool			Init(BFApp* app) = 0;	
+	virtual bool			Init(BFApp* app) = 0;
 	virtual void			AddRenderWindow(RenderWindow* renderWindow);
 	virtual void			RemoveRenderWindow(RenderWindow* renderWindow);
 	

+ 455 - 250
BeefySysLib/platform/win/DXRenderDevice.cpp

@@ -18,6 +18,7 @@ using namespace DirectX;
 //#include <D3DX11async.h>
 //#include <D3DX10math.h>
 //#include <DxErr.h>
+#include <dxgi1_3.h>
 #pragma warning(pop)
 
 #include "util/AllocDebug.h"
@@ -192,6 +193,7 @@ void DXShaderParam::SetFloat4(float x, float y, float z, float w)
 DXShader::DXShader()
 {
 	//? mD3DEffect = NULL;
+	mVertexDef = NULL;
 	mD3DPixelShader = NULL;
 	mD3DVertexShader = NULL;
 	mD3DLayout = NULL;
@@ -201,23 +203,233 @@ DXShader::DXShader()
 
 DXShader::~DXShader()
 {
-	DXShaderParamMap::iterator itr = mParamsMap.begin();
-	while (itr != mParamsMap.end())
-	{
-		delete itr->second;
-		++itr;
-	}
+	delete mVertexDef;
+	ReleaseNative();
+		
+	//? if (mD3DEffect != NULL)
+	//? 	mD3DEffect->Release();	
+}
+
+void DXShader::ReleaseNative()
+{
 	if (mD3DLayout != NULL)
 		mD3DLayout->Release();
+	mD3DLayout = NULL;
 	if (mD3DVertexShader != NULL)
 		mD3DVertexShader->Release();
+	mD3DVertexShader = NULL;
 	if (mD3DPixelShader != NULL)
 		mD3DPixelShader->Release();
+	mD3DPixelShader = NULL;
 	if (mConstBuffer != NULL)
 		mConstBuffer->Release();
+	mConstBuffer = NULL;
+}
 
-	//? if (mD3DEffect != NULL)
-	//? 	mD3DEffect->Release();	
+extern "C"
+typedef HRESULT(WINAPI* Func_D3DX10CompileFromFileW)(LPCWSTR pSrcFile, CONST D3D10_SHADER_MACRO* pDefines, LPD3D10INCLUDE pInclude,
+	LPCSTR pFunctionName, LPCSTR pProfile, UINT Flags1, UINT Flags2, ID3D10Blob** ppShader, ID3D10Blob** ppErrorMsgs);
+
+static Func_D3DX10CompileFromFileW gFunc_D3DX10CompileFromFileW;
+
+static bool LoadDXShader(const StringImpl& filePath, const StringImpl& entry, const StringImpl& profile, ID3D10Blob** outBuffer)
+{
+	HRESULT hr;
+	String outObj = filePath + "_" + entry + "_" + profile;
+
+	bool useCache = false;
+	auto srcDate = ::BfpFile_GetTime_LastWrite(filePath.c_str());
+	auto cacheDate = ::BfpFile_GetTime_LastWrite(outObj.c_str());
+	if (cacheDate >= srcDate)
+		useCache = true;
+
+	if (!useCache)
+	{
+		if (gFunc_D3DX10CompileFromFileW == NULL)
+		{
+			auto lib = LoadLibraryA("D3DCompiler_47.dll");
+			if (lib != NULL)
+				gFunc_D3DX10CompileFromFileW = (Func_D3DX10CompileFromFileW)::GetProcAddress(lib, "D3DCompileFromFile");
+		}
+
+		if (gFunc_D3DX10CompileFromFileW == NULL)
+			useCache = true;
+	}
+
+	if (!useCache)
+	{
+		if (gFunc_D3DX10CompileFromFileW == NULL)
+		{
+			auto lib = LoadLibraryA("D3DCompiler_47.dll");
+			if (lib != NULL)
+				gFunc_D3DX10CompileFromFileW = (Func_D3DX10CompileFromFileW)::GetProcAddress(lib, "D3DCompileFromFile");
+		}
+
+		ID3D10Blob* errorMessage = NULL;
+		auto dxResult = gFunc_D3DX10CompileFromFileW(UTF8Decode(filePath).c_str(), NULL, NULL, entry.c_str(), profile.c_str(),
+			D3D10_SHADER_DEBUG | D3D10_SHADER_ENABLE_STRICTNESS, 0, outBuffer, &errorMessage);
+
+		if (DXFAILED(dxResult))
+		{
+			if (errorMessage != NULL)
+			{
+				BF_FATAL(StrFormat("Vertex shader load failed: %s", (char*)errorMessage->GetBufferPointer()).c_str());
+				errorMessage->Release();
+			}
+			else
+				BF_FATAL("Shader load failed");
+			return false;
+		}
+
+		auto ptr = (*outBuffer)->GetBufferPointer();
+		int size = (int)(*outBuffer)->GetBufferSize();
+
+		FILE* fp = fopen(outObj.c_str(), "wb");
+		if (fp != NULL)
+		{
+			fwrite(ptr, 1, size, fp);
+			fclose(fp);
+		}
+		return true;
+	}
+
+	FILE* fp = fopen(outObj.c_str(), "rb");
+	if (fp == NULL)
+	{
+		BF_FATAL("Failed to load compiled shader");
+		return false;
+	}
+
+	fseek(fp, 0, SEEK_END);
+	int size = ftell(fp);
+	fseek(fp, 0, SEEK_SET);
+	D3D10CreateBlob(size, outBuffer);
+	auto ptr = (*outBuffer)->GetBufferPointer();
+	fread(ptr, 1, size, fp);
+	fclose(fp);
+
+	return true;
+}
+
+bool DXShader::Load()
+{
+	//HRESULT hr;
+
+	ID3D10Blob* errorMessage = NULL;
+	ID3D10Blob* vertexShaderBuffer = NULL;
+	ID3D10Blob* pixelShaderBuffer = NULL;
+
+	LoadDXShader(mSrcPath + ".fx", "VS", "vs_4_0", &vertexShaderBuffer);
+	LoadDXShader(mSrcPath + ".fx", "PS", "ps_4_0", &pixelShaderBuffer);
+
+	defer(
+		{
+			vertexShaderBuffer->Release();
+			pixelShaderBuffer->Release();
+		});
+
+	mHas2DPosition = false;
+	mVertexSize = 0;
+	mD3DLayout = NULL;
+
+	static const char* semanticNames[] = {
+		"POSITION",
+		"POSITION",
+		"COLOR",
+		"TEXCOORD",
+		"NORMAL",
+		"BINORMAL",
+		"TANGENT",
+		"BLENDINDICES",
+		"BLENDWEIGHT",
+		"DEPTH",
+		"FOG",
+		"POINTSIZE",
+		"SAMPLE",
+		"TESSELLATEFACTOR" };
+
+	static const DXGI_FORMAT dxgiFormat[] = {
+		DXGI_FORMAT_R32_FLOAT/*VertexElementFormat_Single*/,
+		DXGI_FORMAT_R32G32_FLOAT/*VertexElementFormat_Vector2*/,
+		DXGI_FORMAT_R32G32B32_FLOAT/*VertexElementFormat_Vector3*/,
+		DXGI_FORMAT_R32G32B32A32_FLOAT/*VertexElementFormat_Vector4*/,
+		DXGI_FORMAT_R8G8B8A8_UNORM/*VertexElementFormat_Color*/,
+		DXGI_FORMAT_R8G8B8A8_UINT/*VertexElementFormat_Byte4*/,
+		DXGI_FORMAT_R16G16_UINT/*VertexElementFormat_Short2*/,
+		DXGI_FORMAT_R16G16B16A16_UINT/*VertexElementFormat_Short4*/,
+		DXGI_FORMAT_R16G16_UNORM/*VertexElementFormat_NormalizedShort2*/,
+		DXGI_FORMAT_R16G16B16A16_UNORM/*VertexElementFormat_NormalizedShort4*/,
+		DXGI_FORMAT_R16G16_FLOAT/*VertexElementFormat_HalfVector2*/,
+		DXGI_FORMAT_R16G16B16A16_FLOAT/*VertexElementFormat_HalfVector4*/
+	};
+
+	static const int dxgiSize[] = {
+		sizeof(float) * 1/*VertexElementFormat_Single*/,
+		sizeof(float) * 2/*VertexElementFormat_Vector2*/,
+		sizeof(float) * 3/*VertexElementFormat_Vector3*/,
+		sizeof(float) * 4/*VertexElementFormat_Vector4*/,
+		sizeof(uint32)/*VertexElementFormat_Color*/,
+		sizeof(uint8) * 4/*VertexElementFormat_Byte4*/,
+		sizeof(uint16) * 2/*VertexElementFormat_Short2*/,
+		sizeof(uint16) * 4/*VertexElementFormat_Short4*/,
+		sizeof(uint16) * 2/*VertexElementFormat_NormalizedShort2*/,
+		sizeof(uint16) * 4/*VertexElementFormat_NormalizedShort4*/,
+		sizeof(uint16) * 2/*VertexElementFormat_HalfVector2*/,
+		sizeof(uint16) * 4/*VertexElementFormat_HalfVector4*/
+	};
+
+	D3D11_INPUT_ELEMENT_DESC layout[64];
+	for (int elementIdx = 0; elementIdx < mVertexDef->mNumElements; elementIdx++)
+	{
+		VertexDefData* vertexDefData = &mVertexDef->mElementData[elementIdx];
+
+		if (vertexDefData->mUsage == VertexElementUsage_Position2D)
+			mHas2DPosition = true;
+
+		D3D11_INPUT_ELEMENT_DESC* elementDesc = &layout[elementIdx];
+		elementDesc->SemanticName = semanticNames[vertexDefData->mUsage];
+		elementDesc->SemanticIndex = vertexDefData->mUsageIndex;
+		elementDesc->Format = dxgiFormat[vertexDefData->mFormat];
+		elementDesc->InputSlot = 0;
+		elementDesc->AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
+		elementDesc->InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+		elementDesc->InstanceDataStepRate = 0;
+		mVertexSize += dxgiSize[vertexDefData->mFormat];
+	}
+
+	/* =
+	{
+		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+		{ "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM,  0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+	};
+	UINT numElements = sizeof(layout) / sizeof(layout[0]);*/
+	HRESULT result = mRenderDevice->mD3DDevice->CreateInputLayout(layout, mVertexDef->mNumElements, vertexShaderBuffer->GetBufferPointer(),
+		vertexShaderBuffer->GetBufferSize(), &mD3DLayout);
+	DXCHECK(result);
+	if (FAILED(result))
+		return false;
+
+	// Create the vertex shader from the buffer.
+	result = mRenderDevice->mD3DDevice->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &mD3DVertexShader);
+	DXCHECK(result);
+	if (FAILED(result))
+		return false;
+
+	// Create the pixel shader from the buffer.
+	result = mRenderDevice->mD3DDevice->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &mD3DPixelShader);
+	DXCHECK(result);
+	if (FAILED(result))
+		return false;	
+
+	Init();	
+	return true;
+}
+
+void DXShader::ReinitNative()
+{
+	ReleaseNative();
+	Load();
 }
 
 ShaderParam* DXShader::GetShaderParam(const StringImpl& name)
@@ -248,8 +460,8 @@ DXTexture::DXTexture()
 	mD3DRenderTargetView = NULL;	
 	mRenderDevice = NULL;
 	mD3DDepthBuffer = NULL;
-	mD3DDepthStencilView = NULL;	
-	mImageData = NULL;
+	mD3DDepthStencilView = NULL;		
+	mContentBits = NULL;
 }
 
 DXTexture::~DXTexture()
@@ -258,7 +470,7 @@ DXTexture::~DXTexture()
 		((DXRenderDevice*)mRenderDevice)->mTextureMap.Remove(mPath);
 
 	//OutputDebugStrF("DXTexture::~DXTexture %@\n", this);
-	delete mImageData;
+	delete mContentBits;
 	if (mD3DResourceView != NULL)
 		mD3DResourceView->Release();
 	if (mD3DDepthStencilView != NULL)
@@ -267,6 +479,8 @@ DXTexture::~DXTexture()
 		mD3DDepthBuffer->Release();
 	if (mD3DTexture != NULL)
 		mD3DTexture->Release();
+	if (mRenderDevice != NULL)
+		mRenderDevice->mTextures.Remove(this);
 }
 
 void DXTexture::ReleaseNative()
@@ -297,6 +511,44 @@ void DXTexture::ReleaseNative()
 
 void DXTexture::ReinitNative()
 {
+	ReleaseNative();
+
+	int aWidth = 0;
+	int aHeight = 0;
+
+	D3D11_SUBRESOURCE_DATA resData;
+	resData.pSysMem = mContentBits;
+	resData.SysMemPitch = mWidth * 4;
+	resData.SysMemSlicePitch = mWidth * mHeight * 4;
+
+	// Create the target texture
+	D3D11_TEXTURE2D_DESC desc;
+	ZeroMemory(&desc, sizeof(desc));
+	desc.Width = mWidth;
+	desc.Height = mHeight;
+	desc.MipLevels = 1;
+	desc.ArraySize = 1;
+	desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+	desc.SampleDesc.Count = 1;
+	desc.Usage = D3D11_USAGE_DEFAULT;
+	desc.CPUAccessFlags = 0;
+	desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+
+	//OutputDebugStrF("Creating texture\n");
+
+	auto dxRenderDevice = (DXRenderDevice*)mRenderDevice;
+	
+	DXCHECK(dxRenderDevice->mD3DDevice->CreateTexture2D(&desc, (mContentBits != NULL) ? &resData : NULL, &mD3DTexture));
+	
+	D3D11_SHADER_RESOURCE_VIEW_DESC srDesc;
+	srDesc.Format = desc.Format;
+	srDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+	srDesc.Texture2D.MostDetailedMip = 0;
+	srDesc.Texture2D.MipLevels = 1;
+	
+	DXCHECK(dxRenderDevice->mD3DDevice->CreateShaderResourceView(mD3DTexture, &srDesc, &mD3DResourceView));
+
+	OutputDebugStrF("DXTexture::ReinitNative %p\n", this);
 }
 
 void DXTexture::PhysSetAsTarget()
@@ -338,12 +590,24 @@ void DXTexture::Blt(ImageData* imageData, int x, int y)
 	box.front = 0;
 	box.back = 1;
 	mRenderDevice->mD3DDeviceContext->UpdateSubresource(mD3DTexture, 0, &box, imageData->mBits, imageData->mWidth * sizeof(uint32), 0);	
+
+	if (mContentBits != NULL)
+	{
+		for (int yOfs = 0; yOfs < imageData->mHeight; yOfs++)
+			memcpy(mContentBits + x + (y + yOfs) * mWidth, imageData->mBits + yOfs * imageData->mWidth, imageData->mWidth * 4);
+	}
 }
 
 void DXTexture::SetBits(int destX, int destY, int destWidth, int destHeight, int srcPitch, uint32* bits)
 {
 	D3D11_BOX box = { (UINT)destX, (UINT)destY, (UINT)0, (UINT)(destX + destWidth), (UINT)(destY + destHeight), 1 };
 	mRenderDevice->mD3DDeviceContext->UpdateSubresource(mD3DTexture, 0, &box, bits, srcPitch * sizeof(uint32), 0);
+
+	if (mContentBits != NULL)
+	{
+		for (int y = 0; y < destHeight; y++)
+			memcpy(mContentBits + destX + (destY + y) * mWidth, bits + (y * srcPitch), destWidth * 4);
+	}
 }
 
 void DXTexture::GetBits(int srcX, int srcY, int srcWidth, int srcHeight, int destPitch, uint32* bits)
@@ -367,7 +631,7 @@ void DXTexture::GetBits(int srcX, int srcY, int srcWidth, int srcHeight, int des
 	srcBox.top = srcY;
 	srcBox.right = srcX + srcWidth;
 	srcBox.bottom = srcY + srcHeight;
-	srcBox.back = 1;
+	srcBox.back = 1;	
 
 	ID3D11Texture2D *texture;
 	DXCHECK(mRenderDevice->mD3DDevice->CreateTexture2D(&texDesc, 0, &texture));
@@ -532,20 +796,18 @@ void DXRenderDevice::PhysSetRenderState(RenderState* renderState)
 		{
 			HRESULT result = NULL;
 
-			D3D11_BUFFER_DESC matrixBufferDesc;
-			matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
-			matrixBufferDesc.ByteWidth = sizeof(float[4]);
-			matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
-			matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
-			matrixBufferDesc.MiscFlags = 0;
-			matrixBufferDesc.StructureByteStride = 0;
-
-			static ID3D11Buffer* matrixBuffer = NULL;
-
-			if (matrixBuffer == NULL)
+			if (mMatrix2DBuffer == NULL)
 			{
+				D3D11_BUFFER_DESC matrixBufferDesc;
+				matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
+				matrixBufferDesc.ByteWidth = sizeof(float[4]);
+				matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+				matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+				matrixBufferDesc.MiscFlags = 0;
+				matrixBufferDesc.StructureByteStride = 0;
+						
 				// Create the constant buffer pointer so we can access the vertex shader constant buffer from within this class.
-				result = mD3DDevice->CreateBuffer(&matrixBufferDesc, NULL, &matrixBuffer);
+				result = mD3DDevice->CreateBuffer(&matrixBufferDesc, NULL, &mMatrix2DBuffer);
 				if (FAILED(result))
 				{
 					return;
@@ -554,7 +816,7 @@ void DXRenderDevice::PhysSetRenderState(RenderState* renderState)
 
 			// Lock the constant buffer so it can be written to.
 			D3D11_MAPPED_SUBRESOURCE mappedResource;
-			result = mD3DDeviceContext->Map(matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
+			result = mD3DDeviceContext->Map(mMatrix2DBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
 			if (FAILED(result))
 			{
 				return;
@@ -568,10 +830,10 @@ void DXRenderDevice::PhysSetRenderState(RenderState* renderState)
 			dataPtr[3] = 0;
 
 			// Unlock the constant buffer.
-			mD3DDeviceContext->Unmap(matrixBuffer, 0);
+			mD3DDeviceContext->Unmap(mMatrix2DBuffer, 0);
 
 			//float params[4] = {mCurRenderTarget->mWidth, mCurRenderTarget->mHeight, 0, 0};
-			mD3DDeviceContext->VSSetConstantBuffers(0, 1, &matrixBuffer);
+			mD3DDeviceContext->VSSetConstantBuffers(0, 1, &mMatrix2DBuffer);
 		}
 	}
 
@@ -1003,7 +1265,7 @@ void DXRenderState::ReleaseNative()
 
 void DXRenderState::ReinitNative()
 {
-
+	ReleaseNative();
 }
 
 void DXRenderState::InvalidateRasterizerState() 
@@ -1264,6 +1526,8 @@ DXRenderWindow::DXRenderWindow(DXRenderDevice* renderDevice, WinBFWindow* window
 	mD3DRenderTargetView = NULL;	
 	mD3DDepthBuffer = NULL;
 	mD3DDepthStencilView = NULL;
+	mRefreshRate = 0;
+	mFrameWaitObject = NULL;	
 
 	mRenderDevice = renderDevice;
 	mDXRenderDevice = renderDevice;
@@ -1275,30 +1539,31 @@ DXRenderWindow::DXRenderWindow(DXRenderDevice* renderDevice, WinBFWindow* window
 	ReinitNative();
 }
 
-
 DXRenderWindow::~DXRenderWindow()
 {
 	ReleaseNative();
 }
 
-
 void DXRenderWindow::ReleaseNative()
 {
-	if (mD3DRenderTargetView != NULL)
-	{
+	if (mFrameWaitObject != NULL)
+		::CloseHandle(mFrameWaitObject);
+	mFrameWaitObject = NULL;
+	if (mD3DRenderTargetView != NULL)	
 		mD3DRenderTargetView->Release();
-		mD3DRenderTargetView = NULL;
-	}
-	if (mD3DBackBuffer != NULL)
-	{
+	mD3DRenderTargetView = NULL;	
+	if (mD3DBackBuffer != NULL)	
 		mD3DBackBuffer->Release();
-		mD3DBackBuffer = NULL;
-	}
-	if (mDXSwapChain != NULL)
-	{
+	mD3DBackBuffer = NULL;	
+	if (mDXSwapChain != NULL)	
 		mDXSwapChain->Release();
-		mDXSwapChain = NULL;
-	}
+	mDXSwapChain = NULL;	
+	if (mD3DRenderTargetView != NULL)
+		mD3DRenderTargetView->Release();
+	mD3DRenderTargetView = NULL;
+	if (mD3DDepthStencilView != NULL)
+		mD3DDepthStencilView->Release();
+	mD3DDepthStencilView = NULL;
 }
 
 void DXRenderWindow::ReinitNative()
@@ -1314,8 +1579,8 @@ void DXRenderWindow::ReinitNative()
 	swapChainDesc.SampleDesc.Count = 1;
 	swapChainDesc.SampleDesc.Quality = 0;
 	swapChainDesc.Windowed = mWindowed ? TRUE : FALSE;
-	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
-	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
+	swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;// DXGI_SWAP_EFFECT_FLIP_DISCARD;
+	swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH /*| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT*/;
 
 	IDXGIDevice* pDXGIDevice = NULL;
 	mDXRenderDevice->mD3DDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&pDXGIDevice);
@@ -1324,6 +1589,14 @@ void DXRenderWindow::ReinitNative()
 	pDXGIDevice->Release();
 	pDXGIDevice = NULL;
 
+// 	IDXGISwapChain2* swapChain2 = NULL;
+// 	mDXSwapChain->QueryInterface(__uuidof(IDXGISwapChain2), (void**)&swapChain2);
+// 	if (swapChain2 != NULL)
+// 	{
+// 		mFrameWaitObject = swapChain2->GetFrameLatencyWaitableObject();
+// 		swapChain2->Release();
+// 	}
+	
 	DXCHECK(mDXSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&mD3DBackBuffer));
 	DXCHECK(mDXRenderDevice->mD3DDevice->CreateRenderTargetView(mD3DBackBuffer, NULL, &mD3DRenderTargetView));
 
@@ -1406,7 +1679,14 @@ void DXRenderWindow::Resized()
 		mD3DDepthBuffer->Release();
 		mD3DRenderTargetView->Release();		
 		mD3DDepthStencilView->Release();
-		DXCHECK(mDXSwapChain->ResizeBuffers(0, mWidth, mHeight, DXGI_FORMAT_UNKNOWN, 0));
+
+		HRESULT hr = mDXSwapChain->ResizeBuffers(0, mWidth, mHeight, DXGI_FORMAT_UNKNOWN,
+			DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH /*| DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT*/);
+
+		if ((hr == DXGI_ERROR_DEVICE_REMOVED) || (hr == DXGI_ERROR_DEVICE_RESET))
+			((DXRenderDevice*)mRenderDevice)->mNeedsReinitNative = true;
+		else
+			DXCHECK(hr);
 		
 		D3D11_TEXTURE2D_DESC descDepth;
 		ZeroMemory(&descDepth, sizeof(descDepth));
@@ -1435,8 +1715,10 @@ void DXRenderWindow::Resized()
 
 void DXRenderWindow::Present()
 {	
-	mDXSwapChain->Present((mWindow->mFlags & BFWINDOW_VSYNC) ? 1 : 0, 0);
-	//DXCHECK();
+	HRESULT hr = mDXSwapChain->Present((mWindow->mFlags & BFWINDOW_VSYNC) ? 1 : 0, 0);
+
+	if ((hr == DXGI_ERROR_DEVICE_REMOVED) || (hr == DXGI_ERROR_DEVICE_RESET))
+		((DXRenderDevice*)mRenderDevice)->mNeedsReinitNative = true;
 }
 
 void DXRenderWindow::CopyBitsTo(uint32* dest, int width, int height)
@@ -1476,28 +1758,110 @@ void DXRenderWindow::CopyBitsTo(uint32* dest, int width, int height)
 	texture->Release();
 }
 
+float DXRenderWindow::GetRefreshRate()
+{
+	if (mRefreshRate == 0)
+	{
+		mRefreshRate = -1;
+
+		IDXGIOutput* output = NULL;
+		mDXSwapChain->GetContainingOutput(&output);
+		if (output != NULL)
+		{
+			DXGI_OUTPUT_DESC outputDesc;
+			output->GetDesc(&outputDesc);
+
+			MONITORINFOEXW info;
+			info.cbSize = sizeof(info);
+			// get the associated monitor info
+			if (GetMonitorInfoW(outputDesc.Monitor, &info) != 0)
+			{
+				// using the CCD get the associated path and display configuration
+				UINT32 requiredPaths, requiredModes;
+				if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, &requiredModes) == ERROR_SUCCESS)
+				{
+					std::vector<DISPLAYCONFIG_PATH_INFO> paths(requiredPaths);
+					std::vector<DISPLAYCONFIG_MODE_INFO> modes2(requiredModes);
+					if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &requiredPaths, paths.data(), &requiredModes, modes2.data(), nullptr) == ERROR_SUCCESS)
+					{
+						// iterate through all the paths until find the exact source to match
+						for (auto& p : paths)
+						{
+							DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
+							sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+							sourceName.header.size = sizeof(sourceName);
+							sourceName.header.adapterId = p.sourceInfo.adapterId;
+							sourceName.header.id = p.sourceInfo.id;
+							if (DisplayConfigGetDeviceInfo(&sourceName.header) == ERROR_SUCCESS)
+							{
+								// find the matched device which is associated with current device 
+								// there may be the possibility that display may be duplicated and windows may be one of them in such scenario
+								// there may be two callback because source is same target will be different
+								// as window is on both the display so either selecting either one is ok
+								if (wcscmp(info.szDevice, sourceName.viewGdiDeviceName) == 0)
+								{
+									// get the refresh rate
+									UINT numerator = p.targetInfo.refreshRate.Numerator;
+									UINT denominator = p.targetInfo.refreshRate.Denominator;
+									mRefreshRate = (float)numerator / (float)denominator;
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+
+			output->Release();
+		}
+	}
+
+	return mRefreshRate;
+}
+
+bool DXRenderWindow::WaitForVBlank()
+{
+	IDXGIOutput* output = NULL;
+	mDXSwapChain->GetContainingOutput(&output);
+	if (output == NULL)
+		return false;		
+	bool success = output->WaitForVBlank() == 0;
+	return success;
+}
+
 ///
 
 DXRenderDevice::DXRenderDevice()
 {	
-	mD3DDevice = NULL;
-	
+	mD3DDevice = NULL;	
+	mNeedsReinitNative = false;
+	mMatrix2DBuffer = NULL;
 }
 
 DXRenderDevice::~DXRenderDevice()
 {
-	for (auto& kv : mTextureMap)
-		kv.mValue->mRenderDevice = NULL;
+	for (auto window : mRenderWindowList)
+		((DXRenderWindow*)window)->ReleaseNative();
+	for (auto shader : mShaders)
+		shader->ReleaseNative();
+	for (auto renderState : mRenderStates)
+		renderState->ReleaseNative();
+	for (auto texture : mTextures)
+	{
+		texture->ReleaseNative();
+		texture->mRenderDevice = NULL;
+	}
 
-	mD3DVertexBuffer->Release();
-	mD3DIndexBuffer->Release();
-	delete mDefaultRenderState;
+	ReleaseNative();	
+	
+	delete mDefaultRenderState;	
 }
 
 bool DXRenderDevice::Init(BFApp* app)
 {
 	BP_ZONE("DXRenderDevice::Init");
 
+	mApp = app;
 	WinBFApp* winApp = (WinBFApp*) app;
 	
 	D3D_FEATURE_LEVEL featureLevelArr[] =
@@ -1626,6 +1990,9 @@ void DXRenderDevice::ReleaseNative()
 {
 	mD3DVertexBuffer->Release();
 	mD3DVertexBuffer = NULL;
+	if (mMatrix2DBuffer != NULL)
+		mMatrix2DBuffer->Release();
+	mMatrix2DBuffer = NULL;
 	mD3DIndexBuffer->Release();
 	mD3DIndexBuffer = NULL;
 	mD3DNormalBlendState->Release();
@@ -1636,20 +2003,37 @@ void DXRenderDevice::ReleaseNative()
 	mD3DWrapSamplerState = NULL;
 	mD3DDeviceContext->Release();
 	mD3DDeviceContext = NULL;
+
+// 	ID3D11Debug* debug = NULL;
+// 	mD3DDevice->QueryInterface(__uuidof(ID3D11Debug), (void**)&debug);
+// 	if (debug != NULL)
+// 	{
+// 		debug->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
+// 		debug->Release();
+// 	}
+
 	mD3DDevice->Release();
 	mD3DDevice = NULL;
 }
 
 void DXRenderDevice::ReinitNative()
 {
-	Init(NULL);
+	AutoCrit autoCrit(mApp->mCritSect);
+
+	if (mMatrix2DBuffer != NULL)
+		mMatrix2DBuffer->Release();
+	mMatrix2DBuffer = NULL;
+	
+	Init(mApp);
 	
 	for (auto window : mRenderWindowList)
 		((DXRenderWindow*)window)->ReinitNative();
-	for (auto kv : mRenderStates)
-		kv->ReinitNative();
-	for (auto kv : mRenderStates)
-		kv->ReinitNative();
+	for (auto shader : mShaders)
+		shader->ReinitNative();
+	for (auto renderState : mRenderStates)
+		renderState->ReinitNative();
+	for (auto tex : mTextures)
+		tex->ReinitNative();
 }
 
 void DXRenderDevice::FrameStart()
@@ -1918,6 +2302,10 @@ Texture* DXRenderDevice::LoadTexture(ImageData* imageData, int flags)
 	DXCHECK(mD3DDevice->CreateShaderResourceView(d3DTexture, &srDesc, &d3DShaderResourceView));
 
 	DXTexture* aTexture = new DXTexture();
+
+	aTexture->mContentBits = new uint32[aWidth * aHeight];
+	memcpy(aTexture->mContentBits, imageData->mBits, aWidth * aHeight * 4);
+
 	aTexture->mRenderDevice = this;
 	aTexture->mWidth = aWidth;
 	aTexture->mHeight = aHeight;	
@@ -1976,204 +2364,21 @@ Texture* DXRenderDevice::CreateDynTexture(int width, int height)
 	return aTexture;
 }
 
-extern "C"
-typedef HRESULT (WINAPI* Func_D3DX10CompileFromFileW)(LPCWSTR pSrcFile, CONST D3D10_SHADER_MACRO* pDefines, LPD3D10INCLUDE pInclude,
-	LPCSTR pFunctionName, LPCSTR pProfile, UINT Flags1, UINT Flags2, ID3D10Blob** ppShader, ID3D10Blob** ppErrorMsgs);
-
-static Func_D3DX10CompileFromFileW gFunc_D3DX10CompileFromFileW;
-
-static bool LoadDXShader(const StringImpl& filePath, const StringImpl& entry, const StringImpl& profile, ID3D10Blob** outBuffer)
-{
-	HRESULT hr;
-	String outObj = filePath + "_" + entry + "_" + profile;
-
-	bool useCache = false;
-	auto srcDate = ::BfpFile_GetTime_LastWrite(filePath.c_str());
-	auto cacheDate = ::BfpFile_GetTime_LastWrite(outObj.c_str());
-	if (cacheDate >= srcDate)
-		useCache = true;
-
-	if (!useCache)
-	{
-		if (gFunc_D3DX10CompileFromFileW == NULL)
-		{
-			auto lib = LoadLibraryA("D3DCompiler_47.dll");
-			if (lib != NULL)
-				gFunc_D3DX10CompileFromFileW = (Func_D3DX10CompileFromFileW)::GetProcAddress(lib, "D3DCompileFromFile");
-		}
-
-		if (gFunc_D3DX10CompileFromFileW == NULL)
-			useCache = true;
-	}
-
-	if (!useCache)
-	{
-		if (gFunc_D3DX10CompileFromFileW == NULL)
-		{
-			auto lib = LoadLibraryA("D3DCompiler_47.dll");
-			if (lib != NULL)
-				gFunc_D3DX10CompileFromFileW = (Func_D3DX10CompileFromFileW)::GetProcAddress(lib, "D3DCompileFromFile");
-		}
-
-		ID3D10Blob* errorMessage = NULL;
-		auto dxResult = gFunc_D3DX10CompileFromFileW(UTF8Decode(filePath).c_str(), NULL, NULL, entry.c_str(), profile.c_str(),
-			D3D10_SHADER_DEBUG | D3D10_SHADER_ENABLE_STRICTNESS, 0, outBuffer, &errorMessage);
-
-		if (DXFAILED(dxResult))
-		{
-			if (errorMessage != NULL)
-			{
-				BF_FATAL(StrFormat("Vertex shader load failed: %s", (char*)errorMessage->GetBufferPointer()).c_str());
-				errorMessage->Release();
-			}
-			else
-				BF_FATAL("Shader load failed");
-			return false;
-		}
-
-		auto ptr = (*outBuffer)->GetBufferPointer();
-		int size = (int)(*outBuffer)->GetBufferSize();
-
-		FILE* fp = fopen(outObj.c_str(), "wb");
-		if (fp != NULL)
-		{
-			fwrite(ptr, 1, size, fp);
-			fclose(fp);
-		}
-		return true;
-	}
-	
-	FILE* fp = fopen(outObj.c_str(), "rb");
-	if (fp == NULL)
-	{
-		BF_FATAL("Failed to load compiled shader");
-		return false;
-	}
-	
-	fseek(fp, 0, SEEK_END);
-	int size = ftell(fp);
-	fseek(fp, 0, SEEK_SET);
-	D3D10CreateBlob(size, outBuffer);		
-	auto ptr = (*outBuffer)->GetBufferPointer();
-	fread(ptr, 1, size, fp);
-	fclose(fp);
-	
-	return true;
-}
-
 Shader* DXRenderDevice::LoadShader(const StringImpl& fileName, VertexDefinition* vertexDefinition)
 {
 	BP_ZONE("DXRenderDevice::LoadShader");
 
-	//HRESULT hr;
-	
-	ID3D10Blob* errorMessage = NULL;
-	ID3D10Blob* vertexShaderBuffer = NULL;
-	ID3D10Blob* pixelShaderBuffer = NULL;
-
-	LoadDXShader(fileName + ".fx", "VS", "vs_4_0", &vertexShaderBuffer);
-	LoadDXShader(fileName + ".fx", "PS", "ps_4_0", &pixelShaderBuffer);
-
 	DXShader* dxShader = new DXShader();
-
-	dxShader->mVertexSize = 0;
-	dxShader->mD3DLayout = NULL;
-
-	static const char* semanticNames[] = {
-		"POSITION",
-		"POSITION",
-		"COLOR",
-		"TEXCOORD",
-		"NORMAL",
-		"BINORMAL",
-		"TANGENT",
-		"BLENDINDICES",
-		"BLENDWEIGHT",
-		"DEPTH",
-		"FOG",
-		"POINTSIZE",
-		"SAMPLE",
-		"TESSELLATEFACTOR"};
-
-	static const DXGI_FORMAT dxgiFormat[] = {
-		DXGI_FORMAT_R32_FLOAT/*VertexElementFormat_Single*/,
-		DXGI_FORMAT_R32G32_FLOAT/*VertexElementFormat_Vector2*/,
-		DXGI_FORMAT_R32G32B32_FLOAT/*VertexElementFormat_Vector3*/,
-		DXGI_FORMAT_R32G32B32A32_FLOAT/*VertexElementFormat_Vector4*/,
-		DXGI_FORMAT_R8G8B8A8_UNORM/*VertexElementFormat_Color*/,
-		DXGI_FORMAT_R8G8B8A8_UINT/*VertexElementFormat_Byte4*/,
-		DXGI_FORMAT_R16G16_UINT/*VertexElementFormat_Short2*/,
-		DXGI_FORMAT_R16G16B16A16_UINT/*VertexElementFormat_Short4*/,
-		DXGI_FORMAT_R16G16_UNORM/*VertexElementFormat_NormalizedShort2*/,
-		DXGI_FORMAT_R16G16B16A16_UNORM/*VertexElementFormat_NormalizedShort4*/,
-		DXGI_FORMAT_R16G16_FLOAT/*VertexElementFormat_HalfVector2*/,
-		DXGI_FORMAT_R16G16B16A16_FLOAT/*VertexElementFormat_HalfVector4*/
-	};
-
-	static const int dxgiSize[] = {
-		sizeof(float) * 1/*VertexElementFormat_Single*/,
-		sizeof(float) * 2/*VertexElementFormat_Vector2*/,
-		sizeof(float) * 3/*VertexElementFormat_Vector3*/,
-		sizeof(float) * 4/*VertexElementFormat_Vector4*/,
-		sizeof(uint32)/*VertexElementFormat_Color*/,
-		sizeof(uint8) * 4/*VertexElementFormat_Byte4*/,
-		sizeof(uint16) * 2/*VertexElementFormat_Short2*/,
-		sizeof(uint16) * 4/*VertexElementFormat_Short4*/,
-		sizeof(uint16) * 2/*VertexElementFormat_NormalizedShort2*/,
-		sizeof(uint16) * 4/*VertexElementFormat_NormalizedShort4*/,
-		sizeof(uint16) * 2/*VertexElementFormat_HalfVector2*/,
-		sizeof(uint16) * 4/*VertexElementFormat_HalfVector4*/
-	};
-
-	D3D11_INPUT_ELEMENT_DESC layout[64];
-	for (int elementIdx = 0; elementIdx < vertexDefinition->mNumElements; elementIdx++)
+	dxShader->mRenderDevice = this;
+	dxShader->mSrcPath = fileName;
+	dxShader->mVertexDef = new VertexDefinition(vertexDefinition);
+	if (!dxShader->Load())
 	{
-		VertexDefData* vertexDefData = &vertexDefinition->mElementData[elementIdx];
-
-		if (vertexDefData->mUsage == VertexElementUsage_Position2D)
-			dxShader->mHas2DPosition = true;
-
-		D3D11_INPUT_ELEMENT_DESC* elementDesc = &layout[elementIdx];
-		elementDesc->SemanticName = semanticNames[vertexDefData->mUsage];
-		elementDesc->SemanticIndex = vertexDefData->mUsageIndex;
-		elementDesc->Format = dxgiFormat[vertexDefData->mFormat];
-		elementDesc->InputSlot = 0;
-		elementDesc->AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
-		elementDesc->InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
-		elementDesc->InstanceDataStepRate = 0;
-		dxShader->mVertexSize += dxgiSize[vertexDefData->mFormat];
-	}
-	
-	/* =
-	{
-		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
-		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
-		{ "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM,  0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
-	};
-	UINT numElements = sizeof(layout) / sizeof(layout[0]);*/
-	HRESULT result = mD3DDevice->CreateInputLayout(layout, vertexDefinition->mNumElements, vertexShaderBuffer->GetBufferPointer(),
-		vertexShaderBuffer->GetBufferSize(), &dxShader->mD3DLayout);
-	DXCHECK(result);
-	if (FAILED(result))
-		return NULL;
-
-	// Create the vertex shader from the buffer.
-	result = mD3DDevice->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, &dxShader->mD3DVertexShader);
-	DXCHECK(result);
-	if (FAILED(result))	
+		delete dxShader;
 		return NULL;
-
-	// Create the pixel shader from the buffer.
-	result = mD3DDevice->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, &dxShader->mD3DPixelShader);
-	DXCHECK(result);
-	if (FAILED(result))	
-		return NULL;
-	
-	vertexShaderBuffer->Release();
-	pixelShaderBuffer->Release();
-	
-	dxShader->Init();
-	return dxShader;	
+	}
+	mShaders.Add(dxShader);
+	return dxShader;
 }
 
 void DXRenderDevice::SetRenderState(RenderState* renderState)

+ 29 - 15
BeefySysLib/platform/win/DXRenderDevice.h

@@ -60,22 +60,22 @@ public:
 	DXRenderDevice*			mRenderDevice;
 	ID3D11Texture2D*		mD3DTexture;
 	ID3D11ShaderResourceView* mD3DResourceView;
-	ID3D11RenderTargetView*	mD3DRenderTargetView;	
+	ID3D11RenderTargetView*	mD3DRenderTargetView;
 	ID3D11Texture2D*		mD3DDepthBuffer;
-	ID3D11DepthStencilView*	mD3DDepthStencilView;	
-	ImageData*				mImageData;
+	ID3D11DepthStencilView*	mD3DDepthStencilView;
+	uint32*					mContentBits;
 
 public:
 	DXTexture();
 	~DXTexture();
-
-	void ReleaseNative();
-	void ReinitNative();
+	
+	void					ReleaseNative();
+	void					ReinitNative();
 
 	virtual void			PhysSetAsTarget() override;
 	virtual void			Blt(ImageData* imageData, int x, int y) override;
 	virtual void			SetBits(int destX, int destY, int destWidth, int destHeight, int srcPitch, uint32* bits) override;
-	virtual void			GetBits(int srcX, int srcY, int srcWidth, int srcHeight, int destPitch, uint32* bits) override;
+	virtual void			GetBits(int srcX, int srcY, int srcWidth, int srcHeight, int destPitch, uint32* bits) override;	
 };
 
 class DXShaderParam : public ShaderParam
@@ -95,7 +95,11 @@ typedef std::map<String, DXShaderParam*> DXShaderParamMap;
 
 class DXShader : public Shader
 {
-public:		
+public:	
+	DXRenderDevice*			mRenderDevice;
+	String					mSrcPath;
+	VertexDefinition*		mVertexDef;
+
 	ID3D11InputLayout*		mD3DLayout;
 	ID3D11VertexShader*		mD3DVertexShader;
 	ID3D11PixelShader*		mD3DPixelShader;
@@ -107,7 +111,11 @@ public:
 	DXShader();
 	~DXShader();
 	
-	virtual ShaderParam*	GetShaderParam(const StringImpl& name) override;
+	void					ReleaseNative();
+	void					ReinitNative();
+
+	bool					Load();
+	virtual ShaderParam*	GetShaderParam(const StringImpl& name) override;	
 };
 
 class DXDrawBatch : public DrawBatch
@@ -144,8 +152,10 @@ public:
 	ID3D11RenderTargetView*	mD3DRenderTargetView;	
 	ID3D11Texture2D*		mD3DDepthBuffer;
 	ID3D11DepthStencilView*	mD3DDepthStencilView;
+	HANDLE					mFrameWaitObject;	
+	float					mRefreshRate;
 	bool					mResizePending;
-	bool					mWindowed;
+	bool					mWindowed;	
 	int						mPendingWidth;
 	int						mPendingHeight;
 
@@ -164,6 +174,8 @@ public:
 	virtual void			Present() override;
 
 	void					CopyBitsTo(uint32* dest, int width, int height);
+	virtual float			GetRefreshRate() override;
+	virtual bool			WaitForVBlank() override;
 };
 
 typedef std::vector<DXDrawBatch*> DXDrawBatchVector;
@@ -174,7 +186,7 @@ typedef std::vector<DXDrawBatch*> DXDrawBatchVector;
 class DXDrawBufferPool
 {
 public:
-	std::vector<void*>		mPooledIndexBuffers;	
+	std::vector<void*>		mPooledIndexBuffers;
 	int						mIdxPoolIdx;
 	std::vector<void*>		mPooledVertexBuffers;
 	int						mVtxPoolIdx;
@@ -237,8 +249,8 @@ public:
 class DXModelInstance : public ModelInstance
 {
 public:	
-	DXRenderDevice*			mD3DRenderDevice;	
-	Array<DXModelMesh>		mDXModelMeshs;	
+	DXRenderDevice*			mD3DRenderDevice;
+	Array<DXModelMesh>		mDXModelMeshs;
 
 public:
 	DXModelInstance(ModelDef* modelDef);
@@ -282,11 +294,12 @@ public:
 	IDXGIFactory*			mDXGIFactory;
 	ID3D11Device*			mD3DDevice;	
 	ID3D11DeviceContext*	mD3DDeviceContext;
-	ID3D11BlendState*		mD3DNormalBlendState;		
+	ID3D11BlendState*		mD3DNormalBlendState;
 	ID3D11SamplerState*		mD3DDefaultSamplerState;
 	ID3D11SamplerState*		mD3DWrapSamplerState;
-	bool					mHasVSync;
+	bool					mNeedsReinitNative;
 
+	ID3D11Buffer*			mMatrix2DBuffer;
 	ID3D11Buffer*			mD3DVertexBuffer;
 	ID3D11Buffer*			mD3DIndexBuffer;
 	int						mVtxByteIdx;
@@ -294,6 +307,7 @@ public:
 
 	HashSet<DXRenderState*>	mRenderStates;
 	HashSet<DXTexture*>		mTextures;
+	HashSet<DXShader*>		mShaders;
 	Dictionary<String, DXTexture*> mTextureMap;
 	Dictionary<int, ID3D11Buffer*> mBufferMap;
 			

+ 95 - 3
BeefySysLib/platform/win/WinBFApp.cpp

@@ -600,6 +600,9 @@ LRESULT WinBFWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
 
 		switch (uMsg)
 		{		
+		case WM_DISPLAYCHANGE:
+			((DXRenderWindow*)mRenderWindow)->mRefreshRate = 0;
+			break;
 		case WM_SIZE:
 			mRenderWindow->Resized();
 			if (mMovedFunc != NULL)
@@ -986,7 +989,6 @@ LRESULT WinBFWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
 		case WM_SYSKEYDOWN:
 		case WM_KEYDOWN:
 			{
-
 				mIsMenuKeyHandled = false;
 				int keyCode = (int) wParam;
 				if (keyCode == VK_APPS)
@@ -995,8 +997,13 @@ LRESULT WinBFWindow::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
 				if ((mKeyLayoutHasAltGr) && (keyCode == VK_MENU) && ((lParam & 0x01000000) != 0))
 					keyCode = VK_RMENU;
 
-				mIsKeyDown[keyCode] = true;				
+				mIsKeyDown[keyCode] = true;
 				
+// 				if ((keyCode == 192) && (mIsKeyDown[VK_MENU]))
+// 				{
+// 					((DXRenderDevice*)mRenderWindow->mRenderDevice)->mNeedsReinitNative = true;
+// 				}
+
 				for (auto kv : *menuIDMap)
 				{
 					WinBFMenu* aMenu = kv.mValue;
@@ -1275,10 +1282,71 @@ WinBFApp::WinBFApp()
 	mInMsgProc = false;
 	mDSoundManager = NULL;
 	mDInputManager = NULL;	
+
+	mVSyncThreadId = 0;
+	mClosing = false;
+	mVSyncActive = false;
+	mVSyncThread = BfpThread_Create(VSyncThreadProcThunk, (void*)this, 128 * 1024, BfpThreadCreateFlag_StackSizeReserve, &mVSyncThreadId);
+	BfpThread_SetPriority(mVSyncThread, BfpThreadPriority_High, NULL);
+}
+
+void BFP_CALLTYPE WinBFApp::VSyncThreadProcThunk(void* ptr)
+{
+	((WinBFApp*)ptr)->VSyncThreadProc();
+}
+
+void WinBFApp::VSyncThreadProc()
+{
+	while (!mClosing)
+	{
+		bool didWait = false;
+
+		IDXGIOutput* output = NULL;
+
+		//
+		{
+			AutoCrit autoCrit(mCritSect);
+			if ((mRenderDevice != NULL) && (!mRenderDevice->mRenderWindowList.IsEmpty()))
+			{
+				auto renderWindow = (DXRenderWindow*)mRenderDevice->mRenderWindowList[0];				
+				renderWindow->mDXSwapChain->GetContainingOutput(&output);				
+			}
+		}
+
+		if (output != NULL)
+		{
+			DWORD start = GetTickCount();
+			bool success = output->WaitForVBlank() == 0;
+			int elapsed = (int)(GetTickCount() - start);
+			if (elapsed >= 20)
+			{
+				NOP;
+			}
+
+			if (success)
+			{
+				didWait = true;
+				mVSyncActive = true;
+				mVSyncEvent.Set();
+			}
+
+			output->Release();
+		}
+
+		if (!didWait)		
+		{
+			mVSyncActive = false;
+			BfpThread_Sleep(20);
+		}
+	}
 }
 
 WinBFApp::~WinBFApp()
 {	
+	mClosing = true;
+	BfpThread_WaitFor(mVSyncThread, -1);
+	BfpThread_Release(mVSyncThread);
+
 	delete mRenderDevice;
 	delete mDSoundManager;
 	delete mDInputManager;
@@ -1288,6 +1356,8 @@ void WinBFApp::Init()
 {	
 	BP_ZONE("WinBFApp::Init");
 
+	AutoCrit autoCrit(mCritSect);
+
 	mRunning = true;
 	mInMsgProc = false;
 	
@@ -1311,10 +1381,23 @@ void WinBFApp::Run()
 	}	
 }
 
+void WinBFApp::Process()
+{
+	BFApp::Process();
+
+	auto dxRenderDevice = (DXRenderDevice*)mRenderDevice;
+	if (dxRenderDevice->mNeedsReinitNative)
+	{
+		dxRenderDevice->mNeedsReinitNative = false;
+		dxRenderDevice->ReinitNative();
+		mForceNextDraw = true;
+	}
+}
+
 void WinBFApp::Draw()
 {
 	mRenderDevice->FrameStart();	
-	BFApp::Draw();	
+	BFApp::Draw();
 	mRenderDevice->FrameEnd();	
 }
 
@@ -1392,6 +1475,8 @@ void WinBFApp::GetWorkspaceRectFrom(int fromX, int fromY, int fromWidth, int fro
 
 BFWindow* WinBFApp::CreateNewWindow(BFWindow* parent, const StringImpl& title, int x, int y, int width, int height, int windowFlags)
 {	
+	AutoCrit autoCrit(mCritSect);
+
 	BFWindow* aWindow = new WinBFWindow(parent, title, x, y, width, height, windowFlags);
 	mWindowList.push_back(aWindow);
 	
@@ -2025,6 +2110,13 @@ BFSoundManager* WinBFApp::GetSoundManager()
 	return mDSoundManager;
 }
 
+intptr WinBFApp::GetCriticalThreadId(int idx)
+{
+	if (idx == 0)
+		return mVSyncThreadId;
+	return 0;
+}
+
 void WinBFWindow::ModalsRemoved()
 {
 	::EnableWindow(mHWnd, TRUE);

+ 14 - 5
BeefySysLib/platform/win/WinBFApp.h

@@ -32,7 +32,7 @@ typedef Dictionary<HMENU, WinBFMenu*> WinHMenuMap;
 
 class WinBFWindow : public BFWindow
 {
-public:
+public:	
 	HWND					mHWnd;	
 	bool					mIsMouseInside;		
 	WinMenuIDMap			mMenuIDMap;
@@ -93,13 +93,19 @@ public:
 
 class WinBFApp : public BFApp
 {
-public:
+public:	
 	bool					mInMsgProc;
 	StringToUIntMap			mClipboardFormatMap;
 	DSoundManager*			mDSoundManager;
 	DInputManager*			mDInputManager;
-	
+	BfpThreadId				mVSyncThreadId;
+	BfpThread*				mVSyncThread;	
+	volatile bool			mClosing;
+
 protected:
+	void					VSyncThreadProc();
+	static void BFP_CALLTYPE VSyncThreadProcThunk(void* ptr);
+
 	virtual void			Draw() override;	
 	virtual void			PhysSetCursor() override;
 	
@@ -107,10 +113,11 @@ protected:
 
 public:
 	WinBFApp();
-	virtual ~WinBFApp();
+	virtual ~WinBFApp();	
 
 	virtual void			Init() override;
-	virtual void			Run() override;		
+	virtual void			Run() override;	
+	virtual void			Process() override;
 
 	virtual void			GetDesktopResolution(int& width, int& height) override;
 	virtual void			GetWorkspaceRect(int& x, int& y, int& width, int& height) override;
@@ -129,6 +136,8 @@ public:
 	virtual BFSysBitmap*	LoadSysBitmap(const WCHAR* fileName) override;
 
 	virtual BFSoundManager* GetSoundManager() override;
+
+	virtual intptr			GetCriticalThreadId(int idx) override;
 };
 
 NS_BF_END;

+ 3 - 4
IDE/src/IDEApp.bf

@@ -11767,7 +11767,6 @@ namespace IDE
         public override void Init()
         {
 			scope AutoBeefPerf("IDEApp.Init");
-
 			//int zag = 123;
 
 			if (mVerbosity == .Default)
@@ -14203,7 +14202,7 @@ namespace IDE
 			//mDebugger.InitiateHotResolve(.ActiveMethods | .Allocations);
         }
 
-        public override void Draw()
+        public override void Draw(bool forceDraw)
         {
 			scope AutoBeefPerf("IDEApp.Draw");
 #if CLI
@@ -14213,7 +14212,7 @@ namespace IDE
 			if (mBfResolveSystem != null)
 			{
 	            mBfResolveSystem.PerfZoneStart("IDEApp.Draw");
-	            base.Draw();
+	            base.Draw(forceDraw);
 	            mBfResolveSystem.PerfZoneEnd();
 	            if (mBfResolveSystem.mIsTiming)
 	            {
@@ -14222,7 +14221,7 @@ namespace IDE
 	            }
 			}
 			else
-				base.Draw();
+				base.Draw(forceDraw);
         }
 
         public void DrawSquiggle(Graphics g, float x, float y, float width)

+ 65 - 4
IDE/src/ui/AboutDialog.bf

@@ -3,11 +3,15 @@ using Beefy.gfx;
 using System;
 using Beefy.events;
 using Beefy.widgets;
+using System.Collections;
+using System.Diagnostics;
 
 namespace IDE.ui
 {
 	class AboutDialog : IDEDialog
 	{
+		static AboutDialog gAboutDialog;
+
 		const int cWidth = 320;
 		const int cHeight = 240;
 		const int cRandSize = 3777;
@@ -21,6 +25,14 @@ namespace IDE.ui
 		uint8[cRandSize] mRand;
 		int mRandIdx;
 
+		Stopwatch mStopwatch = new .()..Start() ~ delete _;
+		struct Entry
+		{
+			public double mUpdateCntF;
+			public float mTimeMS;
+		}
+		List<Entry> mEntries = new .() ~ delete _;
+
 		struct CColor
 		{
 			public uint8 r;
@@ -70,8 +82,24 @@ namespace IDE.ui
 				Color colorOut = Color.ToNative(Color.Lerp(mainColors[(int)colorPos], mainColors[(int)colorPos + 1], colorPos - (int)colorPos));
 				mPalette[i] = colorOut;
 			}
+
+			/*mWindowFlags |= .Resizable;
+			mWindowFlags &= ~.Modal;*/
+
+			gAboutDialog = this;
 		}
-		
+
+		~this()
+		{
+			gAboutDialog = null;
+		}
+
+		public override void AddedToParent()
+		{
+			base.AddedToParent();
+			mWidgetWindow.mWantsUpdateF = true;
+		}
+
 		public override void CalcSize()
 		{
 			mWidth = GS!(640);
@@ -140,12 +168,12 @@ namespace IDE.ui
 
 			mImage.SetBits(0, 0, cWidth, cHeight, cWidth, newBits);
 
-			float ang = Math.Min(mUpdateCnt * 0.006f, Math.PI_f / 2);
+			float ang = Math.Min((float)(mUpdateCntF * 0.006f), Math.PI_f / 2);
 			g.SetFont(mBigFont);
 			g.DrawString("Beef IDE", 0, GS!(20) + (1.0f - Math.Sin(ang))*GS!(300), .Centered, mWidth);
 
-			float angMed = Math.Min(mUpdateCnt * 0.0055f, Math.PI_f / 2);
-			float alpha = Math.Clamp(mUpdateCnt * 0.007f - 1.3f, 0, 1.0f);
+			float angMed = Math.Min((float)(mUpdateCntF * 0.0055f), Math.PI_f / 2);
+			float alpha = Math.Clamp((float)(mUpdateCntF * 0.007f) - 1.3f, 0, 1.0f);
 
 			using (g.PushColor(Color.Get(alpha)))
 			{
@@ -164,6 +192,33 @@ namespace IDE.ui
 			}
 
 			g.DrawQuad(mImage, 0, 0, 0.0f, 0.0f, mWidth, mHeight, 1.0f, 1.0f);
+
+			/*if (gAboutDialog == this)
+			{
+				/*Entry entry;
+				entry.mTimeMS = mStopwatch.ElapsedMicroseconds / 1000.0f;
+				entry.mUpdateCntF = mUpdateCntF;*/
+				mEntries.Add(Entry() {mTimeMS = mStopwatch.ElapsedMicroseconds / 1000.0f, mUpdateCntF = mUpdateCntF});
+
+				if (mEntries.Count == 100)
+				{
+					float prevTime = 0;
+
+					for (var entry in mEntries)
+					{
+						Debug.WriteLine($"Entry Time:{entry.mTimeMS - prevTime:0.00} UpdateCntF:{entry.mUpdateCntF:0.00}");
+						prevTime = entry.mTimeMS;
+
+						if (@entry.Index == 20)
+							break;
+					}
+
+					mEntries.Clear();
+				}
+			}
+
+			using (g.PushColor(0xFFFFFFFF))
+				g.FillRect(Math.Cos((float)(mUpdateCntF * 0.2f)) * 600 + mWidth / 2, 0, 8, mHeight);*/
 		}
 
 		public override void Update()
@@ -177,6 +232,12 @@ namespace IDE.ui
 			DoFire();
 		}
 
+		public override void UpdateF(float updatePct)
+		{
+			base.UpdateF(updatePct);
+			MarkDirty();
+		}
+
 		public override void KeyDown(KeyCode keyCode, bool isRepeat)
 		{
 			base.KeyDown(keyCode, isRepeat);

+ 13 - 6
IDE/src/ui/SourceEditWidgetContent.bf

@@ -6508,7 +6508,7 @@ namespace IDE.ui
 			}
 		}
 
-		public void UpdateCollapse()
+		public void UpdateCollapse(float updatePct)
 		{
 			MarkDirty();
 
@@ -6564,9 +6564,12 @@ namespace IDE.ui
 					{
 						if (emitEmbed.mIsOpen)
 						{
-							emitEmbed.mOpenPct = Math.Min(1.0f, emitEmbed.mOpenPct + 0.1f);
+							emitEmbed.mOpenPct = Math.Min(1.0f, emitEmbed.mOpenPct + 0.1f * updatePct);
 							if (emitEmbed.mOpenPct != 1.0f)
+							{
 								mCollapseNeedsUpdate = true;
+								mWidgetWindow.mTempWantsUpdateF = true;
+							}
 
 							if (emitEmbed.mView == null)
 							{
@@ -6577,7 +6580,7 @@ namespace IDE.ui
 						}
 						else
 						{
-							emitEmbed.mOpenPct = Math.Max(0.0f, emitEmbed.mOpenPct - 0.1f);
+							emitEmbed.mOpenPct = Math.Max(0.0f, emitEmbed.mOpenPct - 0.1f * updatePct);
 							if (emitEmbed.mOpenPct == 0.0f)
 							{
 								if (emitEmbed.mView != null)
@@ -6587,7 +6590,10 @@ namespace IDE.ui
 								}
 							}
 							else
+							{
 								mCollapseNeedsUpdate = true;
+								mWidgetWindow.mTempWantsUpdateF = true;
+							}
 						}
 					}
 				}
@@ -6664,13 +6670,14 @@ namespace IDE.ui
 			var entry = mOrderedCollapseEntries[animIdx];
 			if ((entry.mIsOpen) && (entry.mOpenPct < 1.0f))
 			{
-				entry.mOpenPct = Math.Min(entry.mOpenPct + animSpeed, 1.0f);
+				entry.mOpenPct = Math.Min(entry.mOpenPct + animSpeed * updatePct, 1.0f);
+				mWidgetWindow.mTempWantsUpdateF = true;
 			}
 
 			if ((!entry.mIsOpen) && (entry.mOpenPct > 0))
 			{
-				entry.mOpenPct = Math.Max(entry.mOpenPct - animSpeed, 0.0f);
-
+				entry.mOpenPct = Math.Max(entry.mOpenPct - animSpeed * updatePct, 0.0f);
+				mWidgetWindow.mTempWantsUpdateF = true;
 				if (entry.mOpenPct == 0.0f)
 					FinishCollapseClose(animIdx, entry);
 			}

+ 10 - 3
IDE/src/ui/SourceViewPanel.bf

@@ -639,6 +639,7 @@ namespace IDE.ui
 	            mNavigationBar = new NavigationBar(this);            
 	            AddWidget(mNavigationBar); 
 			}
+			mAlwaysUpdateF = true;
         }
 		public ~this()
 		{
@@ -6811,13 +6812,19 @@ namespace IDE.ui
 
 			UpdateQueuedEmitShowData();
 
-			if (ewc.mCollapseNeedsUpdate)
-				ewc.UpdateCollapse();
-
 			// Process after mQueuedCollapseData so mCharIdSpan is still valid
 			ProcessDeferredResolveResults(0);
         }
 
+		public override void UpdateF(float updatePct)
+		{
+			base.UpdateF(updatePct);
+
+			var ewc = (SourceEditWidgetContent)mEditWidget.Content;
+			if (ewc.mCollapseNeedsUpdate)
+				ewc.UpdateCollapse(updatePct);
+		}
+
 		public void UpdateQueuedEmitShowData()
 		{
 			var compiler = ResolveCompiler;