From 183636289abe8b216113d92d4023579bfc5de59d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 3 Nov 2022 19:59:56 +0100 Subject: [PATCH] Optimize shadow projections rendering contexts to collect draw call indices only (draw calls from main render list) --- Source/Engine/Graphics/Models/Mesh.cpp | 25 +++---- Source/Engine/Graphics/Models/SkinnedMesh.cpp | 19 ++--- Source/Engine/Level/Scene/SceneRendering.cpp | 2 +- Source/Engine/Renderer/RenderList.cpp | 75 ++++++++++++++++--- Source/Engine/Renderer/RenderList.h | 43 +++++++++-- Source/Engine/Renderer/Renderer.cpp | 1 + Source/Engine/Renderer/ShadowsPass.cpp | 9 ++- 7 files changed, 128 insertions(+), 46 deletions(-) diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 939c7ff57..58cec930c 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -411,7 +411,7 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons if (drawModes == DrawPass::None) return; - // Submit draw call + // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -434,6 +434,8 @@ void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, cons drawCall.Surface.LODDitherFactor = 0.0f; drawCall.WorldDeterminantSign = Math::FloatSelect(world.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = perInstanceRandom; + + // Push draw call to the render list renderContext.List->AddDrawCall(drawModes, flags, drawCall, receiveDecals); } @@ -454,14 +456,14 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float material = GPUDevice::Instance->GetDefaultMaterial(); if (!material || !material->IsSurface()) return; - + // Check if skip rendering const auto shadowsMode = (ShadowsCastingMode)(entry.ShadowsMode & slot.ShadowsMode); const auto drawModes = (DrawPass)((uint32)info.DrawModes & (uint32)renderContext.View.Pass & (uint32)renderContext.View.GetShadowsDrawPassMask(shadowsMode) & (uint32)material->GetDrawModes()); if (drawModes == DrawPass::None) return; - // Submit draw call + // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -493,6 +495,8 @@ void Mesh::Draw(const RenderContext& renderContext, const DrawInfo& info, float drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; + + // Push draw call to the render list renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); } @@ -514,7 +518,7 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in if (!material || !material->IsSurface()) return; - // Submit draw call + // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; drawCall.Geometry.VertexBuffers[0] = _vertexBuffers[0]; @@ -547,17 +551,10 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - // Push draw call to every context + // Push draw call to the render lists const auto shadowsMode = (ShadowsCastingMode)(entry.ShadowsMode & slot.ShadowsMode); - const auto materialDrawModes = material->GetDrawModes(); - for (const RenderContext& renderContext : renderContextBatch.Contexts) - { - const DrawPass drawModes = (DrawPass)((uint32)info.DrawModes & (uint32)renderContext.View.Pass & (uint32)renderContext.View.GetShadowsDrawPassMask(shadowsMode) & (uint32)materialDrawModes); - if (drawModes != DrawPass::None && renderContext.View.CullingFrustum.Intersects(info.Bounds)) - { - renderContext.List->AddDrawCall(drawModes, info.Flags, drawCall, entry.ReceiveDecals); - } - } + const DrawPass drawModes = (DrawPass)(info.DrawModes & material->GetDrawModes()); + renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals); } bool Mesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index 6c1d704c2..5129889bf 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -174,7 +174,7 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, if (drawModes == DrawPass::None) return; - // Submit draw call + // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; BlendShapesInstance::MeshInstance* blendShapeMeshInstance; @@ -211,6 +211,8 @@ void SkinnedMesh::Draw(const RenderContext& renderContext, const DrawInfo& info, drawCall.Surface.LODDitherFactor = lodDitherFactor; drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; + + // Push draw call to the render list renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals); } @@ -232,7 +234,7 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI if (!material || !material->IsSurface()) return; - // Submit draw call + // Setup draw call DrawCall drawCall; drawCall.Geometry.IndexBuffer = _indexBuffer; BlendShapesInstance::MeshInstance* blendShapeMeshInstance; @@ -270,17 +272,10 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI drawCall.WorldDeterminantSign = Math::FloatSelect(drawCall.World.RotDeterminant(), 1, -1); drawCall.PerInstanceRandom = info.PerInstanceRandom; - // Push draw call to every context + // Push draw call to the render lists const auto shadowsMode = (ShadowsCastingMode)(entry.ShadowsMode & slot.ShadowsMode); - const auto materialDrawModes = material->GetDrawModes(); - for (const RenderContext& renderContext : renderContextBatch.Contexts) - { - const DrawPass drawModes = (DrawPass)((uint32)info.DrawModes & (uint32)renderContext.View.Pass & (uint32)renderContext.View.GetShadowsDrawPassMask(shadowsMode) & (uint32)materialDrawModes); - if (drawModes != DrawPass::None && renderContext.View.CullingFrustum.Intersects(info.Bounds)) - { - renderContext.List->AddDrawCall(drawModes, StaticFlags::None, drawCall, entry.ReceiveDecals); - } - } + const DrawPass drawModes = (DrawPass)(info.DrawModes & material->GetDrawModes()); + renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, StaticFlags::None, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals); } bool SkinnedMesh::DownloadDataGPU(MeshBufferType type, BytesContainer& result) const diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index e5b6e209b..5718d9b6c 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -84,7 +84,7 @@ void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory c else if (origin.IsZero() && frustumsCount == 1) { // Fast path for no origin shifting with a single context - auto& renderContext = renderContextBatch.Contexts[0]; + RenderContext& renderContext = renderContextBatch.GetMainContext(); for (int32 i = 0; i < list.Count(); i++) { auto e = list.Get()[i]; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index a2411cdaf..60b57fa26 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -391,6 +391,7 @@ void RenderList::Clear() BatchedDrawCalls.Clear(); for (auto& list : DrawCallsLists) list.Clear(); + ShadowDepthDrawCallsList.Clear(); PointLights.Clear(); SpotLights.Clear(); SkyLights.Clear(); @@ -445,6 +446,60 @@ void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCa } } +void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, DrawCall& drawCall, bool receivesDecals) +{ +#if ENABLE_ASSERTION_LOW_LAYERS + // Ensure that draw modes are non-empty and in conjunction with material draw modes + auto materialDrawModes = drawCall.Material->GetDrawModes(); + ASSERT_LOW_LAYER(drawModes != DrawPass::None && ((uint32)drawModes & ~(uint32)materialDrawModes) == 0); +#endif + + // Append draw call data + const int32 index = DrawCalls.Count(); + DrawCalls.Add(drawCall); + + // Add draw call to proper draw lists + const RenderContext& mainRenderContext = renderContextBatch.Contexts.Get()[0]; + DrawPass modes = (DrawPass)(drawModes & mainRenderContext.View.GetShadowsDrawPassMask(shadowsMode)); + drawModes = (DrawPass)(modes & mainRenderContext.View.Pass); + if (drawModes != DrawPass::None && mainRenderContext.View.CullingFrustum.Intersects(bounds)) + { + if (drawModes & DrawPass::Depth) + { + DrawCallsLists[(int32)DrawCallsListType::Depth].Indices.Add(index); + } + if (drawModes & (DrawPass::GBuffer | DrawPass::GlobalSurfaceAtlas)) + { + if (receivesDecals) + DrawCallsLists[(int32)DrawCallsListType::GBuffer].Indices.Add(index); + else + DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].Indices.Add(index); + } + if (drawModes & DrawPass::Forward) + { + DrawCallsLists[(int32)DrawCallsListType::Forward].Indices.Add(index); + } + if (drawModes & DrawPass::Distortion) + { + DrawCallsLists[(int32)DrawCallsListType::Distortion].Indices.Add(index); + } + if (drawModes & DrawPass::MotionVectors && (staticFlags & StaticFlags::Transform) == 0) + { + DrawCallsLists[(int32)DrawCallsListType::MotionVectors].Indices.Add(index); + } + } + for (int32 i = 1; i < renderContextBatch.Contexts.Count(); i++) + { + const RenderContext& renderContext = renderContextBatch.Contexts.Get()[i]; + ASSERT_LOW_LAYER(renderContext.View.Pass == DrawPass::Depth); + drawModes = (DrawPass)(modes & renderContext.View.Pass); + if (drawModes != DrawPass::None && renderContext.View.CullingFrustum.Intersects(bounds)) + { + renderContext.List->ShadowDepthDrawCallsList.Indices.Add(index); + } + } +} + namespace { /// @@ -466,7 +521,7 @@ namespace } } -void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list) +void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const DrawCall* drawCalls) { PROFILE_CPU(); @@ -486,7 +541,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD const uint32 sortKeyXor = reverseDistance ? MAX_uint32 : 0; for (int32 i = 0; i < listSize; i++) { - auto& drawCall = DrawCalls[list.Indices[i]]; + const DrawCall& drawCall = drawCalls[list.Indices[i]]; const float distance = Float3::Dot(planeNormal, drawCall.ObjectPosition) - planePoint; const uint32 sortKey = RenderTools::ComputeDistanceSortKey(distance) ^ sortKeyXor; int32 batchKey = GetHash(drawCall.Geometry.IndexBuffer); @@ -516,14 +571,14 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD list.Batches.Clear(); for (int32 i = 0; i < listSize;) { - const auto& drawCall = DrawCalls[list.Indices[i]]; + const DrawCall& drawCall = drawCalls[list.Indices[i]]; int32 batchSize = 1; int32 instanceCount = drawCall.InstanceCount; // Check the following draw calls to merge them (using instancing) for (int32 j = i + 1; j < listSize; j++) { - const auto& other = DrawCalls[list.Indices[j]]; + const DrawCall& other = drawCalls[list.Indices[j]]; if (!CanBatchWith(drawCall, other)) break; @@ -550,7 +605,7 @@ bool CanUseInstancing(DrawPass pass) return pass == DrawPass::GBuffer || pass == DrawPass::Depth; } -void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, GPUTextureView* input) +void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, const DrawCall* drawCalls, GPUTextureView* input) { if (list.IsEmpty()) return; @@ -595,10 +650,10 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL if (batch.BatchSize > 1) { IMaterial::InstancingHandler handler; - DrawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); + drawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); for (int32 j = 0; j < batch.BatchSize; j++) { - auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]]; + auto& drawCall = drawCalls[list.Indices[batch.StartIndex + j]]; handler.WriteDrawCall(instanceData, drawCall); instanceData++; } @@ -631,7 +686,7 @@ DRAW: for (int32 i = 0; i < list.Batches.Count(); i++) { auto& batch = list.Batches[i]; - auto& drawCall = DrawCalls[list.Indices[batch.StartIndex]]; + const DrawCall& drawCall = drawCalls[list.Indices[batch.StartIndex]]; int32 vbCount = 0; while (vbCount < ARRAY_COUNT(drawCall.Geometry.VertexBuffers) && drawCall.Geometry.VertexBuffers[vbCount]) @@ -738,7 +793,7 @@ DRAW: for (int32 j = 0; j < batch.BatchSize; j++) { - auto& drawCall = DrawCalls[list.Indices[batch.StartIndex + j]]; + const DrawCall& drawCall = drawCalls[list.Indices[batch.StartIndex + j]]; bindParams.FirstDrawCall = &drawCall; drawCall.Material->Bind(bindParams); @@ -785,7 +840,7 @@ DRAW: // Draw calls list has nto been batched so execute draw calls separately for (int32 j = 0; j < list.Indices.Count(); j++) { - auto& drawCall = DrawCalls[list.Indices[j]]; + const DrawCall& drawCall = drawCalls[list.Indices[j]]; bindParams.FirstDrawCall = &drawCall; drawCall.Material->Bind(bindParams); diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index fd3b74658..a00a318b1 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -18,6 +18,7 @@ class LightWithShadow; class IPostFxSettingsProvider; class CubeTexture; struct RenderContext; +struct RenderContextBatch; struct RendererDirectionalLightData { @@ -301,6 +302,11 @@ public: /// DrawCallsList DrawCallsLists[(int32)DrawCallsListType::MAX]; + /// + /// The additional draw calls list for Depth drawing into Shadow Projections that use DrawCalls from main render context. This assumes that RenderContextBatch contains main context and shadow projections only. + /// + DrawCallsList ShadowDepthDrawCallsList; + /// /// Light pass members - directional lights /// @@ -477,6 +483,18 @@ public: /// True if the rendered mesh can receive decals. void AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals); + /// + /// Adds the draw call to the draw lists and references it in other render contexts. Performs additional per-context frustum culling. + /// + /// The rendering context batch. This assumes that RenderContextBatch contains main context and shadow projections only. + /// The object draw modes. + /// The object static flags. + /// The object shadows casting mode. + /// The object bounds. + /// The draw call data. + /// True if the rendered mesh can receive decals. + void AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, DrawCall& drawCall, bool receivesDecals); + /// /// Sorts the collected draw calls list. /// @@ -485,7 +503,7 @@ public: /// The collected draw calls list type. API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType) { - SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType]); + SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls.Get()); } /// @@ -493,8 +511,9 @@ public: /// /// The rendering context. /// If set to true reverse draw call distance to the view. Results in back to front sorting. - /// The collected draw calls list. - void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list); + /// The collected draw calls indices list. + /// The collected draw calls list. + void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const DrawCall* drawCalls); /// /// Executes the collected draw calls. @@ -504,16 +523,28 @@ public: /// The input scene color. It's optional and used in forward/postFx rendering. API_FUNCTION() FORCE_INLINE void ExecuteDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, DrawCallsListType listType, GPUTextureView* input = nullptr) { - ExecuteDrawCalls(renderContext, DrawCallsLists[(int32)listType], input); + ExecuteDrawCalls(renderContext, DrawCallsLists[(int32)listType], DrawCalls.Get(), input); } /// /// Executes the collected draw calls. /// /// The rendering context. - /// The collected draw calls list. + /// The collected draw calls indices list. /// The input scene color. It's optional and used in forward/postFx rendering. - void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, GPUTextureView* input = nullptr); + FORCE_INLINE void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, GPUTextureView* input = nullptr) + { + ExecuteDrawCalls(renderContext, list, DrawCalls.Get(), input); + } + + /// + /// Executes the collected draw calls. + /// + /// The rendering context. + /// The collected draw calls indices list. + /// The collected draw calls list. + /// The input scene color. It's optional and used in forward/postFx rendering. + void ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list, const DrawCall* drawCalls, GPUTextureView* input); }; /// diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 98bc4812e..6fb379eeb 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -355,6 +355,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont { auto& shadowContext = renderContextBatch.Contexts[i]; shadowContext.List->SortDrawCalls(shadowContext, false, DrawCallsListType::Depth); + shadowContext.List->SortDrawCalls(shadowContext, false, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls.Get()); } } diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp index 2bbd9b2b4..100a7cda7 100644 --- a/Source/Engine/Renderer/ShadowsPass.cpp +++ b/Source/Engine/Renderer/ShadowsPass.cpp @@ -609,7 +609,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererP return; PROFILE_GPU_CPU("Shadow"); GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.Contexts[0]; + RenderContext& renderContext = renderContextBatch.GetMainContext(); ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; const float sphereModelScale = 3.0f; auto& view = renderContext.View; @@ -631,6 +631,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererP context->ClearDepth(rt); auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); + shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls.Get(), nullptr); } // Restore GPU context @@ -686,7 +687,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererS return; PROFILE_GPU_CPU("Shadow"); GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.Contexts[0]; + RenderContext& renderContext = renderContextBatch.GetMainContext(); ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; const float sphereModelScale = 3.0f; auto& view = renderContext.View; @@ -708,6 +709,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererS context->ClearDepth(rt); auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + faceIndex]; shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); + shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls.Get(), nullptr); } // Restore GPU context @@ -763,7 +765,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererD return; PROFILE_GPU_CPU("Shadow"); GPUContext* context = GPUDevice::Instance->GetMainContext(); - RenderContext& renderContext = renderContextBatch.Contexts[0]; + RenderContext& renderContext = renderContextBatch.GetMainContext(); ShadowData& shadowData = _shadowData[light.ShadowDataIndex]; const float shadowMapsSizeCSM = (float)_shadowMapsSizeCSM; context->SetViewportAndScissors(shadowMapsSizeCSM, shadowMapsSizeCSM); @@ -777,6 +779,7 @@ void ShadowsPass::RenderShadow(RenderContextBatch& renderContextBatch, RendererD context->ClearDepth(rt); auto& shadowContext = renderContextBatch.Contexts[shadowData.ContextIndex + cascadeIndex]; shadowContext.List->ExecuteDrawCalls(shadowContext, DrawCallsListType::Depth); + shadowContext.List->ExecuteDrawCalls(shadowContext, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls.Get(), nullptr); } // Restore GPU context