1 #include <gmock/gmock.h>
2 #include <gtest/gtest.h>
3 
4 #include <CommandBufferStagingStream.h>
5 #include <string.h>
6 
7 #include <condition_variable>
8 #include <mutex>
9 #include <string_view>
10 #include <thread>
11 
12 namespace gfxstream {
13 namespace vk {
14 
15 using ::testing::A;
16 using ::testing::ElementsAre;
17 using ::testing::Eq;
18 using ::testing::Ge;
19 using ::testing::InSequence;
20 using ::testing::IsNull;
21 using ::testing::MockFunction;
22 using ::testing::NotNull;
23 using ::testing::Return;
24 
25 static constexpr size_t kTestBufferSize = 1048576;
26 
27 // tests allocBuffer can successfully allocate a buffer of given size
TEST(CommandBufferStagingStreamTest,AllocateBufferTestUnderMinSize)28 TEST(CommandBufferStagingStreamTest, AllocateBufferTestUnderMinSize) {
29     CommandBufferStagingStream stream;
30     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
31     EXPECT_THAT(buffer, NotNull());
32 }
33 
34 // test reallocate buffer remembers previously committed buffers
TEST(CommandBufferStagingStreamTest,ReallocateBuffer)35 TEST(CommandBufferStagingStreamTest, ReallocateBuffer) {
36     CommandBufferStagingStream stream;
37     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
38     EXPECT_THAT(buffer, NotNull());
39 
40     // write some data
41     const std::string_view commandData{"some command"};
42     const size_t dataSize = commandData.size();
43     memcpy(buffer, commandData.data(), dataSize);
44 
45     // commit data
46     stream.commitBuffer(dataSize);
47 
48     // this will result in a reallocation
49     buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
50     EXPECT_THAT(buffer, NotNull());
51 
52     // get stream data
53     uint8_t* outBuf = nullptr;
54     size_t outSize = 0;
55     stream.getWritten(&outBuf, &outSize);
56 
57     std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
58     EXPECT_THAT(actualData, Eq(commandData));
59 }
60 
61 // tests commitBuffer tracks the portion of the buffer to be committed
TEST(CommandBufferStagingStreamTest,CommitBuffer)62 TEST(CommandBufferStagingStreamTest, CommitBuffer) {
63     CommandBufferStagingStream stream;
64     // allocate buffer
65     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
66 
67     // write some arbitrary data
68     const std::string_view commandData{"some command"};
69     const size_t dataSize = commandData.size();
70     memcpy(buffer, commandData.data(), dataSize);
71 
72     // commit
73     stream.commitBuffer(dataSize);
74 
75     // writeBuffer should have the committed portion in CommandBufferStagingStream
76     uint8_t* outBuf = nullptr;
77     size_t outSize = 0;
78     stream.getWritten(&outBuf, &outSize);
79 
80     std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
81     EXPECT_THAT(actualData, Eq(commandData));
82 }
83 
84 // tests reset api
TEST(CommandBufferStagingStreamTest,Reset)85 TEST(CommandBufferStagingStreamTest, Reset) {
86     CommandBufferStagingStream stream;
87     // allocate buffer
88     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
89 
90     // write some arbitrary data
91     const std::string_view commandData{"some command"};
92     const size_t dataSize = commandData.size();
93     memcpy(buffer, commandData.data(), dataSize);
94 
95     // commit
96     stream.commitBuffer(dataSize);
97 
98     // reset
99     stream.reset();
100 
101     size_t outSize;
102     unsigned char* outBuf;
103     // write buffer
104     stream.getWritten(&outBuf, &outSize);
105 
106     // outSize should be 0 after reset
107     EXPECT_EQ(outSize, 0) << "no data should be available for a write after a reset";
108 }
109 
110 // tests that multiple alloc calls do not result in reallocations
TEST(CommandBufferStagingStreamTest,MultipleAllocationCalls)111 TEST(CommandBufferStagingStreamTest, MultipleAllocationCalls) {
112     CommandBufferStagingStream stream;
113     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
114     EXPECT_THAT(buffer, NotNull());
115 
116     // another call to allocBuffer should not result in reallocations
117     uint8_t* anotherBuffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
118     EXPECT_THAT(anotherBuffer, Eq(buffer));
119 }
120 
121 // this test verifies that allocBuffer doesn't cause reallcation if buffer has available space
TEST(CommandBufferStagingStream,NoReallocationIfBufferIsNotFull)122 TEST(CommandBufferStagingStream, NoReallocationIfBufferIsNotFull) {
123     CommandBufferStagingStream stream;
124     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
125     EXPECT_THAT(buffer, NotNull());
126 
127     // commit portion of buffer
128     const size_t writeSize = 10;
129     stream.commitBuffer(writeSize);
130 
131     uint8_t* writePtr = static_cast<uint8_t*>(stream.allocBuffer(writeSize));
132     EXPECT_THAT(writePtr, buffer + writeSize);
133 }
134 
135 // tests that data written prior to reallocation is still intact
TEST(CommandBufferStagingStreamTest,ReallocationBoundary)136 TEST(CommandBufferStagingStreamTest, ReallocationBoundary) {
137     CommandBufferStagingStream stream;
138     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
139     EXPECT_THAT(buffer, NotNull());
140 
141     // split data into two batches
142     const std::string firstBatchData(kTestBufferSize, 'a');
143 
144     // copy first batch of data into stream
145     memcpy(buffer, firstBatchData.data(), kTestBufferSize);
146 
147     // commit first batch of data
148     stream.commitBuffer(firstBatchData.size());
149 
150     // size of first batch of data brings stream buffer to capacity; this will result in a
151     // reallocation
152     buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
153     EXPECT_THAT(buffer, NotNull());
154 
155     // data written before reallocation should be intact
156     unsigned char* outBuf = nullptr;
157     size_t outSize = 0;
158     stream.getWritten(&outBuf, &outSize);
159 
160     const std::string_view expectedData{firstBatchData};
161     std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
162     EXPECT_THAT(actualData, Eq(expectedData));
163 }
164 
165 // this test is a death test for unsupported apis
TEST(CommandBufferStagingStreamDeathTest,UnsupportedAPIs)166 TEST(CommandBufferStagingStreamDeathTest, UnsupportedAPIs) {
167     CommandBufferStagingStream stream;
168 
169     EXPECT_DEATH(
170         {
171             void* buffer = nullptr;
172             size_t size = 0;
173             stream.readFully(buffer, size);
174         },
175         "")
176         << "readFully should not be supported";
177 
178     EXPECT_DEATH(
179         {
180             void* buffer = nullptr;
181             size_t size = 0;
182             stream.read(buffer, &size);
183         },
184         "")
185         << "read should not be supported";
186 
187     EXPECT_DEATH(
188         {
189             void* buffer = nullptr;
190             size_t size = 0;
191             stream.writeFully(buffer, size);
192         },
193         "")
194         << "writeFully should not be supported";
195 
196     EXPECT_DEATH(
197         {
198             void* buffer = nullptr;
199             size_t size = 0;
200             size_t len = 0;
201             stream.commitBufferAndReadFully(size, buffer, len);
202         },
203         "")
204         << "commitBufferAndReadFully should not be supported";
205 }
206 
207 using MockAlloc = MockFunction<CommandBufferStagingStream::Memory(size_t)>;
208 using MockFree = MockFunction<void(const CommandBufferStagingStream::Memory&)>;
209 // default empty implementation of free
210 static std::function<void(const CommandBufferStagingStream::Memory&)> EmptyFree =
__anonbaa63fbc0102(const CommandBufferStagingStream::Memory&) 211     [](const CommandBufferStagingStream::Memory&) {};
212 // CommandBufferStagingStreamCustomAllocationTest tests behavior of CommandBufferStagingStream
213 // when initialized with custom allocator/free function.
214 // These tests test the same outcome as CommandBufferStagingStreamTest tests
215 
216 // tests allocBuffer can successfully allocate a buffer of given size
TEST(CommandBufferStagingStreamCustomAllocationTest,AllocateBufferTest)217 TEST(CommandBufferStagingStreamCustomAllocationTest, AllocateBufferTest) {
218     // memory source
219     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
220 
221     CommandBufferStagingStream::Memory memory{
222         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
223         .ptr = memorySrc.data()};
224 
225     MockAlloc allocFn;
226 
227     // alloc function should be called once
228     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
229 
230     // free function should be called once
231     MockFree freeFn;
232     EXPECT_CALL(freeFn, Call(Eq(memory))).Times(1);
233 
234     // scope: CommandBufferStagingStream_Creation
235     {
236         auto allocStdFn = allocFn.AsStdFunction();
237         auto freeStdFn = freeFn.AsStdFunction();
238         CommandBufferStagingStream stream(allocStdFn, freeStdFn);
239         uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
240         EXPECT_THAT(buffer, NotNull());
241     }
242 }
243 
244 // test allocBuffer returns nullptr if custom allocation fails
TEST(CommandBufferStagingStreamCustomAllocationTest,AllocateBufferFailure)245 TEST(CommandBufferStagingStreamCustomAllocationTest, AllocateBufferFailure) {
246     // memory source for initial allocation
247     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
248 
249     CommandBufferStagingStream::Memory memory{
250         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
251         .ptr = nullptr,                  // to test alloc call failing
252     };
253 
254     MockAlloc allocFn;
255 
256     // alloc function should be called once
257     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
258 
259     // free function should not be called if allocation fails
260     MockFree freeFn;
261     EXPECT_CALL(freeFn, Call).Times(0);
262 
263     // scope: CommandBufferStagingStream_Creation
264     {
265         auto allocStdFn = allocFn.AsStdFunction();
266         auto freeStdFn = freeFn.AsStdFunction();
267         CommandBufferStagingStream stream(allocStdFn, freeStdFn);
268         void* buffer = stream.allocBuffer(kTestBufferSize);
269         EXPECT_THAT(buffer, IsNull());
270     }
271 }
272 
TEST(CommandBufferStagingStreamCustomAllocationTest,DeviceMemoryPointerIsPassedDuringFree)273 TEST(CommandBufferStagingStreamCustomAllocationTest, DeviceMemoryPointerIsPassedDuringFree) {
274     // memory source
275     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
276 
277     // device memory for test purposes. The test just needs a pointer
278     uint64_t deviceMem = 0;
279     VkDeviceMemory deviceMemPtr = (VkDeviceMemory)(&deviceMem);
280 
281     CommandBufferStagingStream::Memory memory{.deviceMemory = deviceMemPtr,
282                                               .ptr = memorySrc.data()};
283 
284     MockAlloc allocFn;
285 
286     // alloc function should be called once
287     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
288 
289     // free function should be called once
290     MockFree freeFn;
291     EXPECT_CALL(freeFn, Call(Eq(memory))).Times(1);
292 
293     // scope: CommandBufferStagingStream_Creation
294     {
295         auto allocStdFn = allocFn.AsStdFunction();
296         auto freeStdFn = freeFn.AsStdFunction();
297         CommandBufferStagingStream stream(allocStdFn, freeStdFn);
298         uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
299         EXPECT_THAT(buffer, NotNull());
300     }
301 }
302 
303 // test verifies that there are no crashes if alloc/free function reference becomes null
TEST(CommandBufferStagingStreamCustomAllocationTest,AllocFreeInvalidReference)304 TEST(CommandBufferStagingStreamCustomAllocationTest, AllocFreeInvalidReference) {
305     MockAlloc allocFn;
306     // alloc shouldn't be called if reference is invalidated
307     EXPECT_CALL(allocFn, Call).Times(0);
308 
309     MockFree freeFn;
310     // free shouldn't be called if reference is invalidated
311     EXPECT_CALL(freeFn, Call).Times(0);
312 
313     auto allocStdFn = allocFn.AsStdFunction();
314     auto freeStdFn = freeFn.AsStdFunction();
315     // scope: CommandBufferStagingStream_Creation
316     {
317         CommandBufferStagingStream stream(allocStdFn, freeStdFn);
318         // invalidate alloc/free functions
319         allocStdFn = nullptr;
320         freeStdFn = nullptr;
321         stream.allocBuffer(kTestBufferSize);
322         uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
323         EXPECT_THAT(buffer, IsNull());
324     }
325 }
326 
327 // test reallocate buffer remembers previously committed buffers
TEST(CommandBufferStagingStreamCustomAllocationTest,ReallocateBuffer)328 TEST(CommandBufferStagingStreamCustomAllocationTest, ReallocateBuffer) {
329     // memory source for initial allocation
330     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
331     // memory source after reallocation
332     std::vector<uint8_t> reallocatedMemorySrc(kTestBufferSize * 3);
333 
334     CommandBufferStagingStream::Memory memory{
335         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
336         .ptr = memorySrc.data()};
337 
338     CommandBufferStagingStream::Memory reallocatedMemory{
339         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
340         .ptr = reallocatedMemorySrc.data()};
341 
342     MockAlloc allocFn;
343 
344     // alloc function should be called twice
345     {
346         InSequence seq;
347 
348         // expect initial allocation call with allocation size == kTestBufferSize;
349         EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
350 
351         // expect reallocation call with allocation size > kTestBufferSize.
352         EXPECT_CALL(allocFn, Call(testing::Ge(kTestBufferSize)))
353             .Times(1)
354             .WillRepeatedly(Return(reallocatedMemory));
355     }
356 
357     MockFree freeFn;
358     {
359         InSequence seq;
360         // free function should be called when reallocation happens
361         EXPECT_CALL(freeFn, Call(Eq(memory))).Times(1);
362         // free function should be called when stream goes out of scope
363         EXPECT_CALL(freeFn, Call(Eq(reallocatedMemory))).Times(1);
364     }
365 
366     // scope: CommandBufferStagingStream_Creation
367     {
368         auto allocStdFn = allocFn.AsStdFunction();
369         auto freeStdFn = freeFn.AsStdFunction();
370         CommandBufferStagingStream stream(allocStdFn, freeStdFn);
371         uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
372         EXPECT_THAT(buffer, NotNull());
373 
374         // write some data
375         const std::string_view commandData{"some command"};
376         const size_t dataSize = commandData.size();
377         memcpy(buffer, commandData.data(), dataSize);
378 
379         // commit data
380         stream.commitBuffer(dataSize);
381 
382         // this will result in a reallocation
383         buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
384         EXPECT_THAT(buffer, NotNull());
385 
386         // get stream data
387         unsigned char* outBuf = nullptr;
388         size_t outSize = 0;
389         stream.getWritten(&outBuf, &outSize);
390 
391         std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
392         EXPECT_THAT(actualData, Eq(commandData));
393     }
394 }
395 
396 // tests commitBuffer tracks the portion of the buffer to be committed
TEST(CommandBufferStagingStreamCustomAllocationTest,CommitBuffer)397 TEST(CommandBufferStagingStreamCustomAllocationTest, CommitBuffer) {
398     // memory source
399     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
400 
401     CommandBufferStagingStream::Memory memory{
402         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
403         .ptr = memorySrc.data()};
404 
405     MockAlloc allocFn;
406 
407     // alloc function should be called once
408     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
409     auto allocStdFn = allocFn.AsStdFunction();
410     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
411     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
412     EXPECT_THAT(buffer, NotNull());
413 
414     // write some arbitrary data
415     const std::string_view commandData{"some command"};
416     const size_t dataSize = commandData.size();
417     memcpy(buffer, commandData.data(), dataSize);
418 
419     // commit
420     stream.commitBuffer(dataSize);
421 
422     // writeBuffer should have the committed portion in CommandBufferStagingStream
423     uint8_t* outBuf = nullptr;
424     size_t outSize = 0;
425     stream.getWritten(&outBuf, &outSize);
426 
427     std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
428     EXPECT_THAT(actualData, Eq(commandData));
429 }
430 
431 // tests reset api
TEST(CommandBufferStagingStreamCustomAllocationTest,Reset)432 TEST(CommandBufferStagingStreamCustomAllocationTest, Reset) {
433     // memory source
434     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
435 
436     CommandBufferStagingStream::Memory memory{
437         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
438         .ptr = memorySrc.data()};
439 
440     MockAlloc allocFn;
441 
442     // alloc function should be called once
443     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
444     auto allocStdFn = allocFn.AsStdFunction();
445     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
446     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
447 
448     // write some arbitrary data
449     const std::string_view commandData{"some command"};
450     const size_t dataSize = commandData.size();
451     memcpy(buffer, commandData.data(), dataSize);
452 
453     // commit
454     stream.commitBuffer(dataSize);
455 
456     // reset
457     stream.reset();
458 
459     size_t outSize;
460     unsigned char* outBuf;
461     // write buffer
462     stream.getWritten(&outBuf, &outSize);
463 
464     // outSize should be 0 after reset
465     EXPECT_EQ(outSize, 0) << "no data should be available for a write after a reset";
466 }
467 
468 // tests that multiple alloc calls do not result in reallocations
TEST(CommandBufferStagingStreamCustomAllocationTest,MultipleAllocationCalls)469 TEST(CommandBufferStagingStreamCustomAllocationTest, MultipleAllocationCalls) {
470     // memory source
471     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
472 
473     CommandBufferStagingStream::Memory memory{
474         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
475         .ptr = memorySrc.data()};
476 
477     MockAlloc allocFn;
478 
479     // alloc function should be called once, no reallocation
480     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
481 
482     auto allocStdFn = allocFn.AsStdFunction();
483     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
484     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
485     EXPECT_THAT(buffer, NotNull());
486 
487     // another call to allocBuffer should not result in reallocations
488     uint8_t* anotherBuffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
489     EXPECT_THAT(anotherBuffer, Eq(buffer));
490 }
491 
492 // tests that data written prior to reallocation is still intact
TEST(CommandBufferStagingStreamCustomAllocationTest,ReallocationBoundary)493 TEST(CommandBufferStagingStreamCustomAllocationTest, ReallocationBoundary) {
494     // memory source
495     std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
496     CommandBufferStagingStream::Memory memory{
497         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
498         .ptr = memorySrc.data()};
499 
500     MockAlloc allocFn;
501 
502     // alloc function should be called twice
503     {
504         InSequence seq;
505 
506         // expect initial allocation call with allocation size >= kTestBufferSize;
507         EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
508 
509         // expect reallocation call with allocation size > kTestBufferSize.
510         EXPECT_CALL(allocFn, Call(testing::Ge(kTestBufferSize)))
511             .Times(1)
512             .WillRepeatedly(Return(memory));
513     }
514 
515     // free function should be called once during reallocation,
516     // once when stream goes out of scope
517     MockFree freeFn;
518 
519     EXPECT_CALL(freeFn, Call(Eq(memory))).Times(2);
520     auto allocStdFn = allocFn.AsStdFunction();
521     auto freeStdFn = freeFn.AsStdFunction();
522     CommandBufferStagingStream stream(allocStdFn, freeStdFn);
523     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
524     EXPECT_THAT(buffer, NotNull());
525 
526     // split data into two batches
527     // size of first batch data should be enough to fill the allocated buffer
528     // first data batch size = kTestBufferSize - sync data size
529     const std::string firstBatchData(kTestBufferSize - CommandBufferStagingStream::kSyncDataSize,
530                                      'a');
531 
532     // copy first batch of data into stream
533     memcpy(buffer, firstBatchData.data(), firstBatchData.size());
534 
535     // commit first batch of data
536     stream.commitBuffer(firstBatchData.size());
537 
538     // size of first batch of data brings stream buffer to capacity; this will result in a
539     // reallocation
540     buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
541     EXPECT_THAT(buffer, NotNull());
542 
543     // data written before reallocation should be intact
544     unsigned char* outBuf = nullptr;
545     size_t outSize = 0;
546     stream.getWritten(&outBuf, &outSize);
547 
548     const std::string_view expectedData{firstBatchData};
549     std::string_view actualData{reinterpret_cast<char*>(outBuf), outSize};
550     EXPECT_THAT(actualData, Eq(expectedData));
551 }
552 
553 // this test verifies that allocBuffer doesn't cause reallcation if buffer has available space
TEST(CommandBufferStagingStreamCustomAllocationTest,NoReallocationIfBufferIsNotFull)554 TEST(CommandBufferStagingStreamCustomAllocationTest, NoReallocationIfBufferIsNotFull) {
555     // memory source
556     std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
557 
558     CommandBufferStagingStream::Memory memory{
559         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
560         .ptr = memorySrc.data()};
561 
562     MockAlloc allocFn;
563     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
564 
565     auto allocStdFn = allocFn.AsStdFunction();
566     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
567     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
568     EXPECT_THAT(buffer, NotNull());
569 
570     // commit portion of buffer
571     const size_t writeSize = 10;
572     stream.commitBuffer(writeSize);
573 
574     uint8_t* writePtr = static_cast<uint8_t*>(stream.allocBuffer(writeSize));
575     EXPECT_THAT(writePtr, buffer + writeSize);
576 }
577 
578 // this test verifies that CommandBufferStagingStream accounts for metadata in the
579 // beginning of its stream buffer
TEST(CommandBufferStagingStreamCustomAllocationTest,MetadataCheck)580 TEST(CommandBufferStagingStreamCustomAllocationTest, MetadataCheck) {
581     // memory source
582     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
583     CommandBufferStagingStream::Memory memory{
584         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
585         .ptr = memorySrc.data()};
586 
587     // CommandBufferStagingStream allocates metadata when using custom allocators
588     static const size_t expectedMetadataSize = 8;
589     MockAlloc allocFn;
590 
591     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
592 
593     auto allocStdFn = allocFn.AsStdFunction();
594     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
595     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
596     // data should start after metadata
597     EXPECT_THAT(buffer, memorySrc.data() + expectedMetadataSize);
598     // metadata should be initialized to read complete
599     uint32_t* metadataPtr = reinterpret_cast<uint32_t*>(memorySrc.data());
600     EXPECT_THAT(*metadataPtr, CommandBufferStagingStream::kSyncDataReadComplete);
601 }
602 
TEST(CommandBufferStagingStreamCustomAllocationTest,MarkFlushing)603 TEST(CommandBufferStagingStreamCustomAllocationTest, MarkFlushing) {
604     // memory source for  allocation
605     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
606     CommandBufferStagingStream::Memory memory{
607         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
608         .ptr = memorySrc.data()};
609     MockAlloc allocFn;
610 
611     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
612 
613     auto allocStdFn = allocFn.AsStdFunction();
614     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
615     uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
616 
617     // write some data
618     const std::string_view commandData{"some command"};
619     const size_t dataSize = commandData.size();
620     memcpy(buffer, commandData.data(), dataSize);
621 
622     // commit data
623     stream.commitBuffer(dataSize);
624 
625     // will set metadata of the stream buffer to pending read
626     stream.markFlushing();
627 
628     uint32_t* readPtr = reinterpret_cast<uint32_t*>(memorySrc.data());
629     EXPECT_EQ(*readPtr, CommandBufferStagingStream::kSyncDataReadPending);
630 }
631 
632 // this test verifies that realloc waits till consumer of memory has completed read
TEST(CommandBufferStagingStreamCustomAllocationTest,ReallocNotCalledTillBufferIsRead)633 TEST(CommandBufferStagingStreamCustomAllocationTest, ReallocNotCalledTillBufferIsRead) {
634     // memory source for  allocation
635     // allocate a big enough buffer to avoid resizes in test
636     std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
637     CommandBufferStagingStream::Memory memory{
638         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
639         .ptr = memorySrc.data()};
640 
641     std::condition_variable memoryFlushedCondition;
642     std::mutex mutex;
643 
644     MockAlloc allocFn;
645 
646     // mock function to notify read is complete
647     // this will be used to set up the expectation that realloc should
648     // not be called till read is complete
649     MockFunction<void(void)> readCompleteCall;
650 
651     testing::Expectation readCompleteExpectation = EXPECT_CALL(readCompleteCall, Call).Times(1);
652 
653     std::thread consumer([&]() {
654         std::unique_lock readLock(mutex);
655         memoryFlushedCondition.wait(readLock, [&]() {
656             // wait till memorySrc is ready for read
657             uint32_t* syncData = static_cast<uint32_t*>(memory.ptr);
658             return *syncData = CommandBufferStagingStream::kSyncDataReadPending;
659         });
660 
661         readLock.unlock();
662 
663         __atomic_store_n(memorySrc.data(), CommandBufferStagingStream::kSyncDataReadComplete,
664                          __ATOMIC_RELEASE);
665 
666         auto fn = readCompleteCall.AsStdFunction();
667         fn();
668     });
669 
670     auto allocStdFn = allocFn.AsStdFunction();
671     CommandBufferStagingStream stream(allocStdFn, EmptyFree);  // scope for writeLock
672     {
673         std::lock_guard writeLock(mutex);
674 
675         EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
676 
677         uint8_t* buffer = static_cast<uint8_t*>(stream.allocBuffer(kTestBufferSize));
678 
679         // write some data
680         const std::string_view commandData{"some command"};
681         const size_t dataSize = commandData.size();
682         memcpy(buffer, commandData.data(), dataSize);
683 
684         // commit data
685         stream.commitBuffer(dataSize);
686 
687         // will set metadata of the stream buffer to pending read
688         stream.markFlushing();
689 
690         memoryFlushedCondition.notify_one();
691     }
692 
693     // expecatation call for reallocation call
694     EXPECT_CALL(allocFn, Call(testing::Ge(kTestBufferSize)))
695         .Times(1)
696         .After(readCompleteExpectation)
697         .WillRepeatedly(Return(memory));
698 
699     // realloc will be blocked till buffer read is complete by reader
700     (void)stream.allocBuffer(kTestBufferSize);
701 
702     // wait for read thread to finish
703     consumer.join();
704 }
705 
706 // this test verifies that allocBuffer() cannot be called on a stream
707 // that is currently being read by the host
TEST(CommandBufferStagingStreamCustomAllocationTest,AllocBufferFailsIfReadPending)708 TEST(CommandBufferStagingStreamCustomAllocationTest, AllocBufferFailsIfReadPending) {
709     // memory source for  allocation
710     std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
711     CommandBufferStagingStream::Memory memory{
712         .deviceMemory = VK_NULL_HANDLE,  // not needed for this test
713         .ptr = memorySrc.data()};
714     MockAlloc allocFn;
715 
716     EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
717 
718     auto allocStdFn = allocFn.AsStdFunction();
719     CommandBufferStagingStream stream(allocStdFn, EmptyFree);
720     (void)stream.allocBuffer(kTestBufferSize);
721 
722     // will set metadata of the stream buffer to pending read
723     stream.markFlushing();
724 
725     EXPECT_DEATH({ (void)stream.allocBuffer(kTestBufferSize); }, "")
726         << "allocBuffer() should not be called while previous data is being flushed";
727 }
728 
729 }  // namespace vk
730 }  // namespace gfxstream
731