NRSwapChainManager.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. /****************************************************************************
  2. * Copyright 2019 Nreal Techonology Limited. All rights reserved.
  3. *
  4. * This file is part of NRSDK.
  5. *
  6. * https://www.nreal.ai/
  7. *
  8. *****************************************************************************/
  9. namespace NRKernal.Experimental
  10. {
  11. using System.Collections.Generic;
  12. using UnityEngine;
  13. using System;
  14. using UnityEngine.Assertions;
  15. using System.Linq;
  16. public enum LayerTextureType
  17. {
  18. RenderTexture,
  19. StandardTexture,
  20. EglTexture
  21. }
  22. public enum DisplayType
  23. {
  24. Left = NativeDevice.LEFT_DISPLAY,
  25. Right = NativeDevice.RIGHT_DISPLAY
  26. }
  27. public struct BufferSpec
  28. {
  29. public NativeResolution size;
  30. public NRColorFormat colorFormat;
  31. public NRDepthStencilFormat depthFormat;
  32. public bool useExternalSurface;
  33. public int surfaceFlag;
  34. public int samples;
  35. // Number of an arrar texture, default is 1 means a non-layerd texture.
  36. public int bufferCount;
  37. public void Copy(BufferSpec bufferspec)
  38. {
  39. this.size = bufferspec.size;
  40. this.colorFormat = bufferspec.colorFormat;
  41. this.depthFormat = bufferspec.depthFormat;
  42. this.samples = bufferspec.samples;
  43. this.bufferCount = bufferspec.bufferCount;
  44. this.surfaceFlag = bufferspec.surfaceFlag;
  45. this.useExternalSurface = bufferspec.useExternalSurface;
  46. }
  47. public override string ToString()
  48. {
  49. return string.Format("[{0} bufferCount:{1} useExternalSurface:{2} surfaceFlag:{3}]"
  50. , size.ToString(), bufferCount, useExternalSurface, surfaceFlag);
  51. }
  52. }
  53. public enum LayerSide
  54. {
  55. Left = 0,
  56. Right = 1,
  57. Both = 2,
  58. [HideInInspector]
  59. Count = Both
  60. };
  61. public struct ViewPort
  62. {
  63. public int layerID;
  64. public int index;
  65. public Rect sourceUV;
  66. public LayerSide targetDisplay;
  67. public Matrix4x4 transform;
  68. public NativeFov4f fov;
  69. public NRReprojectionType reprojection;
  70. public bool is3DLayer;
  71. public UInt64 nativeHandler;
  72. public override string ToString()
  73. {
  74. return string.Format("[layerID:{0} targetEye:{1} reprojection:{2} index:{3}]", layerID, targetDisplay, reprojection, index);
  75. }
  76. };
  77. public class NRSwapChainManager : SingleTon<NRSwapChainManager>, NRKernal.IFrameProcessor
  78. {
  79. public static List<OverlayBase> Overlays = new List<OverlayBase>();
  80. private NativeRenderring m_NativeRenderring;
  81. private NativeSwapchain NativeSwapchain { get; set; }
  82. private UInt64 m_SwapchainHandle = 0;
  83. private UInt64 m_ViewportListHandle = 0;
  84. private UInt64 m_FrameHandle = 0;
  85. private Dictionary<int, IntPtr> m_LayerBuffersDict;
  86. private Queue<TaskInfo> m_TaskQueue;
  87. /// <summary>
  88. /// Max overlay count is MaxOverlayCount-2, the two overlay are left display and right display.
  89. /// </summary>
  90. private const int MaxOverlayCount = 7;
  91. public struct TaskInfo
  92. {
  93. public Action<OverlayBase> callback;
  94. public OverlayBase obj;
  95. }
  96. public UInt64 GetFrameHandle()
  97. {
  98. return m_FrameHandle;
  99. }
  100. public UInt64 GetViewPortListHandle()
  101. {
  102. return m_ViewportListHandle;
  103. }
  104. private bool IsMultiThread
  105. {
  106. get
  107. {
  108. if (NRSessionManager.Instance.NRSessionBehaviour != null)
  109. {
  110. return NRSessionManager.Instance.NRSessionBehaviour.SessionConfig.UseMultiThread;
  111. }
  112. else
  113. {
  114. return true;
  115. }
  116. }
  117. }
  118. public bool IsInitialized { get; private set; }
  119. private AndroidJavaObject m_ProtectedCodec;
  120. protected AndroidJavaObject ProtectedCodec
  121. {
  122. get
  123. {
  124. if (m_ProtectedCodec == null)
  125. {
  126. m_ProtectedCodec = new AndroidJavaObject("ai.nreal.protect.session.ProtectSession");
  127. }
  128. return m_ProtectedCodec;
  129. }
  130. }
  131. public NRSwapChainManager()
  132. {
  133. m_LayerBuffersDict = new Dictionary<int, IntPtr>();
  134. m_TaskQueue = new Queue<TaskInfo>();
  135. if (Application.isPlaying)
  136. {
  137. InitDisplayOverlay();
  138. var nrRenderer = NRSessionManager.Instance.NRRenderer;
  139. if (nrRenderer != null)
  140. nrRenderer.SetFrameProcessor(this);
  141. }
  142. }
  143. // void OnSessionStateChanged(SessionState state)
  144. void InitDisplayOverlay()
  145. {
  146. NRDebugger.Info("[SwapChain] InitDisplayOverlay");
  147. var leftCamera = NRSessionManager.Instance.NRHMDPoseTracker.leftCamera;
  148. var lOverlay = leftCamera.gameObject.GetComponent<NRDisplayOverlay>();
  149. if (lOverlay == null)
  150. {
  151. lOverlay = leftCamera.gameObject.AddComponent<NRDisplayOverlay>();
  152. lOverlay.targetEye = NativeDevice.LEFT_DISPLAY;
  153. }
  154. var rightCamera = NRSessionManager.Instance.NRHMDPoseTracker.rightCamera;
  155. var rOverlay = rightCamera.gameObject.GetComponent<NRDisplayOverlay>();
  156. if (rOverlay == null)
  157. {
  158. rOverlay = rightCamera.gameObject.AddComponent<NRDisplayOverlay>();
  159. rOverlay.targetEye = NativeDevice.RIGHT_DISPLAY;
  160. }
  161. }
  162. public void Initialize(NativeRenderring nativeRenderring)
  163. {
  164. if (IsInitialized)
  165. {
  166. return;
  167. }
  168. NRDebugger.Info("[SwapChain] Initialize");
  169. m_NativeRenderring = nativeRenderring;
  170. NativeSwapchain = new NativeSwapchain(m_NativeRenderring);
  171. #if !UNITY_EDITOR
  172. m_SwapchainHandle = NativeSwapchain.CreateSwapChain();
  173. #endif
  174. IsInitialized = true;
  175. }
  176. public void Update()
  177. {
  178. if (!IsReady())
  179. {
  180. return;
  181. }
  182. if (m_TaskQueue.Count != 0)
  183. {
  184. while (m_TaskQueue.Count != 0)
  185. {
  186. var task = m_TaskQueue.Dequeue();
  187. task.callback.Invoke(task.obj);
  188. }
  189. }
  190. // NRDebugger.Info("[SwapChain] Update: frameCnt={0}", Time.frameCount);
  191. if (Overlays.Count != 0)
  192. {
  193. UpdateBufferViewportList();
  194. #if !UNITY_EDITOR
  195. SwapDisplayOverlaysRenderBuffers();
  196. #endif
  197. }
  198. }
  199. #region common functions
  200. private bool IsReady()
  201. {
  202. #if UNITY_EDITOR
  203. return IsInitialized;
  204. #else
  205. return IsInitialized && NRFrame.SessionStatus == SessionState.Running && NRSessionManager.Instance.NRRenderer.CurrentState == NRRenderer.RendererState.Running;
  206. #endif
  207. }
  208. public void Add(OverlayBase overlay)
  209. {
  210. if (Overlays.Contains(overlay))
  211. {
  212. NRDebugger.Warning("[SwapChain] There is no this overlay:" + overlay.LayerId);
  213. return;
  214. }
  215. if (Overlays.Count == MaxOverlayCount)
  216. {
  217. throw new NotSupportedException("The current count of overlays exceeds the maximum!");
  218. }
  219. if (IsReady())
  220. {
  221. AddLayer(overlay);
  222. }
  223. else
  224. {
  225. m_TaskQueue.Enqueue(new TaskInfo()
  226. {
  227. callback = AddLayer,
  228. obj = overlay
  229. });
  230. }
  231. }
  232. public void Remove(OverlayBase overlay)
  233. {
  234. if (!Overlays.Contains(overlay))
  235. {
  236. return;
  237. }
  238. if (IsReady())
  239. {
  240. RemoveLayer(overlay);
  241. }
  242. else
  243. {
  244. m_TaskQueue.Enqueue(new TaskInfo()
  245. {
  246. callback = RemoveLayer,
  247. obj = overlay
  248. });
  249. }
  250. }
  251. private void AddLayer(OverlayBase overlay)
  252. {
  253. Overlays.Add(overlay);
  254. Overlays.Sort();
  255. BufferSpec bufferSpec = overlay.BufferSpec;
  256. bufferSpec.bufferCount = GetRecommandBufferCount(m_SwapchainHandle, IsMultiThread, overlay);
  257. overlay.BufferSpec = bufferSpec;
  258. NRDebugger.Info("[SwapChain] AddLayer: name={0}, bufferSpec={1}", overlay.gameObject.name, bufferSpec.ToString());
  259. #if !UNITY_EDITOR
  260. overlay.NativeSpecHandler = NativeSwapchain.CreateBufferSpec(overlay.BufferSpec);
  261. overlay.LayerId = NativeSwapchain.CreateLayer(m_SwapchainHandle, overlay.NativeSpecHandler, bufferSpec.bufferCount);
  262. #endif
  263. overlay.CreateOverlayTextures();
  264. if (overlay.Textures != null && overlay.Textures.Count != 0)
  265. {
  266. #if !UNITY_EDITOR
  267. NativeSwapchain.SetLayerBuffer(m_SwapchainHandle, overlay.LayerId, overlay.Textures.Keys.ToArray());
  268. #endif
  269. }
  270. overlay.CreateViewport();
  271. UpdateProtectContentSetting();
  272. }
  273. private void RemoveLayer(OverlayBase overlay)
  274. {
  275. NRDebugger.Info("[SwapChain] RemoveLayer: name={0}", overlay.gameObject.name);
  276. overlay.ReleaseOverlayTextures();
  277. #if !UNITY_EDITOR
  278. NativeSwapchain.DestroyBufferSpec(overlay.NativeSpecHandler);
  279. var viewports = overlay.ViewPorts;
  280. for (int i = 0; i < viewports.Length; i++)
  281. {
  282. if (viewports[i].nativeHandler != 0)
  283. {
  284. NativeSwapchain.DestroyBufferViewport(viewports[i].nativeHandler);
  285. }
  286. }
  287. NativeSwapchain.DestroyLayer(m_SwapchainHandle, overlay.LayerId);
  288. #endif
  289. Overlays.Remove(overlay);
  290. UpdateOverlayViewPortIndex();
  291. UpdateProtectContentSetting();
  292. }
  293. private bool useProtectContent = false;
  294. private void UpdateProtectContentSetting()
  295. {
  296. bool flag = false;
  297. for (int i = 0; i < Overlays.Count; i++)
  298. {
  299. var overlay = Overlays[i];
  300. if (overlay is NROverlay && ((NROverlay)overlay).isProtectedContent)
  301. {
  302. flag = true;
  303. break;
  304. }
  305. }
  306. if (flag != useProtectContent)
  307. {
  308. NRDebugger.Info("[SwapChain] Protect content setting changed.");
  309. try
  310. {
  311. AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
  312. var unityActivity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
  313. if (flag)
  314. {
  315. NRDebugger.Info("[SwapChain] Use protect content.");
  316. ProtectedCodec.Call("start", unityActivity);
  317. }
  318. else
  319. {
  320. NRDebugger.Info("[SwapChain] Use un-protect content.");
  321. ProtectedCodec.Call("stop", unityActivity);
  322. }
  323. }
  324. catch (Exception e)
  325. {
  326. throw e;
  327. }
  328. useProtectContent = flag;
  329. }
  330. }
  331. private void PrintOverlayInfo()
  332. {
  333. System.Text.StringBuilder st = new System.Text.StringBuilder();
  334. for (int j = 0; j < Overlays.Count; j++)
  335. {
  336. var overlay = Overlays[j];
  337. if (overlay.IsActive)
  338. {
  339. st.Append(string.Format("[Overlay] {0}\n", overlay.ToString()));
  340. }
  341. }
  342. NRDebugger.Info(st.ToString());
  343. }
  344. private void UpdateOverlayViewPortIndex()
  345. {
  346. int index = 0;
  347. for (int j = 0; j < Overlays.Count; j++)
  348. {
  349. var overlay = Overlays[j];
  350. if (overlay.IsActive)
  351. {
  352. var viewports = overlay.ViewPorts;
  353. Assert.IsTrue(viewports != null && viewports.Length >= 1);
  354. int count = viewports.Length;
  355. for (int i = 0; i < count; i++)
  356. {
  357. viewports[i].index = index;
  358. index++;
  359. }
  360. }
  361. }
  362. PrintOverlayInfo();
  363. }
  364. private int GetViewportsCount()
  365. {
  366. int index = 0;
  367. for (int j = 0; j < Overlays.Count; j++)
  368. {
  369. var overlay = Overlays[j];
  370. if (overlay.IsActive)
  371. {
  372. var viewports = overlay.ViewPorts;
  373. Assert.IsTrue(viewports != null && viewports.Length >= 1);
  374. int count = viewports.Length;
  375. for (int i = 0; i < count; i++)
  376. {
  377. index++;
  378. }
  379. }
  380. }
  381. return index;
  382. }
  383. private int GetRecommandBufferCount(UInt64 swapchainhandler, bool usemultithread, OverlayBase overlay)
  384. {
  385. int flag = 0;
  386. if (overlay is NRDisplayOverlay)
  387. {
  388. flag = (int)NRSwapchainFlags.NR_SWAPCHAIN_LAYER_DYNAMIC_TEXTURE;
  389. }
  390. else if(overlay is NROverlay)
  391. {
  392. NROverlay layer = overlay as NROverlay;
  393. if (!layer.isExternalSurface && !layer.isDynamic)
  394. {
  395. return 1;
  396. }
  397. if (layer.isExternalSurface)
  398. flag = (int)NRSwapchainFlags.NR_SWAPCHAIN_EXTERNAL_SURFACE;
  399. else if (layer.isDynamic)
  400. flag = (int)NRSwapchainFlags.NR_SWAPCHAIN_LAYER_DYNAMIC_TEXTURE;
  401. }
  402. if (usemultithread)
  403. {
  404. flag ^= (int)NRSwapchainFlags.NR_SWAPCHAIN_MULTI_THREAD_RENDERING;
  405. }
  406. #if !UNITY_EDITOR
  407. return NativeSwapchain.GetRecommandBufferCount(swapchainhandler, flag);
  408. #else
  409. return 0;
  410. #endif
  411. }
  412. public IntPtr GetSurfaceHandler(int layerid)
  413. {
  414. if (m_SwapchainHandle == 0)
  415. {
  416. NRDebugger.Error("[SwapChain] GetSurfaceHandler error, swapchainHandle:{0} layerid:{1}", m_SwapchainHandle, layerid);
  417. return IntPtr.Zero;
  418. }
  419. #if !UNITY_EDITOR
  420. return NativeSwapchain.GetSurface(m_SwapchainHandle, layerid);
  421. #else
  422. return IntPtr.Zero;
  423. #endif
  424. }
  425. #endregion
  426. #region viewport
  427. private void UpdateBufferViewportList()
  428. {
  429. // The order of layer composition is the same as that of viewport, rendering from front to back
  430. #if !UNITY_EDITOR
  431. m_ViewportListHandle = NativeSwapchain.CreateBufferViewportList();
  432. // NRDebugger.Info("[SwapChain] Recreate viewportList: viewportListHandle={0}", m_ViewportListHandle);
  433. #endif
  434. for (int i = 0; i < Overlays.Count; ++i)
  435. {
  436. if (Overlays[i].IsActive)
  437. {
  438. Overlays[i].UpdateViewPort();
  439. }
  440. }
  441. }
  442. public void CreateBufferViewport(ref ViewPort viewport)
  443. {
  444. #if !UNITY_EDITOR
  445. UInt64 viewPortHandler = 0;
  446. var targetDisplay = viewport.targetDisplay == LayerSide.Left ? DisplayType.Left : DisplayType.Right;
  447. if (viewport.is3DLayer)
  448. {
  449. viewPortHandler = NativeSwapchain.CreateSourceFovBufferViewport(viewport.layerID,
  450. viewport.reprojection, viewport.sourceUV, targetDisplay, viewport.fov);
  451. }
  452. else
  453. {
  454. viewPortHandler = NativeSwapchain.CreateBufferViewport(viewport.layerID, viewport.reprojection,
  455. ref viewport.sourceUV, ref viewport.transform, targetDisplay);
  456. }
  457. viewport.nativeHandler = viewPortHandler;
  458. #endif
  459. // NRDebugger.Info("[SwapChain] CreateBufferViewport: {0}", viewport.ToString());
  460. UpdateOverlayViewPortIndex();
  461. }
  462. /// <summary>
  463. /// Sync viewport setting with native.
  464. /// </summary>
  465. public void UpdateBufferViewport(ref ViewPort viewport)
  466. {
  467. if (viewport.index == -1)
  468. {
  469. NRDebugger.Error("[SwapChain] UpdateBufferViewport index error:" + viewport.ToString());
  470. return;
  471. }
  472. //NRDebugger.Info("[SwapChain] UpdateBufferViewport:{0}", viewport.ToString());
  473. var targetDisplay = viewport.targetDisplay == LayerSide.Left ? DisplayType.Left : DisplayType.Right;
  474. #if !UNITY_EDITOR
  475. if (viewport.is3DLayer)
  476. {
  477. NativeSwapchain.UpdateSourceFovBufferViewport(viewport.nativeHandler, viewport.layerID, viewport.reprojection,
  478. ref viewport.sourceUV, targetDisplay, viewport.fov);
  479. }
  480. else
  481. {
  482. NativeSwapchain.UpdateBufferViewportData(viewport.nativeHandler, viewport.layerID, viewport.reprojection,
  483. ref viewport.sourceUV, ref viewport.transform, targetDisplay);
  484. }
  485. #endif
  486. NativeSwapchain.SetBufferViewPort(m_ViewportListHandle, viewport.index, viewport.nativeHandler);
  487. }
  488. public void DestroyBufferViewPort(UInt64 viewportHandle)
  489. {
  490. NativeSwapchain.DestroyBufferViewPort(viewportHandle);
  491. UpdateOverlayViewPortIndex();
  492. }
  493. #endregion
  494. #region submit frame
  495. private List<int> mDynamicLayers = new List<int>();
  496. private int[] mDynamicLayerIDArray = null;
  497. private IntPtr[] mDynamicLayerPtrArray = null;
  498. private List<int> GetDynamicLayerIds()
  499. {
  500. mDynamicLayers.Clear();
  501. for (int i = 0; i < Overlays.Count; i++)
  502. {
  503. if (Overlays[i] is NRDisplayOverlay)
  504. {
  505. mDynamicLayers.Add(Overlays[i].LayerId);
  506. }
  507. else if (Overlays[i] is NROverlay && ((NROverlay)Overlays[i]).isDynamic)
  508. {
  509. mDynamicLayers.Add(Overlays[i].LayerId);
  510. }
  511. }
  512. return mDynamicLayers;
  513. }
  514. /// <summary>
  515. /// Swap display overlays render buffers.
  516. /// </summary>
  517. private void SwapDisplayOverlaysRenderBuffers()
  518. {
  519. var layerids = GetDynamicLayerIds();
  520. Assert.IsTrue((layerids != null && layerids.Count != 0), "Dynamic layer is empty!");
  521. if (mDynamicLayerIDArray == null || mDynamicLayerIDArray.Length != layerids.Count)
  522. {
  523. mDynamicLayerIDArray = layerids.ToArray();
  524. mDynamicLayerPtrArray = new IntPtr[layerids.Count];
  525. }
  526. else
  527. {
  528. for (int i = 0; i < layerids.Count; i++)
  529. {
  530. mDynamicLayerIDArray[i] = layerids[i];
  531. mDynamicLayerPtrArray[i] = IntPtr.Zero;
  532. }
  533. }
  534. NativeSwapchain.AcquireCameraFrame(m_SwapchainHandle, ref m_FrameHandle, mDynamicLayerIDArray, ref mDynamicLayerPtrArray);
  535. // NRDebugger.Info("[SwapChain] SwapDisplayOverlaysRenderBuffers: layer len={0}, layerBuffer len={1}", layerids.Length, layerBuffers.Length);
  536. for (int i = 0; i < mDynamicLayerPtrArray.Length; i++)
  537. {
  538. m_LayerBuffersDict[layerids[i]] = mDynamicLayerPtrArray[i];
  539. // NRDebugger.Info("[SwapChain] SwapDisplayOverlaysRenderBuffers: layerid={0}, layerBuffer={1}", layerids[i], layerBuffers[i]);
  540. }
  541. for (int i = 0; i < Overlays.Count; ++i)
  542. {
  543. Overlays[i].SwapBuffers(GetBufferHandler(Overlays[i].LayerId));
  544. }
  545. }
  546. public IntPtr GetBufferHandler(int layerid)
  547. {
  548. IntPtr bufferid;
  549. if (!m_LayerBuffersDict.TryGetValue(layerid, out bufferid))
  550. {
  551. return IntPtr.Zero;
  552. }
  553. return bufferid;
  554. }
  555. public void SubmitFrame(ulong frameHandle, ulong viewPortListHandle)
  556. {
  557. // NRDebugger.Info("[SwapChain] SubmitFrame: frameCnt={3}, IsInitialized:{0}, ViewportListHandle:{1}, FrameHandle:{2}", IsInitialized, viewPortListHandle, frameHandle, Time.frameCount);
  558. if (!IsInitialized || viewPortListHandle == 0 || frameHandle == 0)
  559. {
  560. NRDebugger.Warning("[SwapChain] Can not submit frame!");
  561. return;
  562. }
  563. NativeSwapchain.SubmitFrame(frameHandle, viewPortListHandle);
  564. if (m_ViewportListHandle == viewPortListHandle)
  565. m_ViewportListHandle = 0;
  566. NativeSwapchain.DestroyBufferViewportList(viewPortListHandle);
  567. }
  568. public void UpdateExternalSurface(int layerid, NativeMat4f transform, Int64 timestamp, int index)
  569. {
  570. if (m_SwapchainHandle == 0)
  571. {
  572. NRDebugger.Warning("[SwapChain] Can not update external surface!");
  573. return;
  574. }
  575. NativeSwapchain.UpdateExternalSurface(m_SwapchainHandle, layerid, transform, timestamp, index);
  576. }
  577. #endregion
  578. public void Destroy()
  579. {
  580. #if !UNITY_EDITOR
  581. if (m_ViewportListHandle != 0)
  582. {
  583. NativeSwapchain.DestroyBufferViewportList(m_ViewportListHandle);
  584. m_ViewportListHandle = 0;
  585. }
  586. if (m_SwapchainHandle != 0)
  587. {
  588. NativeSwapchain.DestroySwapChain(m_SwapchainHandle);
  589. m_SwapchainHandle = 0;
  590. }
  591. #endif
  592. }
  593. }
  594. }