Skip to content

Commit cb5740c

Browse files
GPUUploadManagerTest: allow initialization with null device context
1 parent 0aad2e1 commit cb5740c

2 files changed

Lines changed: 145 additions & 26 deletions

File tree

Graphics/GraphicsTools/src/GPUUploadManagerImpl.cpp

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,6 @@ GPUUploadManagerImpl::Page::Page(IRenderDevice* pDevice,
110110
Desc.CPUAccessFlags = CPU_ACCESS_WRITE;
111111
pDevice->CreateBuffer(Desc, nullptr, &m_pStagingBuffer);
112112
VERIFY_EXPR(m_pStagingBuffer != nullptr);
113-
114-
RENDER_DEVICE_TYPE DeviceType = pDevice->GetDeviceInfo().Type;
115-
116-
MAP_FLAGS MapFlags = (DeviceType == RENDER_DEVICE_TYPE_D3D12 || DeviceType == RENDER_DEVICE_TYPE_VULKAN) ?
117-
MAP_FLAG_DO_NOT_WAIT :
118-
MAP_FLAG_NONE;
119-
pContext->MapBuffer(m_pStagingBuffer, MAP_WRITE, MapFlags, m_pData);
120-
VERIFY_EXPR(m_pData != nullptr);
121113
}
122114

123115
GPUUploadManagerImpl::Page::Writer GPUUploadManagerImpl::Page::TryBeginWriting()
@@ -276,12 +268,12 @@ void GPUUploadManagerImpl::Page::Reset(IDeviceContext* pContext)
276268
m_Enqueued.store(false);
277269
m_FenceValue = 0;
278270

279-
if (pContext != nullptr)
271+
if (pContext != nullptr && m_pData == nullptr)
280272
{
281-
if (!m_PersistentMapped)
282-
{
283-
pContext->MapBuffer(m_pStagingBuffer, MAP_WRITE, MAP_FLAG_NONE, m_pData);
284-
}
273+
const MAP_FLAGS MapFlags = m_PersistentMapped ?
274+
MAP_FLAG_DO_NOT_WAIT :
275+
MAP_FLAG_NONE;
276+
pContext->MapBuffer(m_pStagingBuffer, MAP_WRITE, MapFlags, m_pData);
285277
VERIFY_EXPR(m_pData != nullptr);
286278
}
287279
}
@@ -375,16 +367,31 @@ GPUUploadManagerImpl::GPUUploadManagerImpl(IReferenceCounters* pRefCounters, con
375367
m_pDevice->CreateFence(Desc, &m_pFence);
376368
VERIFY_EXPR(m_pFence != nullptr);
377369

378-
// Create at least one page.
379-
m_pCurrentPage.store(CreatePage(CI.pContext), std::memory_order_release);
370+
if (CI.pContext != nullptr)
371+
{
372+
// Create at least one page.
373+
m_pCurrentPage.store(CreatePage(CI.pContext), std::memory_order_release);
374+
}
380375

381376
// Create additional pages if requested.
382377
const Uint32 InitialPageCount = CI.MaxPageCount > 0 ?
383378
std::min(CI.InitialPageCount, CI.MaxPageCount) :
384379
CI.InitialPageCount;
385380
while (m_Pages.size() < InitialPageCount)
386381
{
387-
m_FreePages.Push(CreatePage(CI.pContext));
382+
Page* pPage = CreatePage(CI.pContext);
383+
if (CI.pContext != nullptr)
384+
{
385+
// If a context is provided, we can immediately map the staging buffer and
386+
// prepare the page for use, so we push it to the free list.
387+
m_FreePages.Push(pPage);
388+
}
389+
else
390+
{
391+
// If no context is provided, the page needs to be mapped in Reset(),
392+
// so we add it to the list of in-flight pages.
393+
m_InFlightPages.emplace_back(pPage);
394+
}
388395
}
389396
}
390397

@@ -406,7 +413,15 @@ GPUUploadManagerImpl::~GPUUploadManagerImpl()
406413

407414
void GPUUploadManagerImpl::RenderThreadUpdate(IDeviceContext* pContext)
408415
{
409-
DEV_CHECK_ERR(pContext == m_pContext, "The context passed to RenderThreadUpdate must be the same as the one used to create the GPUUploadManagerImpl");
416+
if (!m_pContext)
417+
{
418+
// If no context was provided at creation, we can accept any context in RenderThreadUpdate, but it must be the same across calls.
419+
m_pContext = pContext;
420+
}
421+
else
422+
{
423+
DEV_CHECK_ERR(pContext == m_pContext, "The context provided to RenderThreadUpdate must be the same as the one used to create the GPUUploadManagerImpl");
424+
}
410425

411426
SealAndSwapCurrentPage(pContext);
412427
ReclaimCompletedPages(pContext);
@@ -473,7 +488,14 @@ void GPUUploadManagerImpl::ScheduleBufferUpdate(IDeviceContext* pC
473488

474489
while (!AbortUpdate && !m_Stopping.load(std::memory_order_acquire))
475490
{
476-
Page* P = m_pCurrentPage.load(std::memory_order_acquire);
491+
Page* P = m_pCurrentPage.load(std::memory_order_acquire);
492+
if (P == nullptr)
493+
{
494+
// No current page, wait for a page to become available
495+
UpdatePendingSizeAndTryRotate(P);
496+
continue;
497+
}
498+
477499
Page::Writer Writer = P->TryBeginWriting();
478500
if (!Writer)
479501
{
@@ -531,6 +553,10 @@ GPUUploadManagerImpl::Page* GPUUploadManagerImpl::CreatePage(IDeviceContext* pCo
531553
std::unique_ptr<Page> NewPage = std::make_unique<Page>(m_pDevice, pContext, PageSize);
532554

533555
Page* P = NewPage.get();
556+
if (pContext != nullptr)
557+
{
558+
P->Reset(pContext);
559+
}
534560
m_Pages.emplace_back(std::move(NewPage));
535561

536562
return P;
@@ -548,7 +574,7 @@ bool GPUUploadManagerImpl::SealAndSwapCurrentPage(IDeviceContext* pContext)
548574
Page* pOld = m_pCurrentPage.exchange(pFreshPage, std::memory_order_acq_rel);
549575

550576
// Seal old page and enqueue if no writers; otherwise last writer will enqueue it
551-
if (pOld->TrySeal() == Page::SealStatus::Ready)
577+
if (pOld != nullptr && pOld->TrySeal() == Page::SealStatus::Ready)
552578
{
553579
TryEnqueuePage(pOld);
554580
}
@@ -572,7 +598,7 @@ bool GPUUploadManagerImpl::TryRotatePage(IDeviceContext* pContext, Page* Expecte
572598
}
573599

574600
// We won: seal and enqueue if no writers
575-
if (ExpectedCurrent && ExpectedCurrent->TrySeal() == Page::SealStatus::Ready)
601+
if (ExpectedCurrent != nullptr && ExpectedCurrent->TrySeal() == Page::SealStatus::Ready)
576602
TryEnqueuePage(ExpectedCurrent);
577603

578604
m_PageRotatedSignal.Tick();

Tests/DiligentCoreAPITest/src/GPUUploadManagerTest.cpp

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,21 +255,21 @@ TEST(GPUUploadManagerTest, DestroyWhileUpdatesAreRunning)
255255
const size_t kNumThreads = 4;
256256
std::vector<std::thread> Threads;
257257
std::atomic<Uint32> NumUpdatesRunning{0};
258+
Threading::Signal AllThreadsRunningSignal;
258259
for (size_t t = 0; t < kNumThreads; ++t)
259260
{
260261
Threads.emplace_back(
261262
[&]() {
262-
NumUpdatesRunning.fetch_add(1);
263+
if (NumUpdatesRunning.fetch_add(1) == kNumThreads - 1)
264+
{
265+
AllThreadsRunningSignal.Trigger();
266+
}
263267
pUploadManager->ScheduleBufferUpdate(nullptr, nullptr, 0, 2048, nullptr);
264268
NumUpdatesRunning.fetch_sub(1);
265269
});
266270
}
267271

268-
// Wait until all threads are running updates
269-
while (NumUpdatesRunning.load() < kNumThreads)
270-
{
271-
std::this_thread::yield();
272-
}
272+
AllThreadsRunningSignal.Wait();
273273

274274
std::this_thread::sleep_for(10ms);
275275
EXPECT_EQ(NumUpdatesRunning.load(), kNumThreads) << "All threads should be running updates because RenderThreadUpdate() was not called";
@@ -284,4 +284,97 @@ TEST(GPUUploadManagerTest, DestroyWhileUpdatesAreRunning)
284284
}
285285
}
286286

287+
288+
TEST(GPUUploadManagerTest, CreateWithNullContext)
289+
{
290+
GPUTestingEnvironment* pEnv = GPUTestingEnvironment::GetInstance();
291+
IRenderDevice* pDevice = pEnv->GetDevice();
292+
IDeviceContext* pContext = pEnv->GetDeviceContext();
293+
294+
GPUTestingEnvironment::ScopedReset AutoReset;
295+
296+
RefCntAutoPtr<IGPUUploadManager> pUploadManager;
297+
GPUUploadManagerCreateInfo CreateInfo{pDevice};
298+
CreateInfo.PageSize = 1024;
299+
CreateInfo.InitialPageCount = 8;
300+
CreateGPUUploadManager(CreateInfo, &pUploadManager);
301+
ASSERT_TRUE(pUploadManager != nullptr);
302+
303+
const size_t kNumThreads = std::max(2u, std::thread::hardware_concurrency() - 1);
304+
LOG_INFO_MESSAGE("Number of threads: ", kNumThreads);
305+
306+
constexpr size_t kNumUpdatesPerThread = 16;
307+
constexpr size_t kUpdateSize = 2048;
308+
309+
std::vector<Uint8> BufferData(kNumUpdatesPerThread * kUpdateSize * kNumThreads);
310+
for (size_t i = 0; i < BufferData.size(); ++i)
311+
{
312+
BufferData[i] = static_cast<Uint8>(i % 256);
313+
}
314+
315+
BufferDesc Desc;
316+
Desc.Name = "GPUUploadManagerTest buffer";
317+
Desc.Size = BufferData.size();
318+
Desc.Usage = USAGE_DEFAULT;
319+
Desc.BindFlags = BIND_VERTEX_BUFFER;
320+
321+
RefCntAutoPtr<IBuffer> pBuffer;
322+
pDevice->CreateBuffer(Desc, nullptr, &pBuffer);
323+
ASSERT_TRUE(pBuffer);
324+
325+
std::vector<std::thread> Threads;
326+
std::atomic<Uint32> NumUpdatesRunning{0};
327+
Threading::Signal AllThreadsRunningSignal;
328+
329+
std::atomic<Uint32> CurrOffset{0};
330+
for (size_t t = 0; t < kNumThreads; ++t)
331+
{
332+
Threads.emplace_back(
333+
[&]() {
334+
if (NumUpdatesRunning.fetch_add(1) == kNumThreads - 1)
335+
{
336+
AllThreadsRunningSignal.Trigger();
337+
}
338+
for (size_t i = 0; i < kNumUpdatesPerThread; ++i)
339+
{
340+
Uint32 Offset = CurrOffset.fetch_add(kUpdateSize);
341+
pUploadManager->ScheduleBufferUpdate(nullptr, pBuffer, Offset, kUpdateSize, &BufferData[Offset]);
342+
}
343+
NumUpdatesRunning.fetch_sub(1);
344+
});
345+
}
346+
347+
AllThreadsRunningSignal.Wait();
348+
349+
std::this_thread::sleep_for(10ms);
350+
EXPECT_EQ(NumUpdatesRunning.load(), kNumThreads) << "All threads should be running updates because RenderThreadUpdate() was not called";
351+
352+
while (NumUpdatesRunning.load() > 0)
353+
{
354+
pUploadManager->RenderThreadUpdate(pContext);
355+
pContext->Flush();
356+
pContext->FinishFrame();
357+
std::this_thread::sleep_for(10ms);
358+
}
359+
360+
pUploadManager->RenderThreadUpdate(pContext);
361+
362+
for (std::thread& thread : Threads)
363+
{
364+
thread.join();
365+
}
366+
367+
VerifyBufferContents(pBuffer, BufferData);
368+
369+
GPUUploadManagerStats Stats;
370+
pUploadManager->GetStats(Stats);
371+
LOG_INFO_MESSAGE("GPU Upload Manager Stats:"
372+
"\n NumPages ",
373+
Stats.NumPages,
374+
"\n NumFreePages ", Stats.NumFreePages,
375+
"\n NumInFlightPages ", Stats.NumInFlightPages,
376+
"\n PeakTotalPendingUpdateSize ", Stats.PeakTotalPendingUpdateSize,
377+
"\n PeakUpdateSize ", Stats.PeakUpdateSize);
378+
}
379+
287380
} // namespace

0 commit comments

Comments
 (0)