use coherent memory for command buffers
If vulkan aux memory feature is enabled:
- ResourceTracker uses custom alloc/free functions using coherent memory
- CommandBufferStagingStream(s) in ResourceTracker to use custom allocation
- Flush command buffers to aux memory
- abort()if allocBuffer() is called while previous data read is incomplete
- add new test for this behavior
- Test results: https://paste.googleplex.com/5119004271181824
Tested with `flat run emulator` and verified command buffers are being flushed to aux memory and decoded on host
Tested with `full-gles3` and `full-vulkan` benchmarking app
Change-Id: I508d6f6fa6e29386b0764b5f3f78b5159396a260
(cherry picked from commit f7beff89d41bf8ced82e8554819bd00f4132c642)
diff --git a/system/vulkan_enc/CommandBufferStagingStream.cpp b/system/vulkan_enc/CommandBufferStagingStream.cpp
index faee5bf..8487bcd 100644
--- a/system/vulkan_enc/CommandBufferStagingStream.cpp
+++ b/system/vulkan_enc/CommandBufferStagingStream.cpp
@@ -38,13 +38,13 @@
// use default allocators
m_alloc = [](size_t size) -> Memory {
return {
- .deviceMemory = nullptr, // no device memory for malloc
+ .deviceMemory = VK_NULL_HANDLE, // no device memory for malloc
.ptr = malloc(size),
};
};
m_free = [](const Memory& mem) { free(mem.ptr); };
m_realloc = [](const Memory& mem, size_t size) -> Memory {
- return {.deviceMemory = nullptr, .ptr = realloc(mem.ptr, size)};
+ return {.deviceMemory = VK_NULL_HANDLE, .ptr = realloc(mem.ptr, size)};
};
}
@@ -88,7 +88,7 @@
m_free = [&freeFn](const Memory& mem) {
if (!freeFn) {
- ALOGE("Custom free for device memory(%p) failed\n", mem.deviceMemory);
+ ALOGE("Custom free for memory(%p) failed\n", mem.ptr);
return;
}
freeFn(mem);
@@ -186,6 +186,18 @@
return (void*)(getDataPtr() + m_writePos);
}
+ // for custom allocations, host should have finished reading
+ // data from command buffer since command buffers are flushed
+ // on queue submit.
+ // allocBuffer should not be called on command buffers that are currently
+ // being read by the host
+ if (m_usingCustomAlloc) {
+ uint32_t* syncDWordPtr = reinterpret_cast<uint32_t*>(m_mem.ptr);
+ LOG_ALWAYS_FATAL_IF(
+ __atomic_load_n(syncDWordPtr, __ATOMIC_ACQUIRE) != kSyncDataReadComplete,
+ "FATAL: allocBuffer() called but previous read not complete");
+ }
+
return (void*)(getDataPtr() + m_writePos);
}
@@ -235,3 +247,5 @@
m_writePos = 0;
IOStream::rewind();
}
+
+VkDeviceMemory CommandBufferStagingStream::getDeviceMemory() { return m_mem.deviceMemory; }
\ No newline at end of file
diff --git a/system/vulkan_enc/CommandBufferStagingStream.h b/system/vulkan_enc/CommandBufferStagingStream.h
index 0fb859d..2742e20 100644
--- a/system/vulkan_enc/CommandBufferStagingStream.h
+++ b/system/vulkan_enc/CommandBufferStagingStream.h
@@ -16,9 +16,12 @@
#ifndef __COMMAND_BUFFER_STAGING_STREAM_H
#define __COMMAND_BUFFER_STAGING_STREAM_H
-#include "IOStream.h"
+#include <vulkan/vulkan_core.h>
+
#include <functional>
+#include "IOStream.h"
+
class CommandBufferStagingStream : public IOStream {
public:
// host will write kSyncDataReadComplete to the sync bytes to indicate memory is no longer being
@@ -31,11 +34,10 @@
// indicates read is pending
static constexpr uint32_t kSyncDataReadPending = 0X1;
- // alias for DeviceMemory
- using DeviceMemory = void*;
// \struct backing memory structure
struct Memory {
- DeviceMemory deviceMemory = nullptr; // device memory associated with allocated memory
+ VkDeviceMemory deviceMemory =
+ VK_NULL_HANDLE; // device memory associated with allocated memory
void* ptr = nullptr; // pointer to allocated memory
bool operator==(const Memory& rhs) const {
return (deviceMemory == rhs.deviceMemory) && (ptr == rhs.ptr);
@@ -75,6 +77,10 @@
// when not using custom allocators
void markFlushing();
+ // gets the device memory associated with the stream. This is VK_NULL_HANDLE for default allocation
+ // \return device memory
+ VkDeviceMemory getDeviceMemory();
+
private:
// underlying memory for data
Memory m_mem;
diff --git a/system/vulkan_enc/ResourceTracker.cpp b/system/vulkan_enc/ResourceTracker.cpp
index aebcdc0..c5f1985 100644
--- a/system/vulkan_enc/ResourceTracker.cpp
+++ b/system/vulkan_enc/ResourceTracker.cpp
@@ -15,18 +15,15 @@
#include "ResourceTracker.h"
-#include "Resources.h"
-#include "CommandBufferStagingStream.h"
-#include "DescriptorSetVirtualization.h"
-
-#include "aemu/base/Optional.h"
-#include "aemu/base/threads/AndroidWorkPool.h"
-#include "aemu/base/Tracing.h"
-
-#include "goldfish_vk_private_defs.h"
-
#include "../OpenglSystemCommon/EmulatorFeatureInfo.h"
#include "../OpenglSystemCommon/HostConnection.h"
+#include "CommandBufferStagingStream.h"
+#include "DescriptorSetVirtualization.h"
+#include "Resources.h"
+#include "aemu/base/Optional.h"
+#include "aemu/base/Tracing.h"
+#include "aemu/base/threads/AndroidWorkPool.h"
+#include "goldfish_vk_private_defs.h"
#include "vulkan/vulkan_core.h"
/// Use installed headers or locally defined Fuchsia-specific bits
@@ -205,6 +202,14 @@
Lock mLock;
std::vector<CommandBufferStagingStream*> streams;
std::vector<VkEncoder*> encoders;
+ /// \brief sets alloc and free callbacks for memory allocation for CommandBufferStagingStream(s)
+ /// \param allocFn is the callback to allocate memory
+ /// \param freeFn is the callback to free memory
+ void setAllocFree(CommandBufferStagingStream::Alloc&& allocFn,
+ CommandBufferStagingStream::Free&& freeFn) {
+ mAlloc = allocFn;
+ mFree = freeFn;
+ }
~StagingInfo() {
for (auto stream : streams) {
@@ -228,7 +233,12 @@
CommandBufferStagingStream* stream;
VkEncoder* encoder;
if (streams.empty()) {
- stream = new CommandBufferStagingStream;
+ if (mAlloc && mFree) {
+ // if custom allocators are provided, forward them to CommandBufferStagingStream
+ stream = new CommandBufferStagingStream(mAlloc, mFree);
+ } else {
+ stream = new CommandBufferStagingStream;
+ }
encoder = new VkEncoder(stream);
} else {
stream = streams.back();
@@ -239,6 +249,10 @@
*streamOut = stream;
*encoderOut = encoder;
}
+
+ private:
+ CommandBufferStagingStream::Alloc mAlloc = nullptr;
+ CommandBufferStagingStream::Free mFree = nullptr;
};
static StagingInfo sStaging;
@@ -2933,8 +2947,8 @@
return coherentMemory;
}
- VkResult allocateCoherentMemory(VkDevice device, const VkMemoryAllocateInfo *pAllocateInfo,
- VkEncoder *enc, VkDeviceMemory* pMemory) {
+ VkResult allocateCoherentMemory(VkDevice device, const VkMemoryAllocateInfo* pAllocateInfo,
+ VkEncoder* enc, VkDeviceMemory* pMemory) {
uint64_t offset = 0;
uint8_t *ptr = nullptr;
VkMemoryAllocateFlagsInfo allocFlagsInfo;
@@ -3019,7 +3033,6 @@
VkResult getCoherentMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkEncoder* enc,
VkDevice device, VkDeviceMemory* pMemory) {
-
VkMemoryAllocateFlagsInfo allocFlagsInfo;
VkMemoryOpaqueCaptureAddressAllocateInfo opaqueCaptureAddressAllocInfo;
@@ -3061,6 +3074,9 @@
info.coherentMemory = coherentMemory;
info.device = device;
+ // for suballocated memory, create an alias VkDeviceMemory handle for application
+ // memory used for suballocations will still be VkDeviceMemory associated with
+ // CoherentMemory
auto mem = new_from_host_VkDeviceMemory(VK_NULL_HANDLE);
info_VkDeviceMemory[mem] = info;
*pMemory = mem;
@@ -3731,6 +3747,23 @@
_RETURN_SCUCCESS_WITH_DEVICE_MEMORY_REPORT;
}
+ CoherentMemoryPtr freeCoherentMemoryLocked(VkDeviceMemory memory, VkDeviceMemory_Info& info) {
+ if (info.coherentMemory && info.ptr) {
+ if (info.coherentMemory->getDeviceMemory() != memory) {
+ delete_goldfish_VkDeviceMemory(memory);
+ }
+
+ if (info.ptr) {
+ info.coherentMemory->release(info.ptr);
+ info.ptr = nullptr;
+ }
+
+ return std::move(info.coherentMemory);
+ }
+
+ return nullptr;
+ }
+
void on_vkFreeMemory(
void* context,
VkDevice device,
@@ -3748,15 +3781,12 @@
memoryObjectId = getAHardwareBufferId(info.ahw);
}
#endif
- emitDeviceMemoryReport(
- info_VkDevice[device],
- info.imported ? VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT
- : VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT,
- memoryObjectId,
- 0 /* size */,
- VK_OBJECT_TYPE_DEVICE_MEMORY,
- (uint64_t)(void*)memory
- );
+
+ emitDeviceMemoryReport(info_VkDevice[device],
+ info.imported ? VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_UNIMPORT_EXT
+ : VK_DEVICE_MEMORY_REPORT_EVENT_TYPE_FREE_EXT,
+ memoryObjectId, 0 /* size */, VK_OBJECT_TYPE_DEVICE_MEMORY,
+ (uint64_t)(void*)memory);
#ifdef VK_USE_PLATFORM_FUCHSIA
if (info.vmoHandle && info.ptr) {
@@ -3776,23 +3806,13 @@
return;
}
- if (info.coherentMemory && info.ptr) {
- if (info.coherentMemory->getDeviceMemory() != memory) {
- delete_goldfish_VkDeviceMemory(memory);
- }
+ auto coherentMemory = freeCoherentMemoryLocked(memory, info);
- if (info.ptr) {
- info.coherentMemory->release(info.ptr);
- info.ptr = nullptr;
- }
-
- auto coherentMemory = std::move(info.coherentMemory);
- // We have to release the lock before we could possibly free a
- // CoherentMemory, because that will call into VkEncoder, which
- // shouldn't be called when the lock is held.
- lock.unlock();
- coherentMemory = nullptr;
- }
+ // We have to release the lock before we could possibly free a
+ // CoherentMemory, because that will call into VkEncoder, which
+ // shouldn't be called when the lock is held.
+ lock.unlock();
+ coherentMemory = nullptr;
}
VkResult on_vkMapMemory(
@@ -5716,17 +5736,42 @@
unsigned char* writtenPtr = 0;
size_t written = 0;
- ((CommandBufferStagingStream*)cb->privateStream)->getWritten(&writtenPtr, &written);
+ CommandBufferStagingStream* cmdBufStream =
+ static_cast<CommandBufferStagingStream*>(cb->privateStream);
+ cmdBufStream->getWritten(&writtenPtr, &written);
// There's no pending commands here, skip. (case 2, stream created but no new recordings)
if (!written) continue;
// There are pending commands to flush.
VkEncoder* enc = (VkEncoder*)context;
- enc->vkQueueFlushCommandsGOOGLE(queue, cmdbuf, written, (const void*)writtenPtr, true /* do lock */);
+ VkDeviceMemory deviceMemory = cmdBufStream->getDeviceMemory();
+ VkDeviceSize dataOffset = 0;
+ if (mFeatureInfo->hasVulkanAuxCommandMemory) {
+ // for suballocations, deviceMemory is an alias VkDeviceMemory
+ // get underling VkDeviceMemory for given alias
+ deviceMemoryTransform_tohost(&deviceMemory, 1 /*memoryCount*/, &dataOffset,
+ 1 /*offsetCount*/, nullptr /*size*/, 0 /*sizeCount*/,
+ nullptr /*typeIndex*/, 0 /*typeIndexCount*/,
+ nullptr /*typeBits*/, 0 /*typeBitCounts*/);
+ // mark stream as flushing before flushing commands
+ cmdBufStream->markFlushing();
+ enc->vkQueueFlushCommandsFromAuxMemoryGOOGLE(queue, cmdbuf, deviceMemory,
+ dataOffset, written, true /*do lock*/);
+ } else {
+ enc->vkQueueFlushCommandsGOOGLE(queue, cmdbuf, written, (const void*)writtenPtr,
+ true /* do lock */);
+ }
// Reset this stream.
- ((CommandBufferStagingStream*)cb->privateStream)->reset();
+ // flushing happens on vkQueueSubmit
+ // vulkan api states that on queue submit,
+ // applications MUST not attempt to modify the command buffer in any way
+ // -as the device may be processing the commands recorded to it.
+ // It is safe to call reset() here for this reason.
+ // Command Buffer associated with this stream will only leave pending state
+ // after queue submit is complete and host has read the data
+ cmdBufStream->reset();
}
}
@@ -6603,6 +6648,69 @@
return 0;
}
+ CommandBufferStagingStream::Alloc getAlloc() {
+ if (mFeatureInfo->hasVulkanAuxCommandMemory) {
+ return [this](size_t size) -> CommandBufferStagingStream::Memory {
+ VkMemoryAllocateInfo info{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .pNext = nullptr,
+ .allocationSize = size,
+ .memoryTypeIndex = VK_MAX_MEMORY_TYPES // indicates auxiliary memory
+ };
+
+ auto enc = ResourceTracker::getThreadLocalEncoder();
+ VkDevice device = VK_NULL_HANDLE;
+ VkDeviceMemory vkDeviceMem = VK_NULL_HANDLE;
+ VkResult result = getCoherentMemory(&info, enc, device, &vkDeviceMem);
+ if (result != VK_SUCCESS) {
+ ALOGE("Failed to get coherent memory %u", result);
+ return {.deviceMemory = VK_NULL_HANDLE, .ptr = nullptr};
+ }
+
+ // getCoherentMemory() uses suballocations.
+ // To retrieve the suballocated memory address, look up
+ // VkDeviceMemory filled in by getCoherentMemory()
+ // scope of mLock
+ {
+ AutoLock<RecursiveLock> lock(mLock);
+ const auto it = info_VkDeviceMemory.find(vkDeviceMem);
+ if (it == info_VkDeviceMemory.end()) {
+ ALOGE("Coherent memory allocated %u not found", result);
+ return {.deviceMemory = VK_NULL_HANDLE, .ptr = nullptr};
+ };
+
+ const auto& info = it->second;
+ return {.deviceMemory = vkDeviceMem, .ptr = info.ptr};
+ }
+ };
+ }
+ return nullptr;
+ }
+
+ CommandBufferStagingStream::Free getFree() {
+ if (mFeatureInfo->hasVulkanAuxCommandMemory) {
+ return [this](const CommandBufferStagingStream::Memory& memory) {
+ // deviceMemory may not be the actual backing auxiliary VkDeviceMemory
+ // for suballocations, deviceMemory is a alias VkDeviceMemory hand;
+ // freeCoherentMemoryLocked maps the alias to the backing VkDeviceMemory
+ VkDeviceMemory deviceMemory = memory.deviceMemory;
+ AutoLock<RecursiveLock> lock(mLock);
+ auto it = info_VkDeviceMemory.find(deviceMemory);
+ if (it == info_VkDeviceMemory.end()) {
+ ALOGE("Device memory to free not found");
+ return;
+ }
+ auto coherentMemory = freeCoherentMemoryLocked(deviceMemory, it->second);
+ // We have to release the lock before we could possibly free a
+ // CoherentMemory, because that will call into VkEncoder, which
+ // shouldn't be called when the lock is held.
+ lock.unlock();
+ coherentMemory = nullptr;
+ };
+ }
+ return nullptr;
+ }
+
VkResult on_vkBeginCommandBuffer(
void* context, VkResult input_result,
VkCommandBuffer commandBuffer,
@@ -7103,7 +7211,8 @@
// Resets staging stream for this command buffer and primary command buffers
// where this command buffer has been recorded. If requested, also clears the pending
// descriptor sets.
- void resetCommandBufferStagingInfo(VkCommandBuffer commandBuffer, bool alsoResetPrimaries, bool alsoClearPendingDescriptorSets) {
+ void resetCommandBufferStagingInfo(VkCommandBuffer commandBuffer, bool alsoResetPrimaries,
+ bool alsoClearPendingDescriptorSets) {
struct goldfish_VkCommandBuffer* cb = as_goldfish_VkCommandBuffer(commandBuffer);
if (!cb) {
return;
@@ -7299,6 +7408,8 @@
struct goldfish_VkCommandBuffer* cb = as_goldfish_VkCommandBuffer(commandBuffer);
if (!cb->privateEncoder) {
+ sStaging.setAllocFree(ResourceTracker::get()->getAlloc(),
+ ResourceTracker::get()->getFree());
sStaging.popStaging((CommandBufferStagingStream**)&cb->privateStream, &cb->privateEncoder);
}
uint8_t* writtenPtr; size_t written;
@@ -8114,6 +8225,9 @@
return mImpl->syncEncodersForQueue(queue, current);
}
+CommandBufferStagingStream::Alloc ResourceTracker::getAlloc() { return mImpl->getAlloc(); }
+
+CommandBufferStagingStream::Free ResourceTracker::getFree() { return mImpl->getFree(); }
VkResult ResourceTracker::on_vkBeginCommandBuffer(
void* context, VkResult input_result,
diff --git a/system/vulkan_enc/ResourceTracker.h b/system/vulkan_enc/ResourceTracker.h
index 391eac5..2cb5b2a 100644
--- a/system/vulkan_enc/ResourceTracker.h
+++ b/system/vulkan_enc/ResourceTracker.h
@@ -14,15 +14,15 @@
// limitations under the License.
#pragma once
-#include "aemu/base/Tracing.h"
-
#include <vulkan/vulkan.h>
-#include "VulkanHandleMapping.h"
-#include "VulkanHandles.h"
#include <functional>
#include <memory>
+#include "CommandBufferStagingStream.h"
+#include "VulkanHandleMapping.h"
+#include "VulkanHandles.h"
+#include "aemu/base/Tracing.h"
#include "goldfish_vk_transform_guest.h"
struct EmulatorFeatureInfo;
@@ -528,6 +528,9 @@
uint32_t syncEncodersForCommandBuffer(VkCommandBuffer commandBuffer, VkEncoder* current);
uint32_t syncEncodersForQueue(VkQueue queue, VkEncoder* current);
+ CommandBufferStagingStream::Alloc getAlloc();
+ CommandBufferStagingStream::Free getFree();
+
VkResult on_vkBeginCommandBuffer(
void* context, VkResult input_result,
VkCommandBuffer commandBuffer,
diff --git a/system/vulkan_enc_unit_tests/Android.mk b/system/vulkan_enc_unit_tests/Android.mk
index 162c95d..e506bc9 100644
--- a/system/vulkan_enc_unit_tests/Android.mk
+++ b/system/vulkan_enc_unit_tests/Android.mk
@@ -6,7 +6,7 @@
LOCAL_C_INCLUDES += \
device/generic/goldfish-opengl/host/include/libOpenglRender \
-
+ external/gfxstream-protocols/include/vulkan/include/
LOCAL_SRC_FILES:= \
CommandBufferStagingStream_test.cpp \
diff --git a/system/vulkan_enc_unit_tests/CommandBufferStagingStream_test.cpp b/system/vulkan_enc_unit_tests/CommandBufferStagingStream_test.cpp
index eb34cfb..375bc6e 100644
--- a/system/vulkan_enc_unit_tests/CommandBufferStagingStream_test.cpp
+++ b/system/vulkan_enc_unit_tests/CommandBufferStagingStream_test.cpp
@@ -215,8 +215,9 @@
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
@@ -243,8 +244,8 @@
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
CommandBufferStagingStream::Memory memory{
- .deviceMemory = nullptr, // not needed for this test
- .ptr = nullptr, // to test alloc call failing
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = nullptr, // to test alloc call failing
};
MockAlloc allocFn;
@@ -271,8 +272,8 @@
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
// device memory for test purposes. The test just needs a pointer
- uint32_t deviceMem = 0;
- uint32_t* deviceMemPtr = &deviceMem;
+ uint64_t deviceMem = 0;
+ VkDeviceMemory deviceMemPtr = (VkDeviceMemory)(&deviceMem);
CommandBufferStagingStream::Memory memory{.deviceMemory = deviceMemPtr,
.ptr = memorySrc.data()};
@@ -327,11 +328,12 @@
// memory source after reallocation
std::vector<uint8_t> reallocatedMemorySrc(kTestBufferSize * 3);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
CommandBufferStagingStream::Memory reallocatedMemory{
- .deviceMemory = nullptr, // not needed for this test
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
.ptr = reallocatedMemorySrc.data()};
MockAlloc allocFn;
@@ -393,8 +395,9 @@
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
@@ -427,8 +430,9 @@
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
@@ -463,8 +467,9 @@
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
@@ -485,8 +490,9 @@
TEST(CommandBufferStagingStreamCustomAllocationTest, ReallocationBoundary) {
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
@@ -546,8 +552,9 @@
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
@@ -570,8 +577,9 @@
TEST(CommandBufferStagingStreamCustomAllocationTest, MetadataCheck) {
// memory source
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
// CommandBufferStagingStream allocates metadata when using custom allocators
static const size_t expectedMetadataSize = 8;
@@ -592,8 +600,9 @@
TEST(CommandBufferStagingStreamCustomAllocationTest, MarkFlushing) {
// memory source for allocation
std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
MockAlloc allocFn;
EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
@@ -622,8 +631,9 @@
// memory source for allocation
// allocate a big enough buffer to avoid resizes in test
std::vector<uint8_t> memorySrc(kTestBufferSize * 3);
- CommandBufferStagingStream::Memory memory{.deviceMemory = nullptr, // not needed for this test
- .ptr = memorySrc.data()};
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
std::condition_variable memoryFlushedCondition;
std::mutex mutex;
@@ -688,4 +698,27 @@
// wait for read thread to finish
consumer.join();
+}
+
+// this test verifies that allocBuffer() cannot be called on a stream
+// that is currently being read by the host
+TEST(CommandBufferStagingStreamCustomAllocationTest, AllocBufferFailsIfReadPending) {
+ // memory source for allocation
+ std::vector<uint8_t> memorySrc(kTestBufferSize * 2);
+ CommandBufferStagingStream::Memory memory{
+ .deviceMemory = VK_NULL_HANDLE, // not needed for this test
+ .ptr = memorySrc.data()};
+ MockAlloc allocFn;
+
+ EXPECT_CALL(allocFn, Call(Ge(kTestBufferSize))).Times(1).WillRepeatedly(Return(memory));
+
+ auto allocStdFn = allocFn.AsStdFunction();
+ CommandBufferStagingStream stream(allocStdFn, EmptyFree);
+ (void)stream.allocBuffer(kTestBufferSize);
+
+ // will set metadata of the stream buffer to pending read
+ stream.markFlushing();
+
+ EXPECT_DEATH({ (void)stream.allocBuffer(kTestBufferSize); }, "")
+ << "allocBuffer() should not be called while previous data is being flushed";
}
\ No newline at end of file