diff options
author | Yilong Li <liyl@google.com> | 2021-04-27 11:48:26 -0700 |
---|---|---|
committer | Yilong Li <liyl@google.com> | 2021-04-28 18:11:25 +0000 |
commit | 53d06dabf94e9b8226b3d402b8cc56dfe5702ee5 (patch) | |
tree | abbd0acda372ee6ff8e44126a152a49242de65b1 | |
parent | 5dc831b5e8ca40019a45b0b61d6e13b732396243 (diff) | |
download | goldfish-opengl-53d06dabf94e9b8226b3d402b8cc56dfe5702ee5.tar.gz |
sync: use fence pool to retrieve temporary VkFences
In the previous change, every time clients call vkQueueSubmit() with
external (zircon/syncfd) semaphores to trigger, we create a temporary
VkFence as the |fence| argument, let vkQueueSubmit() signal that
temporary fence and then do async wait on that fence. In this way, a
new VkFence is created and destroyed every queue submit as well as
goldfish-sync timelines, which caused some extra overhead.
This change introduces a thread-safe SyncFencePool from which clients
can acquire a pair of unused VkFence and sync timeline FIDL client,
and return them once they finish using them.
TEST=FEMU workstation.x64
Change-Id: I281637947360f8c7197d2af132e4d691588628aa
-rw-r--r-- | BUILD.gn | 3 | ||||
-rw-r--r-- | android-emu/android/base/synchronization/AndroidObjectPool.h | 132 | ||||
-rw-r--r-- | system/vulkan_enc/ResourceTracker.cpp | 68 | ||||
-rw-r--r-- | system/vulkan_enc/SyncFencePool.cpp | 95 | ||||
-rw-r--r-- | system/vulkan_enc/SyncFencePool.h | 67 |
5 files changed, 325 insertions, 40 deletions
@@ -19,6 +19,7 @@ shared_library("libvulkan_goldfish") { "android-emu/android/base/synchronization/AndroidLock.h", "android-emu/android/base/synchronization/AndroidMessageChannel.cpp", "android-emu/android/base/synchronization/AndroidMessageChannel.h", + "android-emu/android/base/synchronization/AndroidObjectPool.h", "android-emu/android/base/threads/AndroidFunctorThread.cpp", "android-emu/android/base/threads/AndroidFunctorThread.h", "android-emu/android/base/threads/AndroidThread.h", @@ -63,6 +64,8 @@ shared_library("libvulkan_goldfish") { "system/vulkan_enc/ResourceTracker.h", "system/vulkan_enc/Resources.cpp", "system/vulkan_enc/Resources.h", + "system/vulkan_enc/SyncFencePool.cpp", + "system/vulkan_enc/SyncFencePool.h", "system/vulkan_enc/Validation.cpp", "system/vulkan_enc/Validation.h", "system/vulkan_enc/VkEncoder.cpp", diff --git a/android-emu/android/base/synchronization/AndroidObjectPool.h b/android-emu/android/base/synchronization/AndroidObjectPool.h new file mode 100644 index 00000000..45eb499b --- /dev/null +++ b/android-emu/android/base/synchronization/AndroidObjectPool.h @@ -0,0 +1,132 @@ +// Copyright 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "android/base/synchronization/AndroidConditionVariable.h" +#include "android/base/synchronization/AndroidLock.h" + +#include <list> +#include <queue> + +namespace android { +namespace base { +namespace guest { + +template <class T> +class ObjectPool { +public: + explicit ObjectPool(size_t sizeLimit, + std::function<T()> createObject, + std::function<void(T&)> onDestroy = {[] {}}, + std::function<void(T&)> onRelease = {[] {}}) + : mCreateObject(std::move(createObject)), + mOnDestroy(std::move(onDestroy)), + mOnRelease(std::move(onRelease)), + mSizeLimit(sizeLimit) {} + + virtual ~ObjectPool() { + while (!mObjects.empty()) { + mOnDestroy(mObjects.back()); + mObjects.pop_back(); + } + }; + + void setCreateObjectFunc(std::function<T()> createObject) { + mCreateObject = std::move(createObject); + } + + void setObjectOnDestroyCallback(std::function<void(T&)> onDestroy) { + mOnDestroy = std::move(onDestroy); + } + + void setObjectOnReleaseCallback(std::function<void(T&)> onRelease) { + mOnRelease = std::move(onRelease); + } + + T* acquire() { + AutoLock lock(mLock); + T* obj = nullptr; + if (!mAvailableObjects.empty()) { + obj = mAvailableObjects.front(); + mAvailableObjects.pop(); + return obj; + } + + if (mSizeLimit == 0 || mObjects.size() < mSizeLimit) { + mObjects.emplace_back(mCreateObject()); + return &mObjects.back(); + } + + mCv.wait(&lock, [this, &obj] { + if (!mAvailableObjects.empty()) { + obj = mAvailableObjects.front(); + mAvailableObjects.pop(); + return true; + } + return false; + }); + return obj; + } + + void release(T* obj) { + mOnRelease(*obj); + + AutoLock lock(mLock); + mAvailableObjects.push(obj); + mCv.signalAndUnlock(&lock); + } + +private: + // Called when ObjectPool creates a new Object. + std::function<T()> mCreateObject; + + // Called when an Object is destroyed. + // This function will be called in the base class destructor, thus any + // method / member variables from derived class should NOT be used in + // this function. + std::function<void(T& obj)> mOnDestroy; + + // Called when an Object is released back to the pool. + std::function<void(T& obj)> mOnRelease; + + android::base::guest::StaticLock mLock; + android::base::guest::StaticLock mCvLock; + android::base::guest::ConditionVariable mCv; + std::queue<T*> mAvailableObjects; + std::list<T> mObjects; + size_t mTotalObjectsCreated = 0u; + + size_t mSizeLimit = 1u; +}; + +template <class T> +class DefaultObjectPool : public ObjectPool<T> { +public: + explicit DefaultObjectPool(size_t sizeLimit) + : ObjectPool<T>( + sizeLimit, + /* createObject */ [] { return T{}; }, + /* onDestroy */ [] {}, + /* onRelease */ [] {}) {} + ~DefaultObjectPool() = default; + +private: + T createObject() { return T{}; } + void destroyObject(T&) {} +}; + +} // namespace guest +} // namespace base +} // namespace android diff --git a/system/vulkan_enc/ResourceTracker.cpp b/system/vulkan_enc/ResourceTracker.cpp index 1dfc5f1e..3b6efe85 100644 --- a/system/vulkan_enc/ResourceTracker.cpp +++ b/system/vulkan_enc/ResourceTracker.cpp @@ -125,6 +125,7 @@ VkResult getMemoryAndroidHardwareBufferANDROID(struct AHardwareBuffer **) { retu #include "HostVisibleMemoryVirtualization.h" #include "Resources.h" +#include "SyncFencePool.h" #include "VkEncoder.h" #include "android/base/AlignedBuf.h" @@ -315,6 +316,7 @@ public: std::vector<HostMemBlocks> hostMemBlocks { VK_MAX_MEMORY_TYPES }; uint32_t apiVersion; std::set<std::string> enabledExtensions; + std::unique_ptr<SyncFencePool> fencePool; }; struct VirtioGpuHostmemResourceInfo { @@ -345,7 +347,6 @@ public: uint64_t id; VkFence fence = VK_NULL_HANDLE; - VkFence tempFence = VK_NULL_HANDLE; #ifdef VK_USE_PLATFORM_FUCHSIA std::vector<zx::event> eventsToWait; @@ -353,7 +354,6 @@ public: zx::eventpair syncEvent; std::unique_ptr<async::WaitOnce> wait; - fidl::WireSyncClient<fuchsia_hardware_goldfish::SyncTimeline> timelineClient; #endif // VK_USE_PLATFORM_FUCHSIA }; @@ -491,7 +491,6 @@ public: auto it = info_VkDevice.find(device); if (it == info_VkDevice.end()) return; - auto info = it->second; info_VkDevice.erase(device); lock.unlock(); } @@ -845,6 +844,9 @@ public: mFeatureInfo.get(), &mHostVisibleMemoryVirtInfo); info.apiVersion = props.apiVersion; +#ifdef VK_USE_PLATFORM_FUCHSIA + info.fencePool = std::make_unique<SyncFencePool>(device, mSyncDevice.get()); +#endif // VK_USE_PLATFORM_FUCHSIA if (!ppEnabledExtensionNames) return; @@ -1697,7 +1699,7 @@ public: auto it = info_VkDevice.find(device); if (it == info_VkDevice.end()) return; - auto info = it->second; + auto hostMemBlocks = it->second.hostMemBlocks; lock.unlock(); @@ -1706,7 +1708,7 @@ public: bool freeMemorySyncSupported = mFeatureInfo->hasVulkanFreeMemorySync; for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; ++i) { - for (auto& block : info.hostMemBlocks[i]) { + for (auto& block : hostMemBlocks[i]) { destroyHostMemAlloc( freeMemorySyncSupported, enc, device, &block); @@ -5722,9 +5724,13 @@ public: } lock.unlock(); - // Create a temporary VkFence. + // Acquire a VkFence and timeline from syncFencePool. VkFence tempFence = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE; + + SyncFencePool* syncFencePool = nullptr; + SyncFence* syncFence = nullptr; + if (!postWaitEvents.empty()) { lock.lock(); const auto& queueInfo = info_VkQueue.find(queue); @@ -5732,19 +5738,15 @@ public: return VK_ERROR_OUT_OF_HOST_MEMORY; } device = queueInfo->second.device; + const auto& deviceInfo = info_VkDevice.find(device); + if (deviceInfo == info_VkDevice.end() || deviceInfo->second.fencePool == nullptr) { + return VK_ERROR_OUT_OF_HOST_MEMORY; + } + syncFencePool = deviceInfo->second.fencePool.get(); lock.unlock(); - VkFenceCreateInfo fenceCreateInfo = { - .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - }; - - auto result = enc->vkCreateFence(device, &fenceCreateInfo, nullptr, &tempFence, - true /* do lock */); - if (result != VK_SUCCESS) { - return result; - } + syncFence = syncFencePool->acquire(); + tempFence = syncFence->fence; } if (!preSignalSemaphores.empty()) { @@ -5790,20 +5792,6 @@ public: } if (!postWaitEvents.empty()) { - auto timelineEnds = fidl::CreateEndpoints<fuchsia_hardware_goldfish::SyncTimeline>(); - if (!timelineEnds.is_ok()) { - ALOGE("Cannot create sync timeline channels, error: %s", - timelineEnds.status_string()); - return VK_ERROR_DEVICE_LOST; - } - - auto createTimelineResult = - mSyncDevice->CreateTimeline(std::move(timelineEnds->server)); - if (!createTimelineResult.ok()) { - ALOGE("CreateTimeline failed, error: %s", createTimelineResult.status_string()); - return VK_ERROR_DEVICE_LOST; - } - zx::eventpair syncEventClient, syncEventServer; auto status = zx::eventpair::create(0u, &syncEventClient, &syncEventServer); if (status != ZX_OK) { @@ -5818,10 +5806,7 @@ public: queueSubmitState = { .id = queueInfo.vkQueueSubmitId, .fence = fence, - .tempFence = tempFence, .syncEvent = std::move(syncEventClient), - .timelineClient = - fidl::WireSyncClient(fidl::BindSyncClient(std::move(timelineEnds->client))), }; queueSubmitState.wait = std::make_unique<async::WaitOnce>( @@ -5829,7 +5814,7 @@ public: queueSubmitState.wait->Begin( mLoop->dispatcher(), [this, queue, id = queueSubmitState.id, postWaitEvents /* copy of zx handles */, - fence, device, tempFence](async_dispatcher_t* dispatcher, async::WaitOnce* wait, + fence, device, syncFence](async_dispatcher_t* dispatcher, async::WaitOnce* wait, zx_status_t status, const zx_packet_signal_t* signal) { #ifndef FUCHSIA_NO_TRACE AEMU_SCOPED_TRACE("sync event handler"); @@ -5860,13 +5845,16 @@ public: break; } - vkEncoder->vkDestroyFence(device, tempFence, nullptr, true /* do lock */); - - AutoLock lock(mLock); - info_VkQueue[queue].vkQueueSubmitStateMap.erase(id); + SyncFencePool* fencePool = nullptr; + { + AutoLock lock(mLock); + info_VkQueue[queue].vkQueueSubmitStateMap.erase(id); + fencePool = info_VkDevice[device].fencePool.get(); + } + fencePool->release(syncFence); }); - auto triggerResult = queueSubmitState.timelineClient.TriggerHostWait( + auto triggerResult = syncFence->timelineClient.TriggerHostWait( get_host_u64_VkFence(tempFence), 1u, std::move(syncEventServer)); if (!triggerResult.ok()) { ALOGE("TriggerHostWait failed, error: %s", triggerResult.status_string()); diff --git a/system/vulkan_enc/SyncFencePool.cpp b/system/vulkan_enc/SyncFencePool.cpp new file mode 100644 index 00000000..03238828 --- /dev/null +++ b/system/vulkan_enc/SyncFencePool.cpp @@ -0,0 +1,95 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "SyncFencePool.h" +#include "VkEncoder.h" + +#include <log/log.h> +#include <vulkan/vulkan.h> + +namespace goldfish_vk { + +namespace { + +static VkFence newFence(VkDevice device) { + auto hostConn = ResourceTracker::threadingCallbacks.hostConnectionGetFunc(); + auto vkEncoder = ResourceTracker::threadingCallbacks.vkEncoderGetFunc(hostConn); + VkFenceCreateInfo createInfo = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .pNext = nullptr, + .flags = 0u, + }; + + VkFence fence = VK_NULL_HANDLE; + VkResult result = vkEncoder->vkCreateFence(device, &createInfo, 0u, &fence, true /* do lock */); + if (result != VK_SUCCESS) { + ALOGE("%s: vkCreateFence failed: result: %d", result); + return VK_NULL_HANDLE; + } + return fence; +} + +static void destroyFence(VkDevice device, VkFence fence) { + auto hostConn = ResourceTracker::threadingCallbacks.hostConnectionGetFunc(); + auto vkEncoder = ResourceTracker::threadingCallbacks.vkEncoderGetFunc(hostConn); + + vkEncoder->vkDestroyFence(device, fence, nullptr, true /* do lock */); +} + +static void resetFence(VkDevice device, VkFence fence) { + auto hostConn = ResourceTracker::threadingCallbacks.hostConnectionGetFunc(); + auto vkEncoder = ResourceTracker::threadingCallbacks.vkEncoderGetFunc(hostConn); + + vkEncoder->vkResetFences(device, 1, &fence, true /* do lock */); +} + +static SyncTimelineClient newTimelineClient(SyncDeviceClient* syncDevice) { +#ifdef VK_USE_PLATFORM_FUCHSIA + auto timelineEnds = fidl::CreateEndpoints<fuchsia_hardware_goldfish::SyncTimeline>(); + if (!timelineEnds.is_ok()) { + ALOGE("Cannot create sync timeline channels, error: %s", timelineEnds.status_string()); + return {}; + } + + auto createTimelineResult = syncDevice->CreateTimeline(std::move(timelineEnds->server)); + if (!createTimelineResult.ok()) { + ALOGE("CreateTimeline failed, error: %s", createTimelineResult.status_string()); + return {}; + } + + return fidl::BindSyncClient(std::move(timelineEnds->client)); +#else // !VK_USE_PLATFORM_FUCHSIA + (void)syncDevice; + return 0; +#endif // VK_USE_PLATFORM_FUCHSIA +} + +} // namespace + +SyncFencePool::SyncFencePool(VkDevice device, SyncDeviceClient* syncDevice) + : ObjectPool<SyncFence>( + kPoolSizeLimit, + // createObject + [device, syncDevice] { + return SyncFence{ + .fence = newFence(device), + .timelineClient = newTimelineClient(syncDevice), + }; + }, + // onDestroy + [device = device](SyncFence& fence) { destroyFence(device, fence.fence); }, + // onRelease + [device = device](SyncFence& fence) { resetFence(device, fence.fence); }) {} + +} // namespace goldfish_vk diff --git a/system/vulkan_enc/SyncFencePool.h b/system/vulkan_enc/SyncFencePool.h new file mode 100644 index 00000000..4ea5b853 --- /dev/null +++ b/system/vulkan_enc/SyncFencePool.h @@ -0,0 +1,67 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef VK_USE_PLATFORM_FUCHSIA +#include <fuchsia/hardware/goldfish/llcpp/fidl.h> +#endif // VK_USE_PLATFORM_FUCHSIA + +#include "ResourceTracker.h" +#include "VkEncoder.h" + +#include "android/base/synchronization/AndroidObjectPool.h" + +#include <log/log.h> +#include <vulkan/vulkan.h> + +namespace goldfish_vk { + +#ifdef VK_USE_PLATFORM_FUCHSIA +using SyncDeviceClient = fidl::WireSyncClient<fuchsia_hardware_goldfish::SyncDevice>; +using SyncTimelineClient = fidl::WireSyncClient<fuchsia_hardware_goldfish::SyncTimeline>; + +#else // !VK_USE_PLATFORM_FUCHSIA +using SyncDeviceClient = int; +using SyncTimelineClient = int; + +#endif // VK_USE_PLATFORM_FUCHSIA + +// A SyncFence is a pair of a normal VkFence dedicated for host / guest sync +// purposes and a goldfish sync timeline associated with that fence. +// +// When guest calls vkQueueSubmit() and it needs to wait for the submitted +// commands to finish, it acquires a SyncFence, calls vkQueueSubmit() with the +// |fence| variable from |SyncFence|, and triggers a VkFence wait using the +// |timelineClient| member variable. +struct SyncFence { + VkFence fence; +#ifdef VK_USE_PLATFORM_FUCHSIA + SyncTimelineClient timelineClient; +#endif // VK_USE_PLATFORM_FUCHSIA +}; + +// A SyncFencePool stores multiple SyncFence objects and allows for reuse. +// Every time when clients need a SyncFence, it calls |acquire()| from +// SyncFencePool to get an fence, and returns it by calling |release()| after +// it finishes using the fence. +class SyncFencePool : public android::base::guest::ObjectPool<SyncFence> { +public: + SyncFencePool(VkDevice device, SyncDeviceClient* syncDevice); + ~SyncFencePool() = default; + +private: + // unlimited + constexpr static size_t kPoolSizeLimit = 0u; +}; + +} // namespace goldfish_vk |