Skip to content

Commit 2b1ee82

Browse files
Added AsyncInitializer helper class
1 parent 4806d47 commit 2b1ee82

File tree

8 files changed

+169
-79
lines changed

8 files changed

+169
-79
lines changed

Common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ set(INTERFACE
1010
interface/AdvancedMath.hpp
1111
interface/Align.hpp
1212
interface/Array2DTools.hpp
13+
interface/AsyncInitializer.hpp
1314
interface/BasicMath.hpp
1415
interface/BasicFileStream.hpp
1516
interface/DataBlobImpl.hpp
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2024 Diligent Graphics LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* In no event and under no legal theory, whether in tort (including negligence),
17+
* contract, or otherwise, unless required by applicable law (such as deliberate
18+
* and grossly negligent acts) or agreed to in writing, shall any Contributor be
19+
* liable for any damages, including any direct, indirect, special, incidental,
20+
* or consequential damages of any character arising as a result of this License or
21+
* out of the use or inability to use the software (including but not limited to damages
22+
* for loss of goodwill, work stoppage, computer failure or malfunction, or any and
23+
* all other commercial damages or losses), even if such Contributor has been advised
24+
* of the possibility of such damages.
25+
*/
26+
27+
#pragma once
28+
29+
#include <atomic>
30+
#include <memory>
31+
32+
#include "SpinLock.hpp"
33+
#include "ThreadPool.hpp"
34+
35+
namespace Diligent
36+
{
37+
38+
class AsyncInitializer
39+
{
40+
public:
41+
static_assert((ASYNC_TASK_STATUS_UNKNOWN < ASYNC_TASK_STATUS_NOT_STARTED &&
42+
ASYNC_TASK_STATUS_NOT_STARTED < ASYNC_TASK_STATUS_RUNNING &&
43+
ASYNC_TASK_STATUS_RUNNING < ASYNC_TASK_STATUS_CANCELLED &&
44+
ASYNC_TASK_STATUS_CANCELLED < ASYNC_TASK_STATUS_COMPLETE),
45+
"ASYNC_TASK_STATUS enum values are not ordered correctly");
46+
47+
ASYNC_TASK_STATUS Update(bool WaitForCompletion)
48+
{
49+
ASYNC_TASK_STATUS CurrStatus = m_Status.load();
50+
VERIFY_EXPR(CurrStatus != ASYNC_TASK_STATUS_UNKNOWN);
51+
if (CurrStatus <= ASYNC_TASK_STATUS_RUNNING)
52+
{
53+
RefCntAutoPtr<IAsyncTask> pTask;
54+
{
55+
Threading::SpinLockGuard Guard{m_TaskLock};
56+
pTask = m_wpTask.Lock();
57+
}
58+
59+
ASYNC_TASK_STATUS NewStatus = ASYNC_TASK_STATUS_UNKNOWN;
60+
if (pTask)
61+
{
62+
if (WaitForCompletion)
63+
{
64+
pTask->WaitForCompletion();
65+
}
66+
NewStatus = pTask->GetStatus();
67+
}
68+
69+
if (NewStatus == ASYNC_TASK_STATUS_CANCELLED || NewStatus == ASYNC_TASK_STATUS_COMPLETE)
70+
{
71+
Threading::SpinLockGuard Guard{m_TaskLock};
72+
m_wpTask.Release();
73+
}
74+
75+
if (NewStatus > CurrStatus)
76+
{
77+
while (!m_Status.compare_exchange_weak(CurrStatus, std::max(CurrStatus, NewStatus)))
78+
{
79+
// If exchange fails, CurrStatus will hold the actual value of m_Status.
80+
}
81+
}
82+
}
83+
84+
return m_Status.load();
85+
}
86+
87+
ASYNC_TASK_STATUS GetStatus() const
88+
{
89+
return m_Status.load();
90+
}
91+
92+
RefCntAutoPtr<IAsyncTask> GetCompileTask() const
93+
{
94+
Threading::SpinLockGuard Guard{m_TaskLock};
95+
return m_wpTask.Lock();
96+
}
97+
98+
template <typename HanlderType>
99+
static std::unique_ptr<AsyncInitializer> Start(IThreadPool* pThreadPool,
100+
IAsyncTask** ppPrerequisites,
101+
Uint32 NumPrerequisites,
102+
HanlderType&& Handler)
103+
{
104+
VERIFY_EXPR(pThreadPool != nullptr);
105+
return std::unique_ptr<AsyncInitializer>{
106+
new AsyncInitializer{EnqueueAsyncWork(pThreadPool, ppPrerequisites, NumPrerequisites, std::forward<HanlderType>(Handler))},
107+
};
108+
}
109+
110+
template <typename HanlderType>
111+
static std::unique_ptr<AsyncInitializer> Start(IThreadPool* pThreadPool,
112+
HanlderType&& Handler)
113+
{
114+
return Start(pThreadPool, nullptr, 0, std::forward<HanlderType>(Handler));
115+
}
116+
117+
static ASYNC_TASK_STATUS Update(std::unique_ptr<AsyncInitializer>& Initializer, bool WaitForCompletion)
118+
{
119+
return Initializer ? Initializer->Update(WaitForCompletion) : ASYNC_TASK_STATUS_UNKNOWN;
120+
}
121+
122+
static RefCntAutoPtr<IAsyncTask> GetAsyncTask(const std::unique_ptr<AsyncInitializer>& Initializer)
123+
{
124+
return Initializer ? Initializer->GetCompileTask() : RefCntAutoPtr<IAsyncTask>{};
125+
}
126+
127+
private:
128+
AsyncInitializer(RefCntAutoPtr<IAsyncTask> pTask) :
129+
m_wpTask{pTask}
130+
{
131+
// Do not get the actual status from the task since it needs to be done in Update().
132+
// The task may happen to be complete by the time the initializer is created.
133+
}
134+
135+
private:
136+
// It is important to set the task status to a non-unknown value before the task is started.
137+
std::atomic<ASYNC_TASK_STATUS> m_Status{ASYNC_TASK_STATUS_NOT_STARTED};
138+
139+
// Note that while RefCntAutoPtr/RefCntWeakPtr allow safely accessing the same object
140+
// from multiple threads using different pointers, they are not thread-safe by themselves
141+
// (e.g. it is not safe to call Lock() or Release() on the same pointer from multiple threads).
142+
// Therefore, we need to use a lock to protect access to m_wpTask.
143+
mutable Threading::SpinLock m_TaskLock;
144+
mutable RefCntWeakPtr<IAsyncTask> m_wpTask;
145+
};
146+
147+
} // namespace Diligent

Graphics/GraphicsEngine/include/PipelineStateBase.hpp

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@
4646
#include "HashUtils.hpp"
4747
#include "PipelineResourceSignatureBase.hpp"
4848
#include "RefCntAutoPtr.hpp"
49-
#include "ThreadPool.hpp"
50-
#include "SpinLock.hpp"
49+
#include "AsyncInitializer.hpp"
5150
#include "GraphicsTypesX.hpp"
5251

5352
namespace Diligent
@@ -428,7 +427,7 @@ class PipelineStateBase : public DeviceObjectBase<typename EngineImplTraits::Pip
428427

429428
~PipelineStateBase()
430429
{
431-
VERIFY(!m_wpInitializeTask.IsValid(), "Initialize task is still running. This may result in a crash if the task accesses resources owned by the pipeline state object.");
430+
VERIFY(!AsyncInitializer::GetAsyncTask(m_AsyncInitializer), "Initialize task is still running. This may result in a crash if the task accesses resources owned by the pipeline state object.");
432431

433432
/*
434433
/// \note Destructor cannot directly remove the object from the registry as this may cause a
@@ -766,33 +765,11 @@ class PipelineStateBase : public DeviceObjectBase<typename EngineImplTraits::Pip
766765
virtual PIPELINE_STATE_STATUS DILIGENT_CALL_TYPE GetStatus(bool WaitForCompletion = false) override
767766
{
768767
VERIFY_EXPR(m_Status.load() != PIPELINE_STATE_STATUS_UNINITIALIZED);
769-
if (m_InitializeTaskRunning.load())
768+
ASYNC_TASK_STATUS InitTaskStatus = AsyncInitializer::Update(m_AsyncInitializer, WaitForCompletion);
769+
if (InitTaskStatus == ASYNC_TASK_STATUS_COMPLETE)
770770
{
771-
RefCntAutoPtr<IAsyncTask> pInitializeTask;
772-
{
773-
Threading::SpinLockGuard Guard{m_InitializeTaskLock};
774-
pInitializeTask = m_wpInitializeTask.Lock();
775-
}
776-
777-
bool TaskFinished = (pInitializeTask == nullptr);
778-
if (pInitializeTask)
779-
{
780-
if (WaitForCompletion)
781-
{
782-
pInitializeTask->WaitForCompletion();
783-
}
784-
TaskFinished = pInitializeTask->IsFinished();
785-
}
786-
787-
if (TaskFinished)
788-
{
789-
VERIFY(m_Status.load() > PIPELINE_STATE_STATUS_COMPILING, "Pipeline state status must be atomically set by the initialization task before it finishes");
790-
Threading::SpinLockGuard Guard{m_InitializeTaskLock};
791-
m_wpInitializeTask.Release();
792-
m_InitializeTaskRunning.store(false);
793-
}
771+
VERIFY(m_Status.load() > PIPELINE_STATE_STATUS_COMPILING, "Pipeline state status must be atomically set by the initialization task before it finishes");
794772
}
795-
796773
return m_Status.load();
797774
}
798775

@@ -902,8 +879,7 @@ class PipelineStateBase : public DeviceObjectBase<typename EngineImplTraits::Pip
902879

903880
std::vector<IAsyncTask*> Prerequisites{ShaderCompileTasks.begin(), ShaderCompileTasks.end()};
904881

905-
m_InitializeTaskRunning.store(true);
906-
m_wpInitializeTask = EnqueueAsyncWork(
882+
m_AsyncInitializer = AsyncInitializer::Start(
907883
this->m_pDevice->GetShaderCompilationThreadPool(),
908884
Prerequisites.data(), // Make sure that all asynchronous shader compile tasks are
909885
static_cast<Uint32>(Prerequisites.size()), // completed before the pipeline initialization task starts
@@ -1296,21 +1272,15 @@ class PipelineStateBase : public DeviceObjectBase<typename EngineImplTraits::Pip
12961272
}
12971273

12981274
protected:
1275+
std::unique_ptr<AsyncInitializer> m_AsyncInitializer;
1276+
12991277
/// Shader stages that are active in this PSO.
13001278
SHADER_TYPE m_ActiveShaderStages = SHADER_TYPE_UNKNOWN;
13011279

13021280
/// True if the pipeline was created using implicit root signature.
13031281
const bool m_UsingImplicitSignature;
13041282

13051283
std::atomic<PIPELINE_STATE_STATUS> m_Status{PIPELINE_STATE_STATUS_UNINITIALIZED};
1306-
std::atomic<bool> m_InitializeTaskRunning{false};
1307-
1308-
// Note that while RefCntAutoPtr/RefCntWeakPtr allow safely accessing the same object
1309-
// from multiple threads using different pointers, they are not thread-safe by themselves
1310-
// (e.g. it is not safe to call Lock() or Release() on the same pointer from multiple threads).
1311-
// Therefore, we need to use a lock to protect access to m_wpInitializeTask.
1312-
Threading::SpinLock m_InitializeTaskLock;
1313-
RefCntWeakPtr<IAsyncTask> m_wpInitializeTask;
13141284

13151285
/// The number of signatures in m_Signatures array.
13161286
/// Note that this is not necessarily the same as the number of signatures

Graphics/GraphicsEngine/include/ShaderBase.hpp

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,8 @@
4040
#include "PlatformMisc.hpp"
4141
#include "EngineMemory.h"
4242
#include "Align.hpp"
43-
#include "ThreadPool.h"
4443
#include "RefCntAutoPtr.hpp"
45-
#include "SpinLock.hpp"
44+
#include "AsyncInitializer.hpp"
4645

4746
namespace Diligent
4847
{
@@ -151,35 +150,19 @@ class ShaderBase : public DeviceObjectBase<typename EngineImplTraits::ShaderInte
151150

152151
~ShaderBase()
153152
{
154-
VERIFY(!m_wpCompileTask.IsValid(), "Compile task is still running. This may result in a crash if the task accesses resources owned by the shader object.");
153+
VERIFY(!GetCompileTask(), "Compile task is still running. This may result in a crash if the task accesses resources owned by the shader object.");
155154
}
156155

157156
IMPLEMENT_QUERY_INTERFACE_IN_PLACE(IID_Shader, TDeviceObjectBase)
158157

159158
virtual SHADER_STATUS DILIGENT_CALL_TYPE GetStatus(bool WaitForCompletion) override
160159
{
161160
VERIFY_EXPR(m_Status.load() != SHADER_STATUS_UNINITIALIZED);
162-
if (m_CompileTaskRunning.load())
161+
ASYNC_TASK_STATUS InitTaskStatus = AsyncInitializer::Update(m_AsyncInitializer, WaitForCompletion);
162+
if (InitTaskStatus == ASYNC_TASK_STATUS_COMPLETE)
163163
{
164-
bool TaskFinished = true;
165-
if (RefCntAutoPtr<IAsyncTask> pCompileTask = GetCompileTask())
166-
{
167-
if (WaitForCompletion)
168-
{
169-
pCompileTask->WaitForCompletion();
170-
}
171-
TaskFinished = pCompileTask->IsFinished();
172-
}
173-
174-
if (TaskFinished)
175-
{
176-
VERIFY(!IsCompiling(), "Shader status must be atomically set by the compiling task before it finishes");
177-
Threading::SpinLockGuard Guard{m_CompileTaskLock};
178-
m_wpCompileTask.Release();
179-
m_CompileTaskRunning.store(false);
180-
}
164+
VERIFY(m_Status.load() > SHADER_STATUS_COMPILING, "Shader status must be atomically set by the compiling task before it finishes");
181165
}
182-
183166
return m_Status.load();
184167
}
185168

@@ -190,22 +173,15 @@ class ShaderBase : public DeviceObjectBase<typename EngineImplTraits::ShaderInte
190173

191174
RefCntAutoPtr<IAsyncTask> GetCompileTask() const
192175
{
193-
Threading::SpinLockGuard Guard{m_CompileTaskLock};
194-
return m_wpCompileTask.Lock();
176+
return AsyncInitializer::GetAsyncTask(m_AsyncInitializer);
195177
}
196178

197179
protected:
180+
std::unique_ptr<AsyncInitializer> m_AsyncInitializer;
181+
198182
const std::string m_CombinedSamplerSuffix;
199183

200184
std::atomic<SHADER_STATUS> m_Status{SHADER_STATUS_UNINITIALIZED};
201-
std::atomic<bool> m_CompileTaskRunning{false};
202-
203-
// Note that while RefCntAutoPtr/RefCntWeakPtr allow safely accessing the same object
204-
// from multiple threads using different pointers, they are not thread-safe by themselves
205-
// (e.g. it is not safe to call Lock() or Release() on the same pointer from multiple threads).
206-
// Therefore, we need to use a lock to protect access to m_wpCompileTask.
207-
mutable Threading::SpinLock m_CompileTaskLock;
208-
mutable RefCntWeakPtr<IAsyncTask> m_wpCompileTask;
209185
};
210186

211187
} // namespace Diligent

Graphics/GraphicsEngineD3DBase/include/ShaderD3DBase.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ class ShaderD3DBase : public ShaderBase<EngineImplTraits>
9595
}
9696
else
9797
{
98-
// Set CompileTaskRunning flag before enqueuing the task
99-
this->m_CompileTaskRunning.store(true);
100-
this->m_wpCompileTask = EnqueueAsyncWork(
98+
this->m_AsyncInitializer = AsyncInitializer::Start(
10199
D3DShaderCI.pShaderCompilationThreadPool,
102100
[this,
103101
ShaderCI = ShaderCreateInfoWrapper{ShaderCI, GetRawAllocator()},

Graphics/GraphicsEngineVulkan/src/ShaderVkImpl.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,7 @@ ShaderVkImpl::ShaderVkImpl(IReferenceCounters* pRefCounters,
278278
}
279279
else
280280
{
281-
m_CompileTaskRunning.store(true);
282-
m_wpCompileTask = EnqueueAsyncWork(
281+
this->m_AsyncInitializer = AsyncInitializer::Start(
283282
VkShaderCI.pCompilationThreadPool,
284283
[this,
285284
ShaderCI = ShaderCreateInfoWrapper{ShaderCI, GetRawAllocator()},

Graphics/GraphicsEngineWebGPU/src/PipelineStateWebGPUImpl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ struct PipelineStateWebGPUImpl::AsyncPipelineBuilder : public ObjectBase<IObject
328328
void PipelineStateWebGPUImpl::InitializePipeline(const GraphicsPipelineStateCreateInfo& CreateInfo)
329329
{
330330
TShaderStages ShaderStages = InitInternalObjects(CreateInfo);
331-
if (!m_InitializeTaskRunning.load())
331+
if (!m_AsyncInitializer)
332332
{
333333
InitializeWebGPURenderPipeline(ShaderStages);
334334
}
@@ -342,7 +342,7 @@ void PipelineStateWebGPUImpl::InitializePipeline(const GraphicsPipelineStateCrea
342342
void PipelineStateWebGPUImpl::InitializePipeline(const ComputePipelineStateCreateInfo& CreateInfo)
343343
{
344344
TShaderStages ShaderStages = InitInternalObjects(CreateInfo);
345-
if (!m_InitializeTaskRunning.load())
345+
if (!m_AsyncInitializer)
346346
{
347347
InitializeWebGPUComputePipeline(ShaderStages);
348348
}

Graphics/GraphicsEngineWebGPU/src/ShaderWebGPUImpl.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,7 @@ ShaderWebGPUImpl::ShaderWebGPUImpl(IReferenceCounters* pRefCounters,
279279
}
280280
else
281281
{
282-
m_CompileTaskRunning.store(true);
283-
m_wpCompileTask = EnqueueAsyncWork(
282+
this->m_AsyncInitializer = AsyncInitializer::Start(
284283
WebGPUShaderCI.pCompilationThreadPool,
285284
[this,
286285
ShaderCI = ShaderCreateInfoWrapper{ShaderCI, GetRawAllocator()},

0 commit comments

Comments
 (0)