Compare commits
10 Commits
pf/vk-time
...
MapAsyncEx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f14ebba87 | ||
|
|
aa4e2c56b5 | ||
|
|
aa4f1910b8 | ||
|
|
223a4b18a8 | ||
|
|
57ef534acd | ||
|
|
719914fb84 | ||
|
|
080f958da3 | ||
|
|
7547aa3807 | ||
|
|
67a0c6e0e1 | ||
|
|
37cb842993 |
@@ -8,3 +8,5 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: add `View::getLastDynamicResolutionScale()` (b/457753622)
|
||||
|
||||
- materials: Make Material Instances' UBO descriptor use dynamic offsets. [⚠️ **Recompile Materials**]
|
||||
|
||||
@@ -22,6 +22,7 @@ object Utils {
|
||||
/**
|
||||
* Initializes the utils JNI layer. Must be called before using any utils functionality.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun init() {
|
||||
// Load Filament first to ensure that the NioUtils Java class is available in the JNIEnv.
|
||||
Filament.init()
|
||||
|
||||
@@ -17,12 +17,20 @@ filamentTools {
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyMesh', Copy) {
|
||||
tasks.register('copyDamagedHelmetGltf', Copy) {
|
||||
from file("../../../third_party/models/DamagedHelmet/DamagedHelmet.glb")
|
||||
into file("src/main/assets/models")
|
||||
rename {String fileName -> "helmet.glb"}
|
||||
}
|
||||
|
||||
// don't forget to update MainACtivity.kt when/if changing this.
|
||||
tasks.register('copyBusterGltf', Copy) {
|
||||
from "../../../third_party/models/BusterDrone"
|
||||
into "src/main/assets/models"
|
||||
}
|
||||
|
||||
preBuild.dependsOn copyMesh
|
||||
preBuild.dependsOn copyDamagedHelmetGltf
|
||||
preBuild.dependsOn copyBusterGltf
|
||||
|
||||
clean.doFirst {
|
||||
delete "src/main/assets"
|
||||
|
||||
Binary file not shown.
@@ -216,6 +216,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanPipelineCache.h
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/VulkanQueryManager.cpp
|
||||
src/vulkan/VulkanQueryManager.h
|
||||
src/vulkan/VulkanReadPixels.cpp
|
||||
src/vulkan/VulkanReadPixels.h
|
||||
src/vulkan/VulkanSamplerCache.cpp
|
||||
|
||||
@@ -48,7 +48,7 @@ struct VulkanPlatformPrivate;
|
||||
|
||||
// Forward declare the fence status that will be maintained by the command
|
||||
// buffer manager.
|
||||
struct VulkanCmdBufferState;
|
||||
struct VulkanCmdFence;
|
||||
|
||||
/**
|
||||
* A Platform interface that creates a Vulkan backend.
|
||||
@@ -242,7 +242,7 @@ public:
|
||||
* @return A Platform::Sync object tracking the provided fence.
|
||||
*/
|
||||
virtual Platform::Sync* createSync(VkFence fence,
|
||||
std::shared_ptr<VulkanCmdBufferState> fenceStatus) noexcept;
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept;
|
||||
|
||||
/**
|
||||
* Destroys a sync. If called with a sync not created by this platform
|
||||
@@ -434,7 +434,7 @@ public:
|
||||
protected:
|
||||
struct VulkanSync : public Platform::Sync {
|
||||
VkFence fence;
|
||||
std::shared_ptr<VulkanCmdBufferState> fenceStatus;
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
||||
};
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
@@ -444,6 +444,11 @@ protected:
|
||||
|
||||
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
|
||||
|
||||
/**
|
||||
* Query if transient attachments are supported by the backend.
|
||||
*/
|
||||
bool isTransientAttachmentSupported() const noexcept;
|
||||
|
||||
private:
|
||||
friend struct VulkanPlatformPrivate;
|
||||
};
|
||||
|
||||
@@ -29,8 +29,9 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
FenceStatus VulkanCmdBufferState::waitOnFence(VkDevice device, uint64_t const timeout,
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
FenceStatus VulkanCmdFence::wait(VkDevice device, uint64_t const timeout,
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
|
||||
// this lock MUST be held for READ when calling vkWaitForFences()
|
||||
std::shared_lock rl(mLock);
|
||||
|
||||
@@ -80,7 +81,7 @@ FenceStatus VulkanCmdBufferState::waitOnFence(VkDevice device, uint64_t const ti
|
||||
return FenceStatus::ERROR; // not supported
|
||||
}
|
||||
|
||||
void VulkanCmdBufferState::resetFence(VkDevice device) {
|
||||
void VulkanCmdFence::resetFence(VkDevice device) {
|
||||
// This lock prevents vkResetFences() from being called simultaneously with vkWaitForFences(),
|
||||
// but by construction, when we're here, we know that the fence has signaled and
|
||||
// vkWaitForFences() will return shortly.
|
||||
@@ -89,35 +90,4 @@ void VulkanCmdBufferState::resetFence(VkDevice device) {
|
||||
vkResetFences(device, 1, &mFence);
|
||||
}
|
||||
|
||||
uint64_t VulkanTimerQuery::getResult() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mDurationLock);
|
||||
if (mDuration != UNKNOWN_QUERY_RESULT) {
|
||||
return mDuration;
|
||||
}
|
||||
}
|
||||
|
||||
VulkanCmdBufferState* startState = nullptr;
|
||||
VulkanCmdBufferState* endState = nullptr;
|
||||
{
|
||||
std::shared_lock const l(mMutex);
|
||||
startState = mBeginState.get();
|
||||
endState = mEndState.get();
|
||||
}
|
||||
// Here we sum up the time taken by each command buffer that were marked by this timer query.
|
||||
uint64_t duration = 0;
|
||||
do {
|
||||
uint64_t const stDuration = startState->getBufferDuration();
|
||||
assert_invariant(stDuration != VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION);
|
||||
duration += stDuration;
|
||||
startState = startState->getNextState().get();
|
||||
} while (startState != endState);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mDurationLock);
|
||||
mDuration = duration;
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
@@ -36,36 +35,25 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of Vulkan fences
|
||||
// and timer query results.
|
||||
struct VulkanCmdBufferState {
|
||||
static uint64_t const UNKNOWN_BUFFER_DURATION = std::numeric_limits<uint64_t>::max();
|
||||
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
|
||||
struct VulkanCmdFence {
|
||||
explicit VulkanCmdFence(VkFence fence) : mFence(fence) { }
|
||||
~VulkanCmdFence() = default;
|
||||
|
||||
explicit VulkanCmdBufferState(VkFence fence)
|
||||
: mFence(fence) {
|
||||
}
|
||||
~VulkanCmdBufferState() = default;
|
||||
|
||||
void setStatus(VkResult const value, uint64_t duration) {
|
||||
void setStatus(VkResult const value) {
|
||||
std::lock_guard const l(mLock);
|
||||
mStatus = value;
|
||||
mDuration = duration;
|
||||
mCond.notify_all();
|
||||
}
|
||||
|
||||
uint64_t getBufferDuration() {
|
||||
std::shared_lock const l(mLock);
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
VkResult getFenceStatus() {
|
||||
VkResult getStatus() {
|
||||
std::shared_lock const l(mLock);
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
void resetFence(VkDevice device);
|
||||
|
||||
FenceStatus waitOnFence(VkDevice device, uint64_t timeout,
|
||||
FenceStatus wait(VkDevice device, uint64_t timeout,
|
||||
std::chrono::steady_clock::time_point until);
|
||||
|
||||
void cancel() {
|
||||
@@ -74,18 +62,6 @@ struct VulkanCmdBufferState {
|
||||
mCond.notify_all();
|
||||
}
|
||||
|
||||
void setNextState(std::shared_ptr<VulkanCmdBufferState> next) {
|
||||
mNextState = std::move(next);
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState>& getNextState() {
|
||||
return mNextState;
|
||||
}
|
||||
|
||||
VkFence getVkFence() const {
|
||||
return mFence;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_mutex mLock; // NOLINT(*-include-cleaner)
|
||||
std::condition_variable_any mCond;
|
||||
@@ -94,42 +70,38 @@ private:
|
||||
// gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
|
||||
// finishes executing the command buffer, the status changes to VK_SUCCESS.
|
||||
VkResult mStatus{ VK_INCOMPLETE };
|
||||
VkFence const mFence;
|
||||
|
||||
uint64_t mDuration = UNKNOWN_BUFFER_DURATION;
|
||||
|
||||
// We assume that command buffers are serialized. This will point to the state of the next
|
||||
// buffer. This is necessary so that we can return total duration of multiple command buffers
|
||||
// when a TimerQuery is requested by the front-end.
|
||||
std::shared_ptr<VulkanCmdBufferState> mNextState;
|
||||
VkFence mFence;
|
||||
};
|
||||
|
||||
struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
|
||||
VulkanFence() {}
|
||||
|
||||
void setCmdBufferState(std::shared_ptr<VulkanCmdBufferState> state) {
|
||||
std::lock_guard l(lock);
|
||||
cmdbufState = std::move(state);
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) {
|
||||
std::lock_guard const l(lock);
|
||||
sharedFence = std::move(fence);
|
||||
cond.notify_all();
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState>& getCmdBufferState() {
|
||||
std::lock_guard l(lock);
|
||||
return cmdbufState;
|
||||
std::shared_ptr<VulkanCmdFence>& getSharedFence() {
|
||||
std::lock_guard const l(lock);
|
||||
return sharedFence;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<VulkanCmdBufferState>, bool> wait(
|
||||
std::chrono::steady_clock::time_point const until) {
|
||||
std::pair<std::shared_ptr<VulkanCmdFence>, bool>
|
||||
wait(std::chrono::steady_clock::time_point const until) {
|
||||
// hold a reference so that our state doesn't disappear while we wait
|
||||
std::unique_lock l(lock);
|
||||
cond.wait_until(l, until, [&] { return bool(cmdbufState) || canceled; });
|
||||
// here cmdbufState will be null if we timed out
|
||||
return { cmdbufState, canceled };
|
||||
cond.wait_until(l, until, [this] {
|
||||
return bool(sharedFence) || canceled;
|
||||
});
|
||||
// here mSharedFence will be null if we timed out
|
||||
return { sharedFence, canceled };
|
||||
}
|
||||
|
||||
void cancel() const {
|
||||
std::lock_guard const l(lock);
|
||||
if (cmdbufState) {
|
||||
cmdbufState->cancel();
|
||||
if (sharedFence) {
|
||||
sharedFence->cancel();
|
||||
}
|
||||
canceled = true;
|
||||
cond.notify_all();
|
||||
@@ -139,10 +111,10 @@ private:
|
||||
mutable std::mutex lock;
|
||||
mutable std::condition_variable cond;
|
||||
mutable bool canceled = false;
|
||||
std::shared_ptr<VulkanCmdBufferState> cmdbufState;
|
||||
std::shared_ptr<VulkanCmdFence> sharedFence;
|
||||
};
|
||||
|
||||
struct VulkanSync : public HwSync, fvkmemory::ThreadSafeResource {
|
||||
struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
|
||||
struct CallbackData {
|
||||
CallbackHandler* handler;
|
||||
Platform::SyncCallback cb;
|
||||
@@ -156,50 +128,38 @@ struct VulkanSync : public HwSync, fvkmemory::ThreadSafeResource {
|
||||
};
|
||||
|
||||
struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
|
||||
VulkanTimerQuery(uint32_t startingIndex, uint32_t stoppingIndex)
|
||||
: mStartingQueryIndex(startingIndex),
|
||||
mStoppingQueryIndex(stoppingIndex) {}
|
||||
|
||||
static decltype(VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION) const
|
||||
UNKNOWN_QUERY_RESULT = VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION;
|
||||
|
||||
VulkanTimerQuery() {}
|
||||
|
||||
void setBeginState(std::shared_ptr<VulkanCmdBufferState> state) {
|
||||
{
|
||||
std::lock_guard const lock(mMutex);
|
||||
mBeginState = std::move(state);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mDurationLock);
|
||||
mDuration = UNKNOWN_QUERY_RESULT;
|
||||
}
|
||||
|
||||
void setFence(std::shared_ptr<VulkanCmdFence> fence) noexcept {
|
||||
std::lock_guard const lock(mFenceMutex);
|
||||
mFence = std::move(fence);
|
||||
}
|
||||
|
||||
void setEndState(std::shared_ptr<VulkanCmdBufferState> state) {
|
||||
{
|
||||
std::lock_guard const lock(mMutex);
|
||||
mEndState = std::move(state);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mDurationLock);
|
||||
mDuration = UNKNOWN_QUERY_RESULT;
|
||||
}
|
||||
bool isCompleted() noexcept {
|
||||
std::lock_guard const lock(mFenceMutex);
|
||||
// QueryValue is a synchronous call and might occur before beginTimerQuery has written
|
||||
// anything into the command buffer, which is an error according to the validation layer
|
||||
// that ships in the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to
|
||||
// require that the timestamp has at least been written into a processed command buffer.
|
||||
|
||||
// This fence indicates that the corresponding buffer has been completed.
|
||||
return mFence && mFence->getStatus() == VK_SUCCESS;
|
||||
}
|
||||
|
||||
bool isComplete() {
|
||||
std::shared_lock const lock(mMutex);
|
||||
return mBeginState && mEndState &&
|
||||
mBeginState->getBufferDuration() != UNKNOWN_QUERY_RESULT &&
|
||||
mEndState->getBufferDuration() != UNKNOWN_QUERY_RESULT;
|
||||
}
|
||||
uint32_t getStartingQueryIndex() const { return mStartingQueryIndex; }
|
||||
|
||||
uint64_t getResult();
|
||||
uint32_t getStoppingQueryIndex() const {
|
||||
return mStoppingQueryIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_mutex mMutex; // NOLINT(*-include-cleaner)
|
||||
std::mutex mDurationLock;
|
||||
std::shared_ptr<VulkanCmdBufferState> mBeginState;
|
||||
std::shared_ptr<VulkanCmdBufferState> mEndState;
|
||||
uint64_t mDuration = UNKNOWN_QUERY_RESULT;
|
||||
uint32_t mStartingQueryIndex;
|
||||
uint32_t mStoppingQueryIndex;
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> mFence;
|
||||
utils::Mutex mFenceMutex;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -55,22 +55,6 @@ VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
|
||||
return cmdbuffer;
|
||||
}
|
||||
|
||||
VkFence createFence(VkDevice device, VulkanContext const& context, VkCommandPool pool) {
|
||||
VkFenceCreateInfo fenceCreateInfo{ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
|
||||
VkExportFenceCreateInfo exportFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
|
||||
};
|
||||
|
||||
// Necessary to guard this. Otherwise, swiftshader would throw an error.
|
||||
if (context.getFenceExportFlags()) {
|
||||
exportFenceCreateInfo.handleTypes = context.getFenceExportFlags(),
|
||||
fenceCreateInfo.pNext = &exportFenceCreateInfo;
|
||||
}
|
||||
VkFence fence;
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
|
||||
return fence;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
@@ -107,21 +91,30 @@ uint32_t VulkanCommandBuffer::sAgeCounter = 0;
|
||||
|
||||
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice device,
|
||||
VkQueue queue, VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager,
|
||||
bool isProtected, uint32_t timerQueryIndex, VkQueryPool queryPool)
|
||||
: mContext(context),
|
||||
mMarkerCount(0),
|
||||
isProtected(isProtected),
|
||||
mDevice(device),
|
||||
mQueue(queue),
|
||||
mSemaphoreManager(semaphoreManager),
|
||||
mBuffer(createCommandBuffer(device, pool)),
|
||||
mSubmission(semaphoreManager->acquire()),
|
||||
mFence(createFence(device, context, pool)),
|
||||
mCmdbufState(std::make_shared<VulkanCmdBufferState>(mFence)),
|
||||
mAge(++sAgeCounter),
|
||||
mQueryBeginIndex(timerQueryIndex),
|
||||
mQueryEndIndex(timerQueryIndex + 1),
|
||||
mQueryPool(queryPool) {}
|
||||
bool isProtected)
|
||||
: mContext(context),
|
||||
mMarkerCount(0),
|
||||
isProtected(isProtected),
|
||||
mDevice(device),
|
||||
mQueue(queue),
|
||||
mSemaphoreManager(semaphoreManager),
|
||||
mBuffer(createCommandBuffer(device, pool)),
|
||||
mSubmission(semaphoreManager->acquire()),
|
||||
mAge(++sAgeCounter) {
|
||||
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||||
VkExportFenceCreateInfo exportFenceCreateInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
|
||||
.handleTypes = context.getFenceExportFlags()
|
||||
};
|
||||
|
||||
// Necessary to guard this. Otherwise, swiftshader would throw an error.
|
||||
if (context.getFenceExportFlags()) {
|
||||
fenceCreateInfo.pNext = &exportFenceCreateInfo;
|
||||
}
|
||||
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
|
||||
|
||||
mFenceStatus = std::make_shared<VulkanCmdFence>(mFence);
|
||||
}
|
||||
|
||||
VulkanCommandBuffer::~VulkanCommandBuffer() {
|
||||
vkDestroyFence(mDevice, mFence, VKALLOC);
|
||||
@@ -136,12 +129,12 @@ void VulkanCommandBuffer::reset() noexcept {
|
||||
mSubmission = mSemaphoreManager->acquire();
|
||||
|
||||
// reset the fence with proper host synchronization
|
||||
mCmdbufState->resetFence(mDevice);
|
||||
mFenceStatus->resetFence(mDevice);
|
||||
|
||||
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
|
||||
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
|
||||
// finishes executing the command buffer, the status changes to VK_SUCCESS.
|
||||
mCmdbufState = std::make_shared<VulkanCmdBufferState>(mFence);
|
||||
mFenceStatus = std::make_shared<VulkanCmdFence>(mFence);
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::pushMarker(char const* marker) noexcept {
|
||||
@@ -198,12 +191,6 @@ void VulkanCommandBuffer::begin() noexcept {
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
vkBeginCommandBuffer(mBuffer, &binfo);
|
||||
|
||||
// We need to reset the queries before writing to it. (This must happen within a command buffer,
|
||||
// but not before the queries have occurred).
|
||||
vkCmdResetQueryPool(mBuffer, mQueryPool, mQueryBeginIndex, 2);
|
||||
vkCmdWriteTimestamp(mBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, mQueryPool,
|
||||
mQueryBeginIndex);
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
||||
@@ -211,9 +198,6 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
||||
popMarker();
|
||||
}
|
||||
|
||||
vkCmdWriteTimestamp(mBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, mQueryPool,
|
||||
mQueryEndIndex);
|
||||
|
||||
vkEndCommandBuffer(mBuffer);
|
||||
|
||||
VkSemaphore submissionSemaphore = mSubmission->getVkSemaphore();
|
||||
@@ -252,7 +236,7 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE VkResult result =
|
||||
vkQueueSubmit(mQueue, 1, &submitInfo, mFence);
|
||||
mCmdbufState->setStatus(VK_NOT_READY, VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION);
|
||||
mFenceStatus->setStatus(VK_NOT_READY);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_COMMAND_BUFFER)
|
||||
if (result != VK_SUCCESS) {
|
||||
@@ -264,45 +248,6 @@ fvkmemory::resource_ptr<VulkanSemaphore> VulkanCommandBuffer::submit() {
|
||||
return mSubmission;
|
||||
}
|
||||
|
||||
void VulkanCommandBuffer::setComplete() {
|
||||
if (mCmdbufState->getBufferDuration() != VulkanCmdBufferState::UNKNOWN_BUFFER_DURATION) {
|
||||
return;
|
||||
}
|
||||
// We always query the duration of this buffer on the GPU. This is then used to inform timer
|
||||
// queries from the Filament-side. The reason for doing it this way is because the timestamp is
|
||||
// consistent within a command buffer, but not guarranteed to be consistent outside of a command
|
||||
// buffer. So we can only infer duration by looking at execution duration with respect to a
|
||||
// buffer.
|
||||
struct QueryResult {
|
||||
uint64_t beginTime = 0;
|
||||
uint64_t beginAvailable = 0;
|
||||
uint64_t endTime = 0;
|
||||
uint64_t endAvailable = 0;
|
||||
} result;
|
||||
|
||||
constexpr size_t dataSize = sizeof(result);
|
||||
constexpr VkDeviceSize stride = sizeof(uint64_t) * 2;
|
||||
VkResult const vkresult =
|
||||
vkGetQueryPoolResults(mDevice, mQueryPool, mQueryBeginIndex, 2, dataSize,
|
||||
(void*) &result, stride,
|
||||
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
|
||||
<< "vkGetQueryPoolResults error=" << static_cast<int32_t>(vkresult);
|
||||
|
||||
uint64_t const begin = result.beginTime;
|
||||
uint64_t const end = result.endTime;
|
||||
uint64_t duration = end - begin;
|
||||
|
||||
assert_invariant(result.beginAvailable != 0 && result.endAvailable != 0);
|
||||
if (begin > end) {
|
||||
FVK_LOGW << "Timestamps are not monotonically increasing. begin=" << begin <<
|
||||
" end=" << end;
|
||||
duration = begin - end;
|
||||
}
|
||||
|
||||
mCmdbufState->setStatus(VK_SUCCESS, duration);
|
||||
}
|
||||
|
||||
CommandBufferPool::CommandBufferPool(VulkanContext const& context, VkDevice device, VkQueue queue,
|
||||
uint8_t queueFamilyIndex, VulkanSemaphoreManager* semaphoreManager, bool isProtected)
|
||||
: mDevice(device),
|
||||
@@ -314,24 +259,11 @@ CommandBufferPool::CommandBufferPool(VulkanContext const& context, VkDevice devi
|
||||
(isProtected ? VK_COMMAND_POOL_CREATE_PROTECTED_BIT : 0u),
|
||||
.queueFamilyIndex = queueFamilyIndex,
|
||||
};
|
||||
VkResult result = vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateCommandPool failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
// Create a timestamp pool large enough to hold a pair of queries for each command buffer.
|
||||
VkQueryPoolCreateInfo tqpCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
||||
.queryCount = CAPACITY *2,
|
||||
};
|
||||
result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mQueryPool);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateQueryPool failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
vkCreateCommandPool(device, &createInfo, VKALLOC, &mPool);
|
||||
|
||||
for (size_t i = 0; i < CAPACITY; ++i) {
|
||||
mBuffers.emplace_back(std::make_unique<VulkanCommandBuffer>(context, device, queue, mPool,
|
||||
semaphoreManager, isProtected, i * 2, mQueryPool));
|
||||
mBuffers.emplace_back(std::make_unique<VulkanCommandBuffer>(
|
||||
context, device, queue, mPool, semaphoreManager, isProtected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,7 +271,6 @@ CommandBufferPool::~CommandBufferPool() {
|
||||
wait();
|
||||
gc();
|
||||
vkDestroyCommandPool(mDevice, mPool, VKALLOC);
|
||||
vkDestroyQueryPool(mDevice, mQueryPool, VKALLOC);
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& CommandBufferPool::getRecording() {
|
||||
@@ -483,7 +414,7 @@ void VulkanCommands::terminate() {
|
||||
mPool.reset();
|
||||
mProtectedPool.reset();
|
||||
mLastSubmit = {};
|
||||
mLastBufferState = {};
|
||||
mLastFenceStatus = {};
|
||||
}
|
||||
|
||||
VulkanCommandBuffer& VulkanCommands::get() {
|
||||
@@ -512,7 +443,8 @@ bool VulkanCommands::flush() {
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> dependency;
|
||||
bool hasFlushed = false;
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState> flushedBufferState;
|
||||
VkFence flushedFence = VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdFence> flushedFenceStatus;
|
||||
|
||||
// Note that we've ordered it so that the non-protected commands are followed by the protected
|
||||
// commands. This assumes that the protected commands will be that one doing the rendering into
|
||||
@@ -535,7 +467,8 @@ bool VulkanCommands::flush() {
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT);
|
||||
mLastSubmit = {};
|
||||
}
|
||||
flushedBufferState = pool->getRecording().getState();
|
||||
flushedFence = pool->getRecording().getVkFence();
|
||||
flushedFenceStatus = pool->getRecording().getFenceStatus();
|
||||
dependency = pool->flush();
|
||||
hasFlushed = true;
|
||||
}
|
||||
@@ -543,11 +476,8 @@ bool VulkanCommands::flush() {
|
||||
if (hasFlushed) {
|
||||
mInjectedDependency = VK_NULL_HANDLE;
|
||||
mLastSubmit = dependency;
|
||||
|
||||
if (mLastBufferState) {
|
||||
mLastBufferState->setNextState(flushedBufferState);
|
||||
}
|
||||
mLastBufferState = flushedBufferState;
|
||||
mLastFence = flushedFence;
|
||||
mLastFenceStatus = flushedFenceStatus;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace fvkmemory;
|
||||
struct CommandBufferPool;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
class VulkanGroupMarkers {
|
||||
@@ -66,8 +65,7 @@ private:
|
||||
// we're done waiting on the most recent submission of the given command buffer.
|
||||
struct VulkanCommandBuffer {
|
||||
VulkanCommandBuffer(VulkanContext const& mContext, VkDevice device, VkQueue queue,
|
||||
VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager, bool isProtected,
|
||||
uint32_t timerQueryIndex, VkQueryPool queryPool);
|
||||
VkCommandPool pool, VulkanSemaphoreManager* semaphoreManager, bool isProtected);
|
||||
|
||||
VulkanCommandBuffer(VulkanCommandBuffer const&) = delete;
|
||||
VulkanCommandBuffer& operator=(VulkanCommandBuffer const&) = delete;
|
||||
@@ -92,14 +90,20 @@ struct VulkanCommandBuffer {
|
||||
void begin() noexcept;
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> submit();
|
||||
|
||||
void setComplete();
|
||||
|
||||
VkResult getStatus() {
|
||||
return mCmdbufState->getFenceStatus();
|
||||
inline void setComplete() {
|
||||
mFenceStatus->setStatus(VK_SUCCESS);
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState> getState() const {
|
||||
return mCmdbufState;
|
||||
VkResult getStatus() {
|
||||
return mFenceStatus->getStatus();
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> getFenceStatus() const {
|
||||
return mFenceStatus;
|
||||
}
|
||||
|
||||
VkFence getVkFence() const {
|
||||
return mFence;
|
||||
}
|
||||
|
||||
VkCommandBuffer buffer() const {
|
||||
@@ -111,31 +115,22 @@ struct VulkanCommandBuffer {
|
||||
}
|
||||
|
||||
private:
|
||||
VkFence getVkFence() const {
|
||||
return mFence;
|
||||
}
|
||||
|
||||
static uint32_t sAgeCounter;
|
||||
|
||||
VulkanContext const& mContext;
|
||||
uint8_t mMarkerCount;
|
||||
bool const isProtected;
|
||||
VkDevice const mDevice;
|
||||
VkQueue const mQueue;
|
||||
VkDevice mDevice;
|
||||
VkQueue mQueue;
|
||||
VulkanSemaphoreManager* mSemaphoreManager;
|
||||
fvkutils::StaticVector<VkSemaphore, 2> mWaitSemaphores;
|
||||
fvkutils::StaticVector<VkPipelineStageFlags, 2> mWaitSemaphoreStages;
|
||||
VkCommandBuffer const mBuffer;
|
||||
VkCommandBuffer mBuffer;
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> mSubmission;
|
||||
VkFence const mFence;
|
||||
std::shared_ptr<VulkanCmdBufferState> mCmdbufState;
|
||||
VkFence mFence;
|
||||
std::shared_ptr<VulkanCmdFence> mFenceStatus;
|
||||
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
|
||||
uint32_t mAge;
|
||||
uint32_t const mQueryBeginIndex;
|
||||
uint32_t const mQueryEndIndex;
|
||||
VkQueryPool const mQueryPool;
|
||||
|
||||
friend struct CommandBufferPool;
|
||||
};
|
||||
|
||||
struct CommandBufferPool {
|
||||
@@ -179,8 +174,6 @@ private:
|
||||
std::vector<std::unique_ptr<VulkanCommandBuffer>> mBuffers;
|
||||
int8_t mRecording;
|
||||
|
||||
VkQueryPool mQueryPool;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
|
||||
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
|
||||
#endif
|
||||
@@ -206,7 +199,7 @@ private:
|
||||
//
|
||||
// - Allows off-thread queries of command buffer status.
|
||||
// - Exposes an "updateFences" method that transfers current fence status into atomics.
|
||||
// - Users can examine these atomic variables (see VulkanCmdBufferState) to determine status.
|
||||
// - Users can examine these atomic variables (see VulkanCmdFence) to determine status.
|
||||
// - We do this because vkGetFenceStatus must be called from the rendering thread.
|
||||
//
|
||||
class VulkanCommands {
|
||||
@@ -237,8 +230,12 @@ public:
|
||||
return sem;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState> getMostRecentBufferState() {
|
||||
return mLastBufferState;
|
||||
VkFence getMostRecentFence() {
|
||||
return mLastFence;
|
||||
}
|
||||
|
||||
std::shared_ptr<VulkanCmdFence> getMostRecentFenceStatus() {
|
||||
return mLastFenceStatus;
|
||||
}
|
||||
|
||||
// Takes a semaphore that signals when the next flush can occur. Only one injected
|
||||
@@ -279,7 +276,8 @@ private:
|
||||
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
|
||||
fvkmemory::resource_ptr<VulkanSemaphore> mLastSubmit;
|
||||
|
||||
std::shared_ptr<VulkanCmdBufferState> mLastBufferState;
|
||||
VkFence mLastFence = VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
|
||||
|
||||
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
|
||||
};
|
||||
|
||||
@@ -214,4 +214,10 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
|
||||
// destroying any unused pipeline object.
|
||||
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
|
||||
|
||||
// Indicates if the backend must be setup to allow doing a RenderDoc capture.
|
||||
//
|
||||
// If this is true, the features not supported by RenderDoc must be disabled, otherwise
|
||||
// when using RenderDoc the application will crash or will fail to do a capture.
|
||||
constexpr static const int FVK_RENDERDOC_CAPTURE_MODE = false;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -154,6 +154,10 @@ public:
|
||||
return mStagingBufferBypassEnabled;
|
||||
}
|
||||
|
||||
inline bool pipelineCreationFeedbackSupported() const noexcept {
|
||||
return mPipelineCreationFeedbackSupported;
|
||||
}
|
||||
|
||||
private:
|
||||
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
@@ -181,6 +185,7 @@ private:
|
||||
bool mProtectedMemorySupported = false;
|
||||
bool mIsUnifiedMemoryArchitecture = false;
|
||||
bool mStagingBufferBypassEnabled = false;
|
||||
bool mPipelineCreationFeedbackSupported = false;
|
||||
|
||||
fvkutils::VkFormatList mDepthStencilFormats;
|
||||
fvkutils::VkFormatList mBlittableDepthStencilFormats;
|
||||
|
||||
@@ -231,7 +231,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
|
||||
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
|
||||
mPipelineLayoutCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice()),
|
||||
mPipelineCache(mPlatform->getDevice(), mContext),
|
||||
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
|
||||
mBufferCache(mContext, mResourceManager, mAllocator),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
@@ -241,6 +241,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||
&mDescriptorSetLayoutCache),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
@@ -337,6 +338,8 @@ void VulkanDriver::terminate() {
|
||||
mDefaultRenderTarget = {};
|
||||
mPipelineState = {};
|
||||
|
||||
mQueryManager.terminate();
|
||||
|
||||
mBlitter.terminate();
|
||||
mReadPixels.terminate();
|
||||
|
||||
@@ -857,33 +860,32 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, utils::ImmutableCString&& ta
|
||||
cmdbuf = &mCommands.get();
|
||||
}
|
||||
// Note at this point, the fence has already been constructed via createFenceS, so we just tag
|
||||
// it with appropriate VulkanCmdBufferState, which is associated with the current, recording command
|
||||
// it with appropriate VulkanCmdFence, which is associated with the current, recording command
|
||||
// buffer.
|
||||
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
|
||||
fence->setCmdBufferState(cmdbuf->getState());
|
||||
fence->setFence(cmdbuf->getFenceStatus());
|
||||
mResourceManager.associateHandle(fh.getId(), std::move(tag));
|
||||
}
|
||||
|
||||
void VulkanDriver::createSyncR(Handle<HwSync> sh, utils::ImmutableCString&& tag) {
|
||||
auto sync = resource_ptr<VulkanSync>::cast(&mResourceManager, sh);
|
||||
VkFence fence = VK_NULL_HANDLE;
|
||||
std::shared_ptr<VulkanCmdBufferState> cmdbufState;
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus;
|
||||
if (mCurrentRenderPass.commandBuffer) {
|
||||
VulkanCommandBuffer* cmdBuff = mCurrentRenderPass.commandBuffer;
|
||||
cmdbufState = cmdBuff->getState();
|
||||
fence = cmdbufState->getVkFence();
|
||||
|
||||
fence = cmdBuff->getVkFence();
|
||||
fenceStatus = cmdBuff->getFenceStatus();
|
||||
// If we're currently recording, flush so that the fence only applies
|
||||
// to commands already issued.
|
||||
mCommands.flush();
|
||||
} else {
|
||||
cmdbufState = mCommands.getMostRecentBufferState();
|
||||
fence = cmdbufState->getVkFence();
|
||||
fence = mCommands.getMostRecentFence();
|
||||
fenceStatus = mCommands.getMostRecentFenceStatus();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(sync->lock);
|
||||
sync->sync = mPlatform->createSync(fence, cmdbufState);
|
||||
sync->sync = mPlatform->createSync(fence, fenceStatus);
|
||||
}
|
||||
|
||||
for (auto& cbData : sync->conversionCallbacks) {
|
||||
@@ -1073,10 +1075,9 @@ Handle<HwSwapChain> VulkanDriver::createSwapChainHeadlessS() noexcept {
|
||||
Handle<HwTimerQuery> VulkanDriver::createTimerQueryS() noexcept {
|
||||
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
|
||||
// before createTimerQueryR is executed.
|
||||
auto handle = mResourceManager.allocHandle<VulkanTimerQuery>();
|
||||
auto query = resource_ptr<VulkanTimerQuery>::make(&mResourceManager, handle);
|
||||
auto query = mQueryManager.getNextQuery(&mResourceManager);
|
||||
query.inc();
|
||||
return handle;
|
||||
return Handle<VulkanTimerQuery>(query.id());
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSetLayout> VulkanDriver::createDescriptorSetLayoutS() noexcept {
|
||||
@@ -1133,6 +1134,7 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
return;
|
||||
}
|
||||
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
|
||||
mQueryManager.clearQuery(vtq);
|
||||
vtq.dec();
|
||||
}
|
||||
|
||||
@@ -1195,6 +1197,7 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> const fh) {
|
||||
FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout) {
|
||||
// Even though this is a synchronous call, the fence handle must be (and stay) valid
|
||||
assert_invariant(fh);
|
||||
|
||||
auto fence = resource_ptr<VulkanFence>::cast(&mResourceManager, fh);
|
||||
|
||||
// we have to take into account that the STL's wait_for() actually works with
|
||||
@@ -1211,14 +1214,14 @@ FenceStatus VulkanDriver::fenceWait(FenceHandle const fh, uint64_t const timeout
|
||||
until = now + nanoseconds(timeout);
|
||||
}
|
||||
|
||||
auto const [cmdbufState, canceled] = fence->wait(until);
|
||||
if (!cmdbufState || canceled) {
|
||||
auto const [cmdfence, canceled] = fence->wait(until);
|
||||
if (!cmdfence || canceled) {
|
||||
return canceled ? FenceStatus::ERROR : FenceStatus::TIMEOUT_EXPIRED;
|
||||
}
|
||||
|
||||
// now we are holding a reference to our VulkanCmdBufferState, so we know it can't
|
||||
// now we are holding a reference to our VulkanCmdFence, so we know it can't
|
||||
// disappear while we wait
|
||||
return cmdbufState->waitOnFence(mPlatform->getDevice(), timeout, until);
|
||||
return cmdfence->wait(mPlatform->getDevice(), timeout, until);
|
||||
}
|
||||
|
||||
void VulkanDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
|
||||
@@ -1515,17 +1518,29 @@ void VulkanDriver::setupExternalImage(void* image) {
|
||||
|
||||
TimerQueryResult VulkanDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
|
||||
if (!vtq->isComplete()) {
|
||||
if (!vtq->isCompleted()) {
|
||||
return TimerQueryResult::NOT_READY;
|
||||
}
|
||||
uint64_t result = vtq->getResult();
|
||||
assert_invariant(result != VulkanTimerQuery::UNKNOWN_QUERY_RESULT);
|
||||
|
||||
auto results = mQueryManager.getResult(vtq);
|
||||
if (results.beginAvailable == 0 || results.endAvailable == 0) {
|
||||
return TimerQueryResult::NOT_READY;
|
||||
}
|
||||
|
||||
uint64_t const begin = results.beginTime;
|
||||
uint64_t const end = results.endTime;
|
||||
if (begin >= end) {
|
||||
// TODO: queries might have ran on different command buffers.
|
||||
FVK_LOGW << "Timestamps are not monotonically increasing. ";
|
||||
*elapsedTime = 0;
|
||||
return TimerQueryResult::ERROR;
|
||||
}
|
||||
|
||||
// NOTE: MoltenVK currently writes system time so the following delta will always be zero.
|
||||
// However there are plans for implementing this properly. See the following GitHub ticket.
|
||||
// https://github.com/KhronosGroup/MoltenVK/issues/773
|
||||
float const period = mContext.getPhysicalDeviceLimits().timestampPeriod;
|
||||
*elapsedTime = uint64_t(float(result) * period);
|
||||
*elapsedTime = uint64_t(float(end - begin) * period);
|
||||
return TimerQueryResult::AVAILABLE;
|
||||
}
|
||||
|
||||
@@ -2284,14 +2299,12 @@ void VulkanDriver::scissor(Viewport scissorBox) {
|
||||
|
||||
void VulkanDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
|
||||
auto cmdbuf = &mCommands.get();
|
||||
vtq->setBeginState(cmdbuf->getState());
|
||||
mQueryManager.beginQuery(&(mCommands.get()), vtq);
|
||||
}
|
||||
|
||||
void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
auto vtq = resource_ptr<VulkanTimerQuery>::cast(&mResourceManager, tqh);
|
||||
auto cmdbuf = &mCommands.get();
|
||||
vtq->setEndState(cmdbuf->getState());
|
||||
mQueryManager.endQuery(&(mCommands.get()), vtq);
|
||||
}
|
||||
|
||||
void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanQueryManager.h"
|
||||
#include "VulkanReadPixels.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanSemaphoreManager.h"
|
||||
@@ -158,6 +159,7 @@ private:
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
|
||||
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
|
||||
|
||||
@@ -34,8 +34,41 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
||||
: mDevice(device) {
|
||||
namespace {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedbackInfo) {
|
||||
VkPipelineCreationFeedback const& pipelineInfo = *feedbackInfo.pPipelineCreationFeedback;
|
||||
if (!(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool const isCacheHit =
|
||||
(pipelineInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
|
||||
FVK_LOGD << "Pipeline build stats - Cache hit: " << isCacheHit
|
||||
<< ", Time: " << pipelineInfo.duration / 1000000.0 << "ms";
|
||||
|
||||
for (uint32_t i = 0; i < feedbackInfo.pipelineStageCreationFeedbackCount; ++i) {
|
||||
VkPipelineCreationFeedback const& stageInfo = feedbackInfo.pPipelineStageCreationFeedbacks[i];
|
||||
if (!(stageInfo.flags & VK_PIPELINE_CREATION_FEEDBACK_VALID_BIT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const isVertexShader = (i == 0);
|
||||
bool const isCacheHit = (stageInfo.flags &
|
||||
VK_PIPELINE_CREATION_FEEDBACK_APPLICATION_PIPELINE_CACHE_HIT_BIT);
|
||||
FVK_LOGD << (isVertexShader ? "Vertex" : "Fragment")
|
||||
<< " shader build stats - Cache hit: " << isCacheHit
|
||||
<< ", Time: " << stageInfo.duration / 1000000.0 << "ms";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VulkanContext const& context)
|
||||
: mDevice(device),
|
||||
mContext(context) {
|
||||
VkPipelineCacheCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||
};
|
||||
@@ -216,15 +249,40 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = ("
|
||||
<< shaderStages[0].module << ", " << shaderStages[1].module << ")";
|
||||
#endif
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
|
||||
<< shaderStages[1].module << ")";
|
||||
|
||||
VkPipelineCreationFeedback stageFeedbacks[SHADER_MODULE_COUNT] = {};
|
||||
VkPipelineCreationFeedback pipelineFeedback = {};
|
||||
VkPipelineCreationFeedbackCreateInfo feedbackInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATION_FEEDBACK_CREATE_INFO_EXT,
|
||||
.pNext = nullptr,
|
||||
.pPipelineCreationFeedback = &pipelineFeedback,
|
||||
.pipelineStageCreationFeedbackCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
|
||||
.pPipelineStageCreationFeedbacks = stageFeedbacks,
|
||||
};
|
||||
|
||||
if (mContext.pipelineCreationFeedbackSupported()) {
|
||||
feedbackInfo.pNext = pipelineCreateInfo.pNext;
|
||||
pipelineCreateInfo.pNext = &feedbackInfo;
|
||||
}
|
||||
#endif
|
||||
PipelineCacheEntry cacheEntry = {
|
||||
.lastUsed = mCurrentTime,
|
||||
};
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
|
||||
VKALLOC, &cacheEntry.handle);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
|
||||
<< shaderStages[1].module << ")";
|
||||
|
||||
if (mContext.pipelineCreationFeedbackSupported()) {
|
||||
printPipelineFeedbackInfo(feedbackInfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
if (error != VK_SUCCESS) {
|
||||
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
|
||||
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
|
||||
|
||||
VulkanPipelineCache(VkDevice device);
|
||||
VulkanPipelineCache(VkDevice device, VulkanContext const& context);
|
||||
|
||||
void bindLayout(VkPipelineLayout layout) noexcept;
|
||||
|
||||
@@ -210,6 +210,8 @@ private:
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
[[maybe_unused]] VulkanContext const& mContext;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
106
filament/backend/src/vulkan/VulkanQueryManager.cpp
Normal file
106
filament/backend/src/vulkan/VulkanQueryManager.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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 "VulkanQueryManager.h"
|
||||
|
||||
#include "VulkanAsyncHandles.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanConstants.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
VulkanQueryManager::VulkanQueryManager(VkDevice device) : mDevice(device) {
|
||||
// Create a timestamp pool large enough to hold a pair of queries for each timer.
|
||||
VkQueryPoolCreateInfo tqpCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
|
||||
.queryType = VK_QUERY_TYPE_TIMESTAMP,
|
||||
.queryCount = (uint32_t) mUsed.size() * 2,
|
||||
};
|
||||
VkResult result = vkCreateQueryPool(mDevice, &tqpCreateInfo, VKALLOC, &mPool);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "vkCreateQueryPool failed."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> VulkanQueryManager::getNextQuery(
|
||||
fvkmemory::ResourceManager* resourceManager) {
|
||||
auto unused = ~mUsed;
|
||||
if (unused.empty()) {
|
||||
FVK_LOGE << "More than " << mUsed.size() << " timers are not supported.";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> queryIndices;
|
||||
size_t const firstUnused = unused.firstSetBit();
|
||||
{
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
mUsed.set(firstUnused);
|
||||
queryIndices = { firstUnused * 2, firstUnused * 2 + 1 };
|
||||
}
|
||||
return fvkmemory::resource_ptr<VulkanTimerQuery>::construct(resourceManager, queryIndices.first,
|
||||
queryIndices.second);
|
||||
}
|
||||
|
||||
void VulkanQueryManager::clearQuery(fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
std::unique_lock<utils::Mutex> lock(mMutex);
|
||||
uint32_t const startingIndex = query->getStartingQueryIndex();
|
||||
mUsed.unset(startingIndex / 2);
|
||||
}
|
||||
|
||||
void VulkanQueryManager::beginQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
|
||||
auto const cmdbuffer = commands->buffer();
|
||||
vkCmdResetQueryPool(cmdbuffer, mPool, index, 2);
|
||||
vkCmdWriteTimestamp(cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
|
||||
// We stash this because getResult might come before the query is actually processed.
|
||||
query->setFence(commands->getFenceStatus());
|
||||
}
|
||||
|
||||
void VulkanQueryManager::endQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStoppingQueryIndex();
|
||||
vkCmdWriteTimestamp(commands->buffer(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
|
||||
}
|
||||
|
||||
VulkanQueryManager::QueryResult VulkanQueryManager::getResult(
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query) {
|
||||
uint32_t const index = query->getStartingQueryIndex();
|
||||
QueryResult result;
|
||||
size_t const dataSize = sizeof(result);
|
||||
VkDeviceSize const stride = sizeof(uint64_t) * 2;
|
||||
VkResult vkresult =
|
||||
vkGetQueryPoolResults(mDevice, mPool, index, 2, dataSize, (void*) &result, stride,
|
||||
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WITH_AVAILABILITY_BIT);
|
||||
FILAMENT_CHECK_POSTCONDITION(vkresult == VK_SUCCESS || vkresult == VK_NOT_READY)
|
||||
<< "vkGetQueryPoolResults error=" << static_cast<int32_t>(vkresult);
|
||||
if (vkresult == VK_NOT_READY) {
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void VulkanQueryManager::terminate() noexcept {
|
||||
vkDestroyQueryPool(mDevice, mPool, VKALLOC);
|
||||
mPool = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
} // filament::backend
|
||||
63
filament/backend/src/vulkan/VulkanQueryManager.h
Normal file
63
filament/backend/src/vulkan/VulkanQueryManager.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanCommandBuffer;
|
||||
struct VulkanTimerQuery;
|
||||
|
||||
class VulkanQueryManager {
|
||||
public:
|
||||
struct QueryResult {
|
||||
uint64_t beginTime = 0;
|
||||
uint64_t beginAvailable = 0;
|
||||
uint64_t endTime = 0;
|
||||
uint64_t endAvailable = 0;
|
||||
};
|
||||
|
||||
VulkanQueryManager(VkDevice device);
|
||||
~VulkanQueryManager() = default;
|
||||
|
||||
// Not copy-able.
|
||||
VulkanQueryManager(VulkanQueryManager const&) = delete;
|
||||
VulkanQueryManager& operator=(VulkanQueryManager const&) = delete;
|
||||
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery>
|
||||
getNextQuery(fvkmemory::ResourceManager* mResourceManager);
|
||||
void clearQuery(fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
void beginQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
void endQuery(VulkanCommandBuffer const* commands,
|
||||
fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
QueryResult getResult(fvkmemory::resource_ptr<VulkanTimerQuery> query);
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VkQueryPool mPool;
|
||||
utils::bitset32 mUsed;
|
||||
utils::Mutex mMutex;
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANQUERYMANAGER_H
|
||||
@@ -228,6 +228,10 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
|
||||
#endif
|
||||
VK_KHR_MULTIVIEW_EXTENSION_NAME,
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
ExtensionSet exts;
|
||||
// Identify supported physical device extensions
|
||||
@@ -865,6 +869,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
if (!mImpl->mSharedContext) {
|
||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
context.mPipelineCreationFeedbackSupported =
|
||||
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
|
||||
} else {
|
||||
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
|
||||
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
|
||||
@@ -873,13 +879,16 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
|
||||
// Check the availability of lazily allocated memory
|
||||
context.mLazilyAllocatedMemorySupported = false;
|
||||
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
|
||||
++i) {
|
||||
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
|
||||
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
|
||||
context.mLazilyAllocatedMemorySupported = true;
|
||||
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
break;
|
||||
// RenderDoc doesn't support lazy allocated memory
|
||||
if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
|
||||
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
|
||||
++i) {
|
||||
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
|
||||
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
|
||||
context.mLazilyAllocatedMemorySupported = true;
|
||||
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,7 +980,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
|
||||
}
|
||||
|
||||
Platform::Sync* VulkanPlatform::createSync(VkFence fence,
|
||||
std::shared_ptr<VulkanCmdBufferState> fenceStatus) noexcept {
|
||||
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept {
|
||||
return new VulkanSync{.fence = fence, .fenceStatus = fenceStatus};
|
||||
}
|
||||
|
||||
@@ -1022,4 +1031,8 @@ VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const no
|
||||
return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
|
||||
}
|
||||
|
||||
bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
|
||||
return mImpl->mContext.isLazilyAllocatedMemorySupported();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -158,7 +158,47 @@ std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardware
|
||||
};
|
||||
}
|
||||
|
||||
}// namespace
|
||||
uint32_t selectMemoryTypeForExternalImage(VkPhysicalDevice physicalDevice, VkDevice device,
|
||||
VkImage image, uint32_t types, VkFlags requiredMemoryFlags) {
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
uint32_t const memoryTypeIndex =
|
||||
VulkanContext::selectMemoryType(memoryProperties, types, requiredMemoryFlags);
|
||||
|
||||
if constexpr (FVK_RENDERDOC_CAPTURE_MODE) {
|
||||
// RenderDoc will replay external resources as non-external.
|
||||
// Adjust properties that will trip it up when replaying, even though these are not valid.
|
||||
// Update memory type index if necessary so that we can replay the capture.
|
||||
|
||||
VkMemoryRequirements imageMemoryRequirements;
|
||||
vkGetImageMemoryRequirements(device, image, &imageMemoryRequirements);
|
||||
|
||||
uint32_t const imageMemoryTypeBits = imageMemoryRequirements.memoryTypeBits;
|
||||
bool const isMemoryTypeSupported = ((1 << memoryTypeIndex) & imageMemoryTypeBits) != 0;
|
||||
if (isMemoryTypeSupported) {
|
||||
return memoryTypeIndex;
|
||||
}
|
||||
|
||||
// Current memory type will not be replayable by RenderDoc.
|
||||
// Attempt to change the memory type index
|
||||
VkMemoryPropertyFlags const kRenderDocFallBackReqs = 0;
|
||||
uint32_t commonMemoryTypeBits = types & imageMemoryTypeBits;
|
||||
uint32_t commonTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
commonMemoryTypeBits, kRenderDocFallBackReqs);
|
||||
if (commonMemoryTypeBits && commonTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
return commonTypeIndex;
|
||||
}
|
||||
uint32_t imageTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
imageMemoryTypeBits, kRenderDocFallBackReqs);
|
||||
if (imageTypeIndex != VK_MAX_MEMORY_TYPES) {
|
||||
return imageTypeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return memoryTypeIndex;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
@@ -354,8 +394,6 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
.image = image,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
VkMemoryPropertyFlags requiredMemoryFlags =
|
||||
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
@@ -365,8 +403,8 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
uint32_t const memoryTypeIndex = selectMemoryTypeForExternalImage(physicalDevice, device,
|
||||
image, metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
@@ -407,7 +445,7 @@ bool VulkanPlatformAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) const
|
||||
VulkanSync& vulkanSync = static_cast<VulkanSync&>(*sync);
|
||||
assert_invariant(vulkanSync.fenceStatus);
|
||||
|
||||
if (vulkanSync.fenceStatus->getFenceStatus() == VK_SUCCESS) {
|
||||
if (vulkanSync.fenceStatus->getStatus() == VK_SUCCESS) {
|
||||
// We've already signaled; return -1 so that operations will proceed
|
||||
// immediately. Also, signal that fence conversion was successful.
|
||||
*fd = -1;
|
||||
|
||||
@@ -63,7 +63,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
// WebGPU requires that the size of the data copied from the staging buffer to the GPU buffer is a
|
||||
// multiple of 4. This function handles cases where the buffer descriptor's size is not a multiple
|
||||
// of 4 by padding with zeros.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor&& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
@@ -89,24 +89,49 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = "Filament WebGPU Staging Buffer",
|
||||
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
||||
.size = stagingBufferSize,
|
||||
.mappedAtCreation = true };
|
||||
.size = stagingBufferSize};
|
||||
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
|
||||
|
||||
void* mappedRange = stagingBuffer.GetMappedRange();
|
||||
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
||||
|
||||
// Make sure the padded memory is set to 0 to have deterministic behaviors
|
||||
if (remainder != 0) {
|
||||
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
|
||||
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
}
|
||||
|
||||
stagingBuffer.Unmap();
|
||||
|
||||
// Copy the staging buffer contents to the destination buffer.
|
||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
|
||||
byteOffset, stagingBufferSize);
|
||||
struct UserData final {
|
||||
uint32_t byteOffset;
|
||||
size_t stagingBufferSize;
|
||||
size_t remainder;
|
||||
BufferDescriptor srcBufferDescriptor;
|
||||
wgpu::Buffer stagingBuffer;
|
||||
WebGPUQueueManager* const webGPUQueueManager;
|
||||
wgpu::Buffer dstBuffer;
|
||||
};
|
||||
auto userData = std::make_unique<UserData>(UserData{
|
||||
.byteOffset = byteOffset,
|
||||
.stagingBufferSize = stagingBufferSize,
|
||||
.remainder = remainder,
|
||||
.srcBufferDescriptor = std::move(bufferDescriptor),
|
||||
.stagingBuffer = stagingBuffer,
|
||||
.webGPUQueueManager = webGPUQueueManager,
|
||||
.dstBuffer = mBuffer});
|
||||
stagingBuffer.MapAsync(
|
||||
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowProcessEvents,
|
||||
[](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
|
||||
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
|
||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||
void* mappedRange = data->stagingBuffer.GetMappedRange();
|
||||
memcpy(mappedRange, data->srcBufferDescriptor.buffer,
|
||||
data->srcBufferDescriptor.size);
|
||||
if (data->remainder != 0) {
|
||||
uint8_t* paddingStart =
|
||||
static_cast<uint8_t*>(mappedRange) + data->srcBufferDescriptor.size;
|
||||
memset(paddingStart, 0,
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - data->remainder);
|
||||
}
|
||||
data->stagingBuffer.Unmap();
|
||||
data->webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(
|
||||
data->stagingBuffer, 0, data->dstBuffer, data->byteOffset,
|
||||
data->stagingBufferSize);
|
||||
} else {
|
||||
FWGPU_LOGE << "Failed to map staging buffer for readPixels: " << message;
|
||||
}
|
||||
},
|
||||
userData.release());
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
* happen after draw commands encoded in the encoder. Submitting any commands up to this point
|
||||
* ensures the calls happen in the expected sequence.
|
||||
*/
|
||||
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
|
||||
void updateGPUBuffer(BufferDescriptor&&, uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager);
|
||||
|
||||
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
||||
|
||||
@@ -856,7 +856,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
@@ -867,14 +867,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
->updateGPUBuffer(std::move(bufferDescriptor), byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -58,6 +59,7 @@ WebGPUQueueManager::WebGPUQueueManager(wgpu::Device const& device)
|
||||
WebGPUQueueManager::~WebGPUQueueManager() = default;
|
||||
|
||||
wgpu::CommandEncoder WebGPUQueueManager::getCommandEncoder() {
|
||||
// std::unique_lock<std::mutex> lock(mLock);
|
||||
if (!mCommandEncoder) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||
.label = "Filament Command Encoder",
|
||||
@@ -90,7 +92,9 @@ std::shared_ptr<WebGPUSubmissionState> WebGPUQueueManager::getLatestSubmissionSt
|
||||
}
|
||||
|
||||
void WebGPUQueueManager::submit() {
|
||||
// std::unique_lock<std::mutex> lock(mLock);
|
||||
if (!mCommandEncoder) {
|
||||
std::cout << "Run Yu: no mCommandEncoder found!\n";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ private:
|
||||
wgpu::Queue mQueue;
|
||||
wgpu::CommandEncoder mCommandEncoder;
|
||||
std::shared_ptr<WebGPUSubmissionState> mLatestSubmissionState;
|
||||
std::mutex mLock;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -258,6 +258,10 @@ void PostProcessManager::bindPerRenderableDescriptorSet(DriverApi& driver) const
|
||||
{ { 0, 0 }, driver });
|
||||
}
|
||||
|
||||
UboManager* PostProcessManager::getUboManager() const noexcept {
|
||||
return mEngine.getUboManager();
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void PostProcessManager::registerPostProcessMaterial(std::string_view const name,
|
||||
StaticMaterialInfo const& info) {
|
||||
@@ -513,7 +517,7 @@ UTILS_NOINLINE
|
||||
void PostProcessManager::commitAndRenderFullScreenQuad(DriverApi& driver,
|
||||
FrameGraphResources::RenderPassInfo const& out, FMaterialInstance const* mi,
|
||||
PostProcessVariant const variant) const noexcept {
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
PipelineState const pipeline = getPipelineState(ma, variant);
|
||||
@@ -645,7 +649,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
|
||||
auto th = driver.createTextureView(in, level, 1);
|
||||
mi->setParameter("depth", th, SamplerParams{
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
|
||||
@@ -1079,7 +1083,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
mi->setParameter("ssctRayCount",
|
||||
float2{ options.ssct.rayCount, 1.0f / float(options.ssct.rayCount) });
|
||||
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(ma);
|
||||
@@ -1200,7 +1204,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::bilateralBlurPass(FrameGraph
|
||||
mi->setParameter("sampleCount", kGaussianCount);
|
||||
mi->setParameter("farPlaneOverEdgeDistance", -zf / config.bilateralThreshold);
|
||||
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(ma);
|
||||
@@ -1895,7 +1899,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
.filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST });
|
||||
mi->setParameter("weightScale", 0.5f / float(1u << level)); // FIXME: halfres?
|
||||
mi->setParameter("texelSize", float2{ 1.0f / w, 1.0f / h });
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
@@ -2425,7 +2429,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
mi->setParameter("source", hwOutView, SamplerParams{
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST});
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
renderFullScreenQuad(hwDstRT, pipeline, driver);
|
||||
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
|
||||
@@ -2533,7 +2537,7 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
|
||||
FMaterialInstance* const mi =
|
||||
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
|
||||
vignetteOptions, width, height);
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
}
|
||||
|
||||
void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
@@ -2566,7 +2570,7 @@ void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomRe
|
||||
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
|
||||
mFixedMaterialInstanceIndex.customResolve = fixedIndex;
|
||||
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
material.getMaterial(mEngine);
|
||||
}
|
||||
|
||||
@@ -2619,7 +2623,7 @@ void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver) noexcep
|
||||
auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
|
||||
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
|
||||
mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
material.getMaterial(mEngine);
|
||||
}
|
||||
|
||||
@@ -3111,7 +3115,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
mat4f{ historyProjection * inverse(current.projection) } *
|
||||
normalizedToClip);
|
||||
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
if (colorGradingConfig.asSubpass) {
|
||||
@@ -3196,7 +3200,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::rcas(
|
||||
mi->setParameter("resolution", float4{
|
||||
outputDesc.width, outputDesc.height,
|
||||
1.0f / outputDesc.width, 1.0f / outputDesc.height });
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(material.getMaterial(mEngine), variant);
|
||||
@@ -3287,7 +3291,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleBilinear(FrameGraph&
|
||||
float(vp.width) / inputDesc.width,
|
||||
float(vp.height) / inputDesc.height
|
||||
});
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto out = resources.getRenderPassInfo();
|
||||
@@ -3374,7 +3378,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleSGSR1(FrameGraph& fg,
|
||||
float(inputDesc.height)
|
||||
});
|
||||
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto const out = resources.getRenderPassInfo();
|
||||
@@ -3469,7 +3473,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
|
||||
mi->setParameter("resolution",
|
||||
float4{ outputDesc.width, outputDesc.height,
|
||||
1.0f / outputDesc.width, 1.0f / outputDesc.height });
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
}
|
||||
|
||||
@@ -3496,7 +3500,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::upscaleFSR1(FrameGraph& fg,
|
||||
float(vp.width) / inputDesc.width,
|
||||
float(vp.height) / inputDesc.height
|
||||
});
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
}
|
||||
|
||||
@@ -3582,7 +3586,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blit(FrameGraph& fg, bool co
|
||||
if (layer) {
|
||||
mi->setParameter("layerIndex", layer);
|
||||
}
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(ma);
|
||||
@@ -3840,7 +3844,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
|
||||
});
|
||||
mi->setParameter("layer", uint32_t(layer));
|
||||
mi->setParameter("uvscale", 1.0f / float(dim));
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
renderFullScreenQuadWithScissor(out, pipeline, scissor, driver);
|
||||
@@ -3942,7 +3946,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
|
||||
float(vp.width) / inputDesc.width,
|
||||
float(vp.height) / inputDesc.height
|
||||
});
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
mi->use(driver);
|
||||
|
||||
auto pipeline = getPipelineState(ma);
|
||||
@@ -3959,7 +3963,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::debugCombineArrayTexture(Fra
|
||||
// Render all layers of the texture to the screen side-by-side.
|
||||
for (uint32_t i = 0; i < inputTextureDesc.depth; ++i) {
|
||||
mi->setParameter("layerIndex", i);
|
||||
mi->commit(driver);
|
||||
mi->commit(driver, getUboManager());
|
||||
renderFullScreenQuad(out, pipeline, driver);
|
||||
DescriptorSet::unbind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
|
||||
// From the second draw, don't clear the targetbuffer.
|
||||
|
||||
@@ -65,6 +65,7 @@ class FMaterialInstance;
|
||||
class FrameGraph;
|
||||
class RenderPass;
|
||||
class RenderPassBuilder;
|
||||
class UboManager;
|
||||
struct CameraInfo;
|
||||
|
||||
class PostProcessManager {
|
||||
@@ -422,6 +423,8 @@ private:
|
||||
return getMaterialInstance(ma);
|
||||
}
|
||||
|
||||
UboManager* getUboManager() const noexcept;
|
||||
|
||||
backend::RenderPrimitiveHandle mFullScreenQuadRph;
|
||||
backend::VertexBufferInfoHandle mFullScreenQuadVbih;
|
||||
backend::DescriptorSetLayoutHandle mPerRenderableDslh;
|
||||
|
||||
@@ -1012,6 +1012,8 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
|
||||
uint32_t const index = (first->key & CUSTOM_INDEX_MASK) >> CUSTOM_INDEX_SHIFT;
|
||||
assert_invariant(index < mCustomCommands.size());
|
||||
pCustomCommands[index]();
|
||||
currentPipeline = {};
|
||||
currentPrimitiveHandle ={};
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1069,26 +1071,34 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
|
||||
pipeline.pipelineLayout.setLayout[+DescriptorSetBindingPoints::PER_MATERIAL] =
|
||||
ma->getDescriptorSetLayout(info.materialVariant).getHandle();
|
||||
|
||||
|
||||
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
|
||||
// It is possible to get a post-process material here (even though it's
|
||||
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
|
||||
// Ideally we would have a more formal compute API). In this case, we need
|
||||
// to set the post-process descriptor-set.
|
||||
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
|
||||
} else {
|
||||
// If we have a ColorPassDescriptorSet we use it to bind the per-view
|
||||
// descriptor-set (ideally only if it changed). If we don't, it means
|
||||
// the descriptor-set is already bound and the layout we got from the
|
||||
// material above should match. This is the case for situations where we
|
||||
// have a known per-view descriptor-set layout, e.g.: shadow-maps, ssr and
|
||||
// structure passes.
|
||||
if (mColorPassDescriptorSet) {
|
||||
// If we have a ColorPassDescriptorSet we use it to bind the per-view
|
||||
// descriptor-set (ideally only if it changed).
|
||||
// If we don't, it means the descriptor-set is already bound and the layout we
|
||||
// got from the material above should match. This is the case for situations
|
||||
// where we have a known per-view descriptor-set layout,
|
||||
// e.g.: postfx, shadow-maps, ssr and structure passes.
|
||||
if (mColorPassDescriptorSet) {
|
||||
if (UTILS_UNLIKELY(ma->getMaterialDomain() == MaterialDomain::POST_PROCESS)) {
|
||||
// It is possible to get a post-process material here (even though it's
|
||||
// not technically a public API yet, it is used by the IBLPrefilterLibrary.
|
||||
// Ideally we would have a more formal compute API). In this case, we need
|
||||
// to set the post-process descriptor-set.
|
||||
engine.getPostProcessManager().bindPostProcessDescriptorSet(driver);
|
||||
} else {
|
||||
// We have a ColorPassDescriptorSet, we need to go through it for binding
|
||||
// the per-view descriptor-set because its layout can change based on the
|
||||
// material.
|
||||
mColorPassDescriptorSet->bind(driver, ma->getPerViewLayoutIndex());
|
||||
}
|
||||
} else {
|
||||
// if we're here it means the per-view descriptor set is constant and
|
||||
// already set. This will be the case for postfx, ssr, structure and
|
||||
// shadow passes. All these passes use a static descriptor set layout
|
||||
// (albeit potentially different for each pass). In particular the
|
||||
// per-view UBO must be compatible for all material domains.
|
||||
// This is the case by construction for postfx, ssr. However, shadows
|
||||
// and structure have their own UBO, but it's content is (must be)
|
||||
// compatible with POST_PROCESS and COMPUTE materials.
|
||||
}
|
||||
|
||||
// Each MaterialInstance has its own descriptor set. This binds it.
|
||||
|
||||
@@ -142,7 +142,11 @@ void BufferAllocator::retire(AllocationId id) {
|
||||
InternalSlotNode* targetNode = getNodeById(id);
|
||||
assert_invariant(targetNode != nullptr);
|
||||
|
||||
targetNode->slot.isAllocated = false;
|
||||
Slot& slot = targetNode->slot;
|
||||
slot.isAllocated = false;
|
||||
if (slot.gpuUseCount == 0) {
|
||||
mHasPendingFrees = true;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferAllocator::acquireGpu(AllocationId id) {
|
||||
@@ -157,10 +161,18 @@ void BufferAllocator::releaseGpu(AllocationId id) {
|
||||
assert_invariant(targetNode != nullptr);
|
||||
assert_invariant(targetNode->slot.gpuUseCount > 0);
|
||||
|
||||
targetNode->slot.gpuUseCount--;
|
||||
Slot& slot = targetNode->slot;
|
||||
slot.gpuUseCount--;
|
||||
if (slot.gpuUseCount == 0 && !slot.isAllocated) {
|
||||
mHasPendingFrees = true;
|
||||
}
|
||||
}
|
||||
|
||||
void BufferAllocator::releaseFreeSlots() {
|
||||
if (!mHasPendingFrees) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto curr = mSlotPool.begin();
|
||||
while (curr != mSlotPool.end()) {
|
||||
if (!curr->slot.isFree()) {
|
||||
@@ -199,6 +211,7 @@ void BufferAllocator::releaseFreeSlots() {
|
||||
|
||||
curr = next;
|
||||
}
|
||||
mHasPendingFrees = false;
|
||||
}
|
||||
|
||||
BufferAllocator::allocation_size_t BufferAllocator::getTotalSize() const noexcept {
|
||||
|
||||
@@ -113,8 +113,9 @@ private:
|
||||
|
||||
[[nodiscard]] InternalSlotNode* getNodeById(AllocationId id) const noexcept;
|
||||
|
||||
bool mHasPendingFrees = false;
|
||||
allocation_size_t mTotalSize;
|
||||
const allocation_size_t mSlotSize; // Size of a single slot in bytes.
|
||||
const allocation_size_t mSlotSize; // Size of a single slot in bytes
|
||||
std::list<InternalSlotNode> mSlotPool; // All slots, including both allocated and freed
|
||||
std::multimap</*slot size*/allocation_size_t, InternalSlotNode*> mFreeList;
|
||||
std::unordered_map</*slot offset*/allocation_size_t, InternalSlotNode*> mOffsetMap;
|
||||
|
||||
@@ -468,11 +468,26 @@ void FEngine::init() {
|
||||
}
|
||||
mDefaultMaterial = downcast(defaultMaterialBuilder.build(*this));
|
||||
}
|
||||
|
||||
// We must commit the default material instance here. It may not be used in a scene, but its
|
||||
// descriptor set may still be used for shared variants.
|
||||
mDefaultMaterial->getDefaultInstance()->commit(driverApi);
|
||||
//
|
||||
// Note that this material instance is instantiated before the creation of UboManager, so at
|
||||
// this point `isUboBatchingEnabled` is `false`, and it will fall back to individual UBO
|
||||
// automatically.
|
||||
mDefaultMaterial->getDefaultInstance()->commit(driverApi, mUboManager);
|
||||
|
||||
if (UTILS_UNLIKELY(getSupportedFeatureLevel() >= FeatureLevel::FEATURE_LEVEL_1)) {
|
||||
// UBO batching is not supported in feature level 0
|
||||
if (features.material.enable_material_instance_uniform_batching) {
|
||||
// Ubo size of each material instance is at least 16 bytes.
|
||||
constexpr BufferAllocator::allocation_size_t minSlotSize = 16;
|
||||
auto const uboOffsetAlignment = static_cast<BufferAllocator::allocation_size_t>(
|
||||
driverApi.getUniformBufferOffsetAlignment());
|
||||
BufferAllocator::allocation_size_t slotSize = std::max(minSlotSize, uboOffsetAlignment);
|
||||
mUboManager = new UboManager(getDriverApi(), slotSize, mConfig.sharedUboInitialSizeInBytes);
|
||||
}
|
||||
|
||||
mDefaultColorGrading = downcast(ColorGrading::Builder().build(*this));
|
||||
|
||||
constexpr float3 dummyPositions[1] = {};
|
||||
@@ -653,6 +668,12 @@ void FEngine::shutdown() {
|
||||
|
||||
driver.destroyRenderTarget(std::move(mDefaultRenderTarget));
|
||||
|
||||
if (isUboBatchingEnabled()) {
|
||||
mUboManager->terminate(driver);
|
||||
delete mUboManager;
|
||||
mUboManager = nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Shutdown the backend...
|
||||
*/
|
||||
@@ -693,18 +714,31 @@ void FEngine::prepare() {
|
||||
// UBOs that are visible only. It's not such a big issue because the actual upload() is
|
||||
// skipped if the UBO hasn't changed. Still we could have a lot of these.
|
||||
DriverApi& driver = getDriverApi();
|
||||
const bool useUboBatching = isUboBatchingEnabled();
|
||||
|
||||
if (useUboBatching) {
|
||||
assert_invariant(mUboManager != nullptr);
|
||||
|
||||
mUboManager->beginFrame(driver, mMaterialInstances);
|
||||
}
|
||||
|
||||
UboManager* uboManager = mUboManager;
|
||||
for (auto& materialInstanceList: mMaterialInstances) {
|
||||
materialInstanceList.second.forEach([&driver](FMaterialInstance* item) {
|
||||
materialInstanceList.second.forEach([&driver, uboManager](FMaterialInstance* item) {
|
||||
// post-process materials instances must be commited explicitly because their
|
||||
// parameters are typically not set at this point in time.
|
||||
if (item->getMaterial()->getMaterialDomain() == MaterialDomain::SURFACE) {
|
||||
item->commitStreamUniformAssociations(driver);
|
||||
item->commit(driver);
|
||||
item->commit(driver, uboManager);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (useUboBatching) {
|
||||
assert_invariant(mUboManager != nullptr);
|
||||
getUboManager()->finishBeginFrame(getDriverApi());
|
||||
}
|
||||
|
||||
mMaterials.forEach([](FMaterial* material) {
|
||||
#if FILAMENT_ENABLE_MATDBG // NOLINT(*-include-cleaner)
|
||||
material->checkProgramEdits();
|
||||
@@ -721,6 +755,13 @@ void FEngine::gc() {
|
||||
mCameraManager.gc(*this, em);
|
||||
}
|
||||
|
||||
void FEngine::submitFrame() {
|
||||
if (isUboBatchingEnabled()) {
|
||||
DriverApi& driver = getDriverApi();
|
||||
getUboManager()->endFrame(driver, getMaterialInstanceResourceList());
|
||||
}
|
||||
}
|
||||
|
||||
void FEngine::flush() {
|
||||
// flush the command buffer
|
||||
flushCommandBuffer(mCommandBufferQueue);
|
||||
@@ -951,9 +992,10 @@ FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
||||
return p;
|
||||
}
|
||||
|
||||
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material,
|
||||
const char* name) noexcept {
|
||||
FMaterialInstance* p = mHeapAllocator.make<FMaterialInstance>(*this, material, name);
|
||||
FMaterialInstance* FEngine::createMaterialInstance(const FMaterial* material, const char* name,
|
||||
UboBatchingMode batchingMode) noexcept {
|
||||
FMaterialInstance* p =
|
||||
mHeapAllocator.make<FMaterialInstance>(*this, material, name, batchingMode);
|
||||
if (UTILS_LIKELY(p)) {
|
||||
auto pos = mMaterialInstances.emplace(material, "MaterialInstance");
|
||||
pos.first->second.insert(p);
|
||||
@@ -1243,6 +1285,11 @@ UTILS_NOINLINE
|
||||
bool FEngine::destroy(const FMaterialInstance* p) {
|
||||
if (p == nullptr) return true;
|
||||
|
||||
if (p->isUsingUboBatching()) {
|
||||
assert_invariant(isUboBatchingEnabled());
|
||||
mUboManager->retireSlot(p->getAllocationId());
|
||||
}
|
||||
|
||||
// Check that the material instance we're destroying is not in use in the RenderableManager
|
||||
// To do this, we currently need to inspect all render primitives in the RenderableManager
|
||||
EntityManager const& em = mEntityManager;
|
||||
|
||||
@@ -21,16 +21,17 @@
|
||||
|
||||
#include "Allocators.h"
|
||||
#include "DFG.h"
|
||||
#include "PostProcessManager.h"
|
||||
#include "ResourceList.h"
|
||||
#include "HwDescriptorSetLayoutFactory.h"
|
||||
#include "HwVertexBufferInfoFactory.h"
|
||||
#include "MaterialCache.h"
|
||||
#include "PostProcessManager.h"
|
||||
#include "ResourceList.h"
|
||||
#include "UboManager.h"
|
||||
|
||||
#include "components/CameraManager.h"
|
||||
#include "components/LightManager.h"
|
||||
#include "components/TransformManager.h"
|
||||
#include "components/RenderableManager.h"
|
||||
#include "components/TransformManager.h"
|
||||
|
||||
#include "ds/DescriptorSetLayout.h"
|
||||
|
||||
@@ -263,6 +264,14 @@ public:
|
||||
return mBackend;
|
||||
}
|
||||
|
||||
bool isUboBatchingEnabled() const noexcept {
|
||||
return mUboManager != nullptr;
|
||||
}
|
||||
|
||||
UboManager* getUboManager() noexcept {
|
||||
return mUboManager;
|
||||
}
|
||||
|
||||
Platform* getPlatform() const noexcept {
|
||||
return mPlatform;
|
||||
}
|
||||
@@ -336,11 +345,21 @@ public:
|
||||
|
||||
FRenderer* createRenderer() noexcept;
|
||||
|
||||
// Defines whether a material instance should use UBO batching or not.
|
||||
enum class UboBatchingMode {
|
||||
// For default, it follows the engine settings.
|
||||
// If UBO batching is enabled on the engine and the material domain is not SURFACE, it
|
||||
// turns on the UBO batching. Otherwise, it turns off the UBO batching.
|
||||
DEFAULT,
|
||||
NO_UBO_BATCHING,
|
||||
UBO_BATCHING
|
||||
};
|
||||
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material,
|
||||
const FMaterialInstance* other, const char* name) noexcept;
|
||||
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material,
|
||||
const char* name) noexcept;
|
||||
FMaterialInstance* createMaterialInstance(const FMaterial* material, const char* name,
|
||||
UboBatchingMode batchingMode) noexcept;
|
||||
|
||||
FScene* createScene() noexcept;
|
||||
FView* createView() noexcept;
|
||||
@@ -446,6 +465,7 @@ public:
|
||||
|
||||
void prepare();
|
||||
void gc();
|
||||
void submitFrame();
|
||||
|
||||
using ShaderContent = utils::FixedCapacityVector<uint8_t>;
|
||||
|
||||
@@ -650,6 +670,7 @@ private:
|
||||
|
||||
uint32_t mFlushCounter = 0;
|
||||
|
||||
UboManager* mUboManager = nullptr;
|
||||
RootArenaScope::Arena mPerRenderPassArena;
|
||||
HeapAllocatorArena mHeapAllocator;
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace filament {
|
||||
using namespace backend;
|
||||
using namespace filaflat;
|
||||
using namespace utils;
|
||||
using UboBatchingMode = FEngine::UboBatchingMode;
|
||||
|
||||
struct Material::BuilderDetails {
|
||||
const void* mPayload = nullptr;
|
||||
@@ -217,16 +218,17 @@ void FMaterial::terminate(FEngine& engine) {
|
||||
|
||||
filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
|
||||
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
|
||||
if (Variant::isValidDepthVariant(variant)) {
|
||||
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
|
||||
return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
|
||||
}
|
||||
if (Variant::isSSRVariant(variant)) {
|
||||
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
|
||||
return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
|
||||
if (mDefinition.materialDomain == MaterialDomain::SURFACE) {
|
||||
// `variant` is only sensical for MaterialDomain::SURFACE
|
||||
if (Variant::isValidDepthVariant(variant)) {
|
||||
return mEngine.getPerViewDescriptorSetLayoutDepthVariant();
|
||||
}
|
||||
if (Variant::isSSRVariant(variant)) {
|
||||
return mEngine.getPerViewDescriptorSetLayoutSsrVariant();
|
||||
}
|
||||
}
|
||||
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
|
||||
if (useVsmDescriptorSetLayout) {
|
||||
assert_invariant(mDefinition.materialDomain == MaterialDomain::SURFACE);
|
||||
return mDefinition.perViewDescriptorSetLayoutVsm;
|
||||
}
|
||||
return mDefinition.perViewDescriptorSetLayout;
|
||||
@@ -280,14 +282,14 @@ FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
|
||||
return FMaterialInstance::duplicate(mDefaultMaterialInstance, name);
|
||||
} else {
|
||||
// but if we don't, just create an instance with all the default parameters
|
||||
return mEngine.createMaterialInstance(this, name);
|
||||
return mEngine.createMaterialInstance(this, name, UboBatchingMode::DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* FMaterial::getDefaultInstance() noexcept {
|
||||
if (UTILS_UNLIKELY(!mDefaultMaterialInstance)) {
|
||||
mDefaultMaterialInstance = mEngine.createMaterialInstance(this,
|
||||
mDefinition.name.c_str());
|
||||
mDefaultMaterialInstance =
|
||||
mEngine.createMaterialInstance(this, mDefinition.name.c_str(), UboBatchingMode::DEFAULT);
|
||||
mDefaultMaterialInstance->setDefaultInstance(true);
|
||||
}
|
||||
return mDefaultMaterialInstance;
|
||||
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
backend::CallbackHandler* handler,
|
||||
utils::Invocable<void(Material*)>&& callback) noexcept;
|
||||
|
||||
// Create an instance of this material
|
||||
// Creates an instance of this material, specifying the batching mode.
|
||||
FMaterialInstance* createInstance(const char* name) const noexcept;
|
||||
|
||||
bool hasParameter(const char* name) const noexcept;
|
||||
|
||||
@@ -58,15 +58,19 @@ using namespace utils;
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
using UboBatchingMode = FEngine::UboBatchingMode;
|
||||
|
||||
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name) noexcept
|
||||
: FMaterialInstance(engine, material, name,
|
||||
engine.features.material.enable_material_instance_uniform_batching) {
|
||||
bool shouldEnableBatching(FEngine& engine, UboBatchingMode batchingMode, MaterialDomain domain) {
|
||||
if (batchingMode != UboBatchingMode::DEFAULT) {
|
||||
return batchingMode == UboBatchingMode::UBO_BATCHING;
|
||||
}
|
||||
|
||||
return engine.isUboBatchingEnabled() && domain == MaterialDomain::SURFACE;
|
||||
}
|
||||
|
||||
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name, bool useUboBatching) noexcept : mMaterial(material),
|
||||
FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
|
||||
UboBatchingMode batchingMode) noexcept
|
||||
: mMaterial(material),
|
||||
mDescriptorSet("MaterialInstance", material->getDescriptorSetLayout()),
|
||||
mCulling(CullingMode::BACK),
|
||||
mShadowCulling(CullingMode::BACK),
|
||||
@@ -76,10 +80,12 @@ FMaterialInstance::FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
mHasScissor(false),
|
||||
mIsDoubleSided(false),
|
||||
mIsDefaultInstance(false),
|
||||
mUseUboBatching(useUboBatching),
|
||||
mUseUboBatching(
|
||||
shouldEnableBatching(engine, batchingMode, material->getMaterialDomain())),
|
||||
mTransparencyMode(TransparencyMode::DEFAULT),
|
||||
mName(name ? CString(name) : material->getName()) {
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mUseUboBatching || engine.isUboBatchingEnabled())
|
||||
<< "UBO batching is not enabled.";
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// even if the material doesn't have any parameters, we allocate a small UBO because it's
|
||||
@@ -153,7 +159,7 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
|
||||
mUseUboBatching(other->mUseUboBatching),
|
||||
mScissorRect(other->mScissorRect),
|
||||
mName(name ? CString(name) : other->mName) {
|
||||
|
||||
assert_invariant(!mUseUboBatching || engine.isUboBatchingEnabled());
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
FMaterial const* const material = other->getMaterial();
|
||||
|
||||
@@ -193,8 +199,8 @@ FMaterialInstance::FMaterialInstance(FEngine& engine,
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* FMaterialInstance::duplicate(
|
||||
FMaterialInstance const* other, const char* name) noexcept {
|
||||
FMaterialInstance* FMaterialInstance::duplicate(FMaterialInstance const* other,
|
||||
const char* name) noexcept {
|
||||
FMaterial const* const material = other->getMaterial();
|
||||
FEngine& engine = material->getEngine();
|
||||
return engine.createMaterialInstance(material, other, name);
|
||||
@@ -238,15 +244,21 @@ void FMaterialInstance::commitStreamUniformAssociations(FEngine::DriverApi& driv
|
||||
|
||||
void FMaterialInstance::commit(FEngine& engine) const {
|
||||
if (UTILS_LIKELY(mMaterial->getMaterialDomain() != MaterialDomain::SURFACE)) {
|
||||
commit(engine.getDriverApi());
|
||||
commit(engine.getDriverApi(), engine.getUboManager());
|
||||
}
|
||||
}
|
||||
|
||||
void FMaterialInstance::commit(FEngine::DriverApi& driver) const {
|
||||
void FMaterialInstance::commit(FEngine::DriverApi& driver, UboManager* uboManager) const {
|
||||
if (mUniforms.isDirty() || mHasStreamUniformAssociations) {
|
||||
mUniforms.clean();
|
||||
if (mUseUboBatching) {
|
||||
// TODO: update the content by `copyToMemoryMappedBuffer`
|
||||
assert_invariant(uboManager != nullptr);
|
||||
if (!BufferAllocator::isValid(getAllocationId())) {
|
||||
// The allocation hasn't happened yet, return.
|
||||
return;
|
||||
}
|
||||
|
||||
uboManager->updateSlot(driver, getAllocationId(), mUniforms.toBufferDescriptor(driver));
|
||||
}
|
||||
else {
|
||||
auto* ubHandle = std::get_if<Handle<HwBufferObject>>(&mUboData);
|
||||
@@ -271,6 +283,10 @@ void FMaterialInstance::commit(FEngine::DriverApi& driver) const {
|
||||
// TODO: eventually we should remove this in RELEASE builds
|
||||
fixMissingSamplers();
|
||||
|
||||
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Commit descriptors if needed (e.g. when textures are updated,or the first time)
|
||||
mDescriptorSet.commit(mMaterial->getDescriptorSetLayout(), driver);
|
||||
}
|
||||
@@ -413,6 +429,13 @@ const char* FMaterialInstance::getName() const noexcept {
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
|
||||
if (!mDescriptorSet.getHandle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mUseUboBatching && !BufferAllocator::isValid(getAllocationId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(mMissingSamplerDescriptors.any())) {
|
||||
std::call_once(mMissingSamplersFlag, [this] {
|
||||
@@ -439,7 +462,8 @@ void FMaterialInstance::use(FEngine::DriverApi& driver, Variant variant) const {
|
||||
return;
|
||||
}
|
||||
|
||||
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL);
|
||||
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_MATERIAL,
|
||||
{ { mUboOffset }, driver });
|
||||
}
|
||||
|
||||
void FMaterialInstance::assignUboAllocation(
|
||||
@@ -449,8 +473,10 @@ void FMaterialInstance::assignUboAllocation(
|
||||
assert_invariant(mUseUboBatching);
|
||||
|
||||
mUboData = id;
|
||||
mUboOffset = offset;
|
||||
if (BufferAllocator::isValid(id)) {
|
||||
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, offset,
|
||||
// Use dynamic offset during binding, so the offset here is always zero.
|
||||
mDescriptorSet.setBuffer(mMaterial->getDescriptorSetLayout(), 0, ubHandle, 0,
|
||||
mUniforms.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,8 @@ class FTexture;
|
||||
|
||||
class FMaterialInstance : public MaterialInstance {
|
||||
public:
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name) noexcept;
|
||||
// Use this constructor when you need to override the ubo batching flag for an individual MI.
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material,
|
||||
const char* name, bool useUboBatching) noexcept;
|
||||
FMaterialInstance(FEngine& engine, FMaterial const* material, const char* name,
|
||||
FEngine::UboBatchingMode batchingMode) noexcept;
|
||||
FMaterialInstance(FEngine& engine, FMaterialInstance const* other, const char* name);
|
||||
FMaterialInstance(const FMaterialInstance& rhs) = delete;
|
||||
FMaterialInstance& operator=(const FMaterialInstance& rhs) = delete;
|
||||
@@ -75,7 +72,7 @@ public:
|
||||
|
||||
void commit(FEngine& engine) const;
|
||||
|
||||
void commit(FEngine::DriverApi& driver) const;
|
||||
void commit(FEngine::DriverApi& driver, UboManager* uboManager) const;
|
||||
|
||||
void use(FEngine::DriverApi& driver, Variant variant = {}) const;
|
||||
|
||||
@@ -287,6 +284,7 @@ private:
|
||||
};
|
||||
|
||||
std::variant<BufferAllocator::AllocationId, backend::Handle<backend::HwBufferObject>> mUboData;
|
||||
BufferAllocator::allocation_size_t mUboOffset = 0;
|
||||
tsl::robin_map<backend::descriptor_binding_t, TextureParameter> mTextureParameters;
|
||||
mutable DescriptorSet mDescriptorSet;
|
||||
UniformBuffer mUniforms;
|
||||
@@ -308,7 +306,7 @@ private:
|
||||
bool mHasScissor : 1;
|
||||
bool mIsDoubleSided : 1;
|
||||
bool mIsDefaultInstance : 1;
|
||||
bool mUseUboBatching : 1;
|
||||
const bool mUseUboBatching : 1;
|
||||
TransparencyMode mTransparencyMode : 2;
|
||||
|
||||
uint64_t mMaterialSortingKey = 0;
|
||||
|
||||
@@ -452,6 +452,8 @@ void FRenderer::endFrame() {
|
||||
mFrameInfoManager.endFrame(driver);
|
||||
mFrameSkipper.submitFrame(driver);
|
||||
|
||||
engine.submitFrame();
|
||||
|
||||
driver.endFrame(mFrameId);
|
||||
|
||||
// gives the backend a chance to execute periodic tasks
|
||||
@@ -587,6 +589,8 @@ void FRenderer::renderStandaloneView(FView const* view) {
|
||||
// happen with Renderer::beginFrame/endFrame.
|
||||
renderInternal(view, true);
|
||||
|
||||
engine.submitFrame();
|
||||
|
||||
driver.endFrame(mFrameId);
|
||||
|
||||
// engine.flush() has already been called by renderInternal(), but we need an extra one
|
||||
|
||||
@@ -302,7 +302,7 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const Builder& builder)
|
||||
if (!mBufferObjectsEnabled) {
|
||||
// If buffer objects are not enabled at the API level, then we create them internally.
|
||||
#pragma nounroll
|
||||
for (size_t index = 0; index < MAX_VERTEX_BUFFER_COUNT; ++index) {
|
||||
for (size_t index = 0; index < MAX_VERTEX_ATTRIBUTE_COUNT; ++index) {
|
||||
size_t const i = mAttributes[index].buffer;
|
||||
if (i != Attribute::BUFFER_UNUSED) {
|
||||
assert_invariant(bufferSizes[i] > 0);
|
||||
|
||||
@@ -39,7 +39,7 @@ void PostProcessDescriptorSet::init(FEngine& engine) noexcept {
|
||||
// create the descriptor-set layout
|
||||
mDescriptorSetLayout = filament::DescriptorSetLayout{
|
||||
engine.getDescriptorSetLayoutFactory(),
|
||||
engine.getDriverApi(), descriptor_sets::getPostProcessLayout() };
|
||||
engine.getDriverApi(), descriptor_sets::getDepthVariantLayout() };
|
||||
|
||||
// create the descriptor-set from the layout
|
||||
mDescriptorSet = DescriptorSet{ "PostProcessDescriptorSet", mDescriptorSetLayout };
|
||||
|
||||
@@ -28,9 +28,9 @@
|
||||
|
||||
namespace filament::descriptor_sets {
|
||||
|
||||
backend::DescriptorSetLayout const& getPostProcessLayout() noexcept;
|
||||
backend::DescriptorSetLayout const& getDepthVariantLayout() noexcept;
|
||||
backend::DescriptorSetLayout const& getSsrVariantLayout() noexcept;
|
||||
|
||||
backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept;
|
||||
|
||||
backend::DescriptorSetLayout getPerViewDescriptorSetLayout(
|
||||
|
||||
@@ -40,12 +40,7 @@ namespace filament::descriptor_sets {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
// used for post-processing passes
|
||||
static constexpr std::initializer_list<DescriptorSetLayoutBinding> postProcessDescriptorSetLayoutList = {
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
|
||||
};
|
||||
|
||||
// used to generate shadow-maps
|
||||
// used to generate shadow-maps, structure and postfx passes
|
||||
static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantDescriptorSetLayoutList = {
|
||||
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
|
||||
};
|
||||
@@ -142,10 +137,6 @@ static const std::unordered_map<
|
||||
{{ SamplerType::SAMPLER_EXTERNAL, SamplerFormat::FLOAT }, DescriptorType::SAMPLER_EXTERNAL }
|
||||
};
|
||||
|
||||
// used for post-processing passes
|
||||
static DescriptorSetLayout const postProcessDescriptorSetLayout{ utils::StaticString("postProcess"),
|
||||
postProcessDescriptorSetLayoutList };
|
||||
|
||||
// used to generate shadow-maps
|
||||
static DescriptorSetLayout const depthVariantDescriptorSetLayout{
|
||||
utils::StaticString("depthVariant"), depthVariantDescriptorSetLayoutList
|
||||
@@ -163,10 +154,6 @@ static DescriptorSetLayout perRenderableDescriptorSetLayout = {
|
||||
utils::StaticString("perRenderable"), perRenderableDescriptorSetLayoutList
|
||||
};
|
||||
|
||||
DescriptorSetLayout const& getPostProcessLayout() noexcept {
|
||||
return postProcessDescriptorSetLayout;
|
||||
}
|
||||
|
||||
DescriptorSetLayout const& getDepthVariantLayout() noexcept {
|
||||
return depthVariantDescriptorSetLayout;
|
||||
}
|
||||
@@ -280,10 +267,10 @@ DescriptorSetLayout getPerViewDescriptorSetLayout(
|
||||
return layout;
|
||||
}
|
||||
case MaterialDomain::POST_PROCESS:
|
||||
return postProcessDescriptorSetLayout;
|
||||
return depthVariantDescriptorSetLayout;
|
||||
case MaterialDomain::COMPUTE:
|
||||
// TODO: what's the layout for compute?
|
||||
return postProcessDescriptorSetLayout;
|
||||
return depthVariantDescriptorSetLayout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,9 +129,9 @@ DescriptorSetLayout getPerMaterialDescriptorSet(SamplerInterfaceBlock const& sib
|
||||
DescriptorSetLayout layout;
|
||||
layout.bindings.reserve(1 + samplers.size());
|
||||
|
||||
layout.bindings.push_back(DescriptorSetLayoutBinding { DescriptorType::UNIFORM_BUFFER,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::NONE, 0 });
|
||||
layout.bindings.push_back(DescriptorSetLayoutBinding{ DescriptorType::UNIFORM_BUFFER,
|
||||
ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT,
|
||||
+PerMaterialBindingPoints::MATERIAL_PARAMS, DescriptorFlags::DYNAMIC_OFFSET, 0 });
|
||||
|
||||
for (auto const& sampler: samplers) {
|
||||
DescriptorSetLayoutBinding layoutBinding{
|
||||
|
||||
@@ -225,7 +225,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
|
||||
f.writeUint8(uint8_t(DescriptorType::UNIFORM_BUFFER));
|
||||
f.writeUint8(uint8_t(ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT));
|
||||
f.writeUint8(0);
|
||||
f.writeUint8(uint8_t(DescriptorFlags::NONE));
|
||||
f.writeUint8(uint8_t(DescriptorFlags::DYNAMIC_OFFSET));
|
||||
f.writeUint16(0);
|
||||
|
||||
// all the material's sampler descriptors
|
||||
|
||||
@@ -281,7 +281,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container)
|
||||
}
|
||||
|
||||
text << " "
|
||||
<< setw(alignment) << fieldName.c_str()
|
||||
<< setw(alignment) << fieldName.c_str_safe()
|
||||
<< setw(shortAlignment) << toString(UniformType(fieldType))
|
||||
<< arraySizeToString(fieldSize)
|
||||
<< setw(shortAlignment) << toString(Precision(fieldPrecision))
|
||||
@@ -325,7 +325,7 @@ static bool printParametersInfo(ostream& text, const ChunkContainer& container)
|
||||
}
|
||||
|
||||
text << " "
|
||||
<< setw(alignment) << fieldName.c_str()
|
||||
<< setw(alignment) << fieldName.c_str_safe()
|
||||
<< setw(shortAlignment) << +fieldBinding
|
||||
<< setw(shortAlignment) << toString(SamplerType(fieldType))
|
||||
<< setw(shortAlignment) << toString(Precision(fieldPrecision))
|
||||
@@ -364,7 +364,7 @@ static bool printConstantInfo(ostream& text, const ChunkContainer& container) {
|
||||
}
|
||||
|
||||
text << " "
|
||||
<< setw(alignment) << fieldName.c_str()
|
||||
<< setw(alignment) << fieldName.c_str_safe()
|
||||
<< setw(shortAlignment) << toString(ConstantType(fieldType))
|
||||
<< endl;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user