Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssguserrenderpassmanager_p.h"
#include "graphobjects/qssgrenderuserpass_p.h"
#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(QSSGUserRenderPassManagerLog, "qt.quick3d.runtimerender.userpassmanager")
QSSGUserRenderPassManager::QSSGUserRenderPassManager(Private)
{
}
QSSGUserRenderPassManager::~QSSGUserRenderPassManager()
{
for (auto &rt : m_renderPassRenderTargets)
rt.reset();
const auto textures = m_trackedTextures.keys();
qDeleteAll(textures);
}
void QSSGUserRenderPassManager::setScheduledPasses(const UserPassSet &topLevelPasses)
{
// The scene sync establishes the full set of top-level passes each frame.
// The manager keeps this set ordered by nesting depth and declaration
// order in updateUserPassOrder(), so the caller need not pre-sort it.
m_scheduledUserPasses = topLevelPasses;
m_passlistDirty = true;
}
void QSSGUserRenderPassManager::scheduleUserPass(QSSGRenderUserPass *userPasses)
{
if (userPasses == nullptr)
return;
// Only top-level passes are scheduled. A sub-pass is invoked by its parent
// and renders into the parent's target, so it must never enter the scheduled
// list even if a provider requests its result (QTBUG-148554). This request
// typically comes from a RenderOutputProvider; since a sub-pass produces no
// output of its own, the provider will never get a texture, so warn (once
// per pass; the provider path requests every frame).
if (userPasses->role != QSSGRenderUserPass::Role::TopLevel) {
if (!m_subPassScheduleWarned.contains(userPasses)) {
m_subPassScheduleWarned.insert(userPasses);
qWarning() << "Ignoring request to schedule sub-pass" << userPasses
<< "; a sub-pass renders into its parent's render target and produces no output of its own."
" A RenderOutputProvider must reference a top-level RenderPass.";
}
return;
}
m_subPassScheduleWarned.remove(userPasses);
auto it = std::find(m_scheduledUserPasses.begin(), m_scheduledUserPasses.end(), userPasses);
if (it == m_scheduledUserPasses.end()) {
m_scheduledUserPasses.push_back(userPasses);
m_passlistDirty = true;
}
}
void QSSGUserRenderPassManager::unscheduleUserPass(const QSSGRenderUserPass *userPasses)
{
if (userPasses != nullptr) {
auto it = std::find(m_scheduledUserPasses.cbegin(), m_scheduledUserPasses.cend(), userPasses);
if (it != m_scheduledUserPasses.cend()) {
m_scheduledUserPasses.erase(it);
m_passlistDirty = true;
}
}
}
QSSGRhiRenderableTextureV2Ptr QSSGUserRenderPassManager::getOrCreateRenderableTexture(const QSSGRenderUserPass &userPass)
{
auto it = m_renderPassRenderTargets.find(&userPass);
if (it != m_renderPassRenderTargets.end())
return it.value();
auto iter = m_renderPassRenderTargets.insert(&userPass, std::make_shared<QSSGRhiRenderableTextureV2>(shared_from_this(), QSSGRhiRenderableTextureV2::Private::Initialize));
return iter.value();
}
QSSGRhiRenderableTextureV2Ptr QSSGUserRenderPassManager::getUserPassTexureResult(const QSSGRenderUserPass &userPass) const
{
const auto foundIt = m_renderPassRenderTargets.find(&userPass);
if (foundIt != m_renderPassRenderTargets.end())
return foundIt.value();
return {};
}
void QSSGUserRenderPassManager::updateUserPassOrder(bool forceUpdate)
{
if (m_passlistDirty || forceUpdate) {
// Deeper-nested passes render first ("children before parents"), so a
// pass declared inside its consumer renders before it. Passes at equal
// depth render in declaration order, independent of the order a
// provider scheduled them.
std::sort(m_scheduledUserPasses.begin(), m_scheduledUserPasses.end(), [](const QSSGRenderUserPass *a, const QSSGRenderUserPass *b) {
if (a->m_nestingDepth != b->m_nestingDepth)
return a->m_nestingDepth > b->m_nestingDepth;
return a->m_declarationOrder < b->m_declarationOrder;
});
}
m_passlistDirty = false;
}
bool QSSGUserRenderPassManager::derefTexture(QRhiTexture *texture)
{
auto foundIt = m_trackedTextures.find(texture);
const bool wasFound = foundIt != m_trackedTextures.end();
if (wasFound) {
if (!(foundIt.value() > 1)) {
m_trackedTextures.erase(foundIt);
m_deferredReleaseTextures.insert(texture);
} else {
foundIt.value() -= 1;
}
} else {
qWarning() << "QSSGUserRenderPassManager::derefTexture: Trying to deref a texture that is not tracked.";
}
return wasFound;
}
void QSSGUserRenderPassManager::refTexture(QRhiTexture *texture)
{
auto it = m_trackedTextures.find(texture);
if (it != m_trackedTextures.end())
it.value() += 1;
else
m_trackedTextures.insert(texture, 1);
}
bool QSSGUserRenderPassManager::derefTexture(const std::unique_ptr<QRhiTexture> &texture)
{
return derefTexture(texture.get());
}
void QSSGUserRenderPassManager::refTexture(const std::unique_ptr<QRhiTexture> &texture)
{
refTexture(texture.get());
}
void QSSGUserRenderPassManager::unregisterManagedTexture(QSSGManagedRhiTexture *textureWrapper)
{
QSSG_ASSERT(textureWrapper != nullptr, return);
const auto foundIt = std::find(m_trackedTextureWrappers.cbegin(), m_trackedTextureWrappers.cend(), textureWrapper);
if (foundIt != m_trackedTextureWrappers.cend()) {
auto &texture = textureWrapper->texture();
if (!derefTexture(texture))
qWarning("QSSGSharedRhiTextureWrapper: Texture was not tracked by the manager, possible resource leak.");
m_trackedTextureWrappers.erase(foundIt);
}
}
void QSSGUserRenderPassManager::registerManagedTexture(QSSGManagedRhiTexture *textureWrapper)
{
QSSG_ASSERT(textureWrapper != nullptr, return);
const auto foundIt = std::find(m_trackedTextureWrappers.cbegin(), m_trackedTextureWrappers.cend(), textureWrapper);
if (foundIt == m_trackedTextureWrappers.cend()) {
refTexture(textureWrapper->texture());
m_trackedTextureWrappers.push_back(textureWrapper);
}
}
qsizetype QSSGUserRenderPassManager::acquireUserPassSlot()
{
if (m_userPassSlotsUsed < maxUserPassSlots() - 1)
return qsizetype(m_userPassSlotsUsed++);
qWarning() << "Exceeded maximum number of user render passes(" << QSSGUserRenderPassManager::maxUserPassSlots() << ")";
return -1;
}
void QSSGUserRenderPassManager::resetForFrame()
{
qDeleteAll(m_deferredReleaseTextures.cbegin(), m_deferredReleaseTextures.cend());
m_deferredReleaseTextures.clear();
m_userPassSlotsUsed = 0;
}
void QSSGUserRenderPassManager::releaseAll()
{
m_scheduledUserPasses.clear();
m_subPassScheduleWarned.clear();
// NOTE: We own these so we'll invalidate them, meaning releasing their internal resources.
// Any shared pointers to them held elsewhere will become invalid, as in they will
// get their internal QRhiTexture released and set to nullptr.
for (auto &rt : m_renderPassRenderTargets)
rt->invalidate();
m_renderPassRenderTargets.clear();
{ // We make a copy since invalidate() will call back into us to unregister itself!
auto cpy = m_trackedTextureWrappers;
for (auto &tw : cpy)
tw->invalidate();
cpy.clear();
}
qDeleteAll(m_deferredReleaseTextures.cbegin(), m_deferredReleaseTextures.cend());
m_deferredReleaseTextures.clear();
Q_ASSERT(m_trackedTextureWrappers.size() == 0);
if (m_trackedTextures.size() > 0) {
qWarning() << "QSSGUserRenderPassManager::releaseAll: There are still tracked textures (" << m_trackedTextures.size() << ") when releasing all. This indicates a resource leak.";
for (auto it = m_trackedTextures.constBegin(); it != m_trackedTextures.constEnd(); ++it)
qCDebug(QSSGUserRenderPassManagerLog) << "Texture: " << it.key()->name() << ", has ref count: " << it.value();
}
}
QT_END_NAMESPACE