Revision control
Copy as Markdown
// Copyright (C) 2008-2012 NVIDIA Corporation.
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssglayerrenderdata_p.h"
#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrhicustommaterialsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrhiquadrenderer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrhiparticles_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderskeleton_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderjoint_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendermorphtarget_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderparticles_p.h>
#include "../graphobjects/qssgrenderroot_p.h"
#include "../qssgrendercontextcore.h"
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
#include <QtQuick3DRuntimeRender/private/qssglightmapper_p.h>
#include <QtQuick3DRuntimeRender/private/qssgdebugdrawsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DUtils/private/qssgassert_p.h>
#include <QtQuick/private/qsgtexture_p.h>
#include <QtQuick/private/qsgrenderer_p.h>
#include <QtCore/qspan.h>
#include <array>
#include "qssgrenderpass_p.h"
#include "rendererimpl/qssgrenderhelpers_p.h"
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcQuick3DRender, "qt.quick3d.render");
#define POS4BONETRANS(x) (sizeof(float) * 16 * (x) * 2)
#define POS4BONENORM(x) (sizeof(float) * 16 * ((x) * 2 + 1))
#define BONEDATASIZE4ID(x) POS4BONETRANS(x + 1)
static bool checkParticleSupport(QRhi *rhi)
{
QSSG_ASSERT(rhi, return false);
bool ret = true;
const bool supportRgba32f = rhi->isTextureFormatSupported(QRhiTexture::RGBA32F);
const bool supportRgba16f = rhi->isTextureFormatSupported(QRhiTexture::RGBA16F);
if (!supportRgba32f && !supportRgba16f) {
static bool warningShown = false;
if (!warningShown) {
qWarning () << "Particles not supported due to missing RGBA32F and RGBA16F texture format support";
warningShown = true;
}
ret = false;
}
return ret;
}
struct LayerNodeStatResult
{
qsizetype modelCount = 0;
qsizetype particlesCount = 0;
qsizetype item2DCount = 0;
qsizetype cameraCount = 0;
qsizetype lightCount = 0;
qsizetype reflectionProbeCount = 0;
qsizetype otherCount = 0;
friend LayerNodeStatResult &operator+=(LayerNodeStatResult &lhs, const LayerNodeStatResult &rhs)
{
lhs.modelCount += rhs.modelCount;
lhs.particlesCount += rhs.particlesCount;
lhs.item2DCount += rhs.item2DCount;
lhs.cameraCount += rhs.cameraCount;
lhs.lightCount += rhs.lightCount;
lhs.reflectionProbeCount += rhs.reflectionProbeCount;
lhs.otherCount += rhs.otherCount;
return lhs;
}
friend QDebug operator<<(QDebug dbg, const LayerNodeStatResult &stat)
{
dbg.nospace() << "LayerNodeStatResult(modelCount: " << stat.modelCount
<< ", particlesCount: " << stat.particlesCount
<< ", item2DCount: " << stat.item2DCount
<< ", cameraCount: " << stat.cameraCount
<< ", lightCount: " << stat.lightCount
<< ", reflectionProbeCount: " << stat.reflectionProbeCount
<< ", otherCount: " << stat.otherCount
<< ")";
return dbg.space();
}
};
static LayerNodeStatResult statLayerNodes(const QSSGLayerRenderData::LayerNodes &layerNodes, quint32 layerMask) {
LayerNodeStatResult stat;
for (auto *node : layerNodes) {
if (node->getGlobalState(QSSGRenderNode::GlobalState::Active)) {
if (!node->tag.isSet(layerMask))
++stat.otherCount;
else if (node->type == QSSGRenderGraphObject::Type::Model)
++stat.modelCount;
else if (node->type == QSSGRenderGraphObject::Type::Particles)
++stat.particlesCount;
else if (node->type == QSSGRenderGraphObject::Type::Item2D)
++stat.item2DCount;
else if (node->type == QSSGRenderGraphObject::Type::ReflectionProbe)
++stat.reflectionProbeCount;
else if (QSSGRenderGraphObject::isCamera(node->type))
++stat.cameraCount;
else if (QSSGRenderGraphObject::isLight(node->type))
++stat.lightCount;
else
++stat.otherCount;
}
}
return stat;
}
// These are meant to be pixel offsets, so you need to divide them by the width/height
// of the layer respectively.
static const QVector2D s_ProgressiveAAVertexOffsets[QSSGLayerRenderData::MAX_AA_LEVELS] = {
QVector2D(-0.170840f, -0.553840f), // 1x
QVector2D(0.162960f, -0.319340f), // 2x
QVector2D(0.360260f, -0.245840f), // 3x
QVector2D(-0.561340f, -0.149540f), // 4x
QVector2D(0.249460f, 0.453460f), // 5x
QVector2D(-0.336340f, 0.378260f), // 6x
QVector2D(0.340000f, 0.166260f), // 7x
QVector2D(0.235760f, 0.527760f), // 8x
};
static bool isObjectCullable(const QSSGClippingFrustum &clipFrustum, const QSSGRenderableObject &obj)
{
if (obj.isInstanced) {
// when instancing is enabled we can use the shadowmap bounds if they are set, otherwise we disable culling
if (obj.globalBoundsInstancing.isEmpty() || clipFrustum.intersectsWith(obj.globalBoundsInstancing))
return false;
} else if (clipFrustum.intersectsWith(obj.globalBounds)) {
return false;
}
return true;
}
qsizetype QSSGLayerRenderData::frustumCullingInline(const QSSGClippingFrustum &clipFrustum, QSSGRenderableObjectList &renderables)
{
const qint32 end = renderables.size();
qint32 front = 0;
qint32 back = end - 1;
while (front <= back) {
const QSSGRenderableObject &obj = *renderables.at(front).obj;
if (isObjectCullable(clipFrustum, obj))
renderables.swapItemsAt(front, back--);
else
++front;
}
return back + 1;
}
qsizetype QSSGLayerRenderData::filterLayerMaskInline(quint32 layerMask, QSSGRenderableObjectList &renderables)
{
const qint32 end = renderables.size();
qint32 front = 0;
qint32 back = end - 1;
while (front <= back) {
const auto &b = renderables.at(front).tag;
if (layerMask & b.value())
++front;
else
renderables.swapItemsAt(front, back--);
}
return back + 1;
}
[[nodiscard]] constexpr static inline bool nearestToFurthestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
{
return lhs.cameraDistanceSq < rhs.cameraDistanceSq;
}
[[nodiscard]] constexpr static inline bool furthestToNearestCompare(const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) noexcept
{
return lhs.cameraDistanceSq > rhs.cameraDistanceSq;
}
static void collectBoneTransforms(const QSSGLayerRenderData &renderData, QSSGRenderNode *node, QSSGRenderSkeleton *skeletonNode, const QVector<QMatrix4x4> &poses)
{
if (node->type == QSSGRenderGraphObject::Type::Joint) {
QSSGRenderJoint *jointNode = static_cast<QSSGRenderJoint *>(node);
Q_ASSERT(!jointNode->isDirty(QSSGRenderNode::DirtyFlag::GlobalValuesDirty));
QMatrix4x4 globalTrans = renderData.getGlobalTransform(*jointNode);
// if user doesn't give the inverseBindPose, identity matrices are used.
if (poses.size() > jointNode->index)
globalTrans *= poses[jointNode->index];
memcpy(skeletonNode->boneData.data() + POS4BONETRANS(jointNode->index),
reinterpret_cast<const void *>(globalTrans.constData()),
sizeof(float) * 16);
// only upper 3x3 is meaningful
memcpy(skeletonNode->boneData.data() + POS4BONENORM(jointNode->index),
reinterpret_cast<const void *>(QMatrix4x4(globalTrans.normalMatrix()).constData()),
sizeof(float) * 11);
} else {
skeletonNode->containsNonJointNodes = true;
}
for (auto &child : node->children)
collectBoneTransforms(renderData, &child, skeletonNode, poses);
}
static bool hasDirtyNonJointNodes(QSSGRenderNode *node, bool &hasChildJoints)
{
if (!node)
return false;
// we might be non-joint dirty node, but if we do not have child joints we need to return false
// Note! The frontend clears TransformDirty. Use dirty instead.
bool dirtyNonJoint = ((node->type != QSSGRenderGraphObject::Type::Joint)
&& node->isDirty());
// Tell our parent we are joint
if (node->type == QSSGRenderGraphObject::Type::Joint)
hasChildJoints = true;
bool nodeHasChildJoints = false;
for (auto &child : node->children) {
bool ret = hasDirtyNonJointNodes(&child, nodeHasChildJoints);
// return if we have child joints and non-joint dirty nodes, else check other children
hasChildJoints |= nodeHasChildJoints;
if (ret && nodeHasChildJoints)
return true;
}
// return true if we have child joints and we are dirty non-joint
hasChildJoints |= nodeHasChildJoints;
return dirtyNonJoint && nodeHasChildJoints;
}
#define MAX_MORPH_TARGET 8
#define MAX_MORPH_TARGET_INDEX_SUPPORTS_NORMALS 3
#define MAX_MORPH_TARGET_INDEX_SUPPORTS_TANGENTS 1
QSSGDefaultMaterialPreparationResult::QSSGDefaultMaterialPreparationResult(QSSGShaderDefaultMaterialKey inKey)
: firstImage(nullptr), opacity(1.0f), materialKey(inKey), dirty(false)
{
}
QSSGRenderCameraData QSSGLayerRenderData::getCameraDataImpl(const QSSGRenderCamera *camera) const
{
QSSGRenderCameraData ret;
if (camera) {
// Calculate viewProjection and clippingFrustum for Render Camera
QMatrix4x4 viewProjection(Qt::Uninitialized);
const QMatrix4x4 cameraGlobalTransform = getGlobalTransform(*camera);
camera->calculateViewProjectionMatrix(cameraGlobalTransform, viewProjection);
std::optional<QSSGClippingFrustum> clippingFrustum;
const QVector3D camGlobalPos = QSSGRenderNode::getGlobalPos(cameraGlobalTransform);
if (camera->enableFrustumClipping) {
QSSGClipPlane nearPlane;
QMatrix3x3 theUpper33(cameraGlobalTransform.normalMatrix());
QVector3D dir(QSSGUtils::mat33::transform(theUpper33, QVector3D(0, 0, -1)));
dir.normalize();
nearPlane.normal = dir;
QVector3D theGlobalPos = camGlobalPos + camera->clipPlanes.clipNear() * dir;
nearPlane.d = -(QVector3D::dotProduct(dir, theGlobalPos));
// the near plane's bbox edges are calculated in the clipping frustum's
// constructor.
clippingFrustum = QSSGClippingFrustum{viewProjection, nearPlane};
}
ret = { viewProjection, clippingFrustum, camera->getScalingCorrectDirection(cameraGlobalTransform), camGlobalPos };
}
return ret;
}
// Returns the cached data for the active render camera(s) (if any)
const QSSGRenderCameraDataList &QSSGLayerRenderData::getCachedCameraDatas()
{
ensureCachedCameraDatas();
return *renderedCameraData;
}
void QSSGLayerRenderData::ensureCachedCameraDatas()
{
if (renderedCameraData.has_value())
return;
QSSGRenderCameraDataList cameraData;
for (QSSGRenderCamera *cam : std::as_const(renderedCameras))
cameraData.append(getCameraDataImpl(cam));
renderedCameraData = std::move(cameraData);
}
[[nodiscard]] static inline float getCameraDistanceSq(const QSSGRenderableObject &obj,
const QSSGRenderCameraData &camera) noexcept
{
const QVector3D difference = obj.worldCenterPoint - camera.position;
return QVector3D::dotProduct(difference, camera.direction) + obj.depthBiasSq;
}
// Per-frame cache of renderable objects post-sort.
const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedOpaqueRenderableObjects(const QSSGRenderCamera &camera, size_t index, quint32 layerMask)
{
index = index * size_t(index < opaqueObjectStore.size());
auto &sortedOpaqueObjects = sortedOpaqueObjectCache[index][{&camera, layerMask}];
if (!sortedOpaqueObjects.empty())
return sortedOpaqueObjects;
if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest))
sortedOpaqueObjects = std::as_const(opaqueObjectStore)[index];
if (camera.layerMask != layerMask) {
const auto filteredObjects = filterLayerMaskInline(layerMask, sortedOpaqueObjects);
sortedOpaqueObjects.resize(filteredObjects);
}
const auto &clippingFrustum = getCameraRenderData(&camera).clippingFrustum;
if (clippingFrustum.has_value()) { // Frustum culling
const auto visibleObjects = QSSGLayerRenderData::frustumCullingInline(clippingFrustum.value(), sortedOpaqueObjects);
sortedOpaqueObjects.resize(visibleObjects);
}
// Render nearest to furthest objects
std::sort(sortedOpaqueObjects.begin(), sortedOpaqueObjects.end(), nearestToFurthestCompare);
return sortedOpaqueObjects;
}
// If layer depth test is false, this may also contain opaque objects.
const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedTransparentRenderableObjects(const QSSGRenderCamera &camera, size_t index, quint32 layerMask)
{
index = index * size_t(index < transparentObjectStore.size());
auto &sortedTransparentObjects = sortedTransparentObjectCache[index][{&camera, layerMask}];
if (!sortedTransparentObjects.empty())
return sortedTransparentObjects;
sortedTransparentObjects = std::as_const(transparentObjectStore)[index];
if (!layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)) {
const auto &opaqueObjects = std::as_const(opaqueObjectStore)[index];
sortedTransparentObjects.append(opaqueObjects);
}
if (camera.layerMask != layerMask) {
const auto filteredObjects = filterLayerMaskInline(layerMask, sortedTransparentObjects);
sortedTransparentObjects.resize(filteredObjects);
}
const auto &clippingFrustum = getCameraRenderData(&camera).clippingFrustum;
if (clippingFrustum.has_value()) { // Frustum culling
const auto visibleObjects = QSSGLayerRenderData::frustumCullingInline(clippingFrustum.value(), sortedTransparentObjects);
sortedTransparentObjects.resize(visibleObjects);
}
// render furthest to nearest.
std::sort(sortedTransparentObjects.begin(), sortedTransparentObjects.end(), furthestToNearestCompare);
return sortedTransparentObjects;
}
const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderData::getSortedScreenTextureRenderableObjects(const QSSGRenderCamera &camera, size_t index)
{
index = index * size_t(index < screenTextureObjectStore.size());
const auto &screenTextureObjects = std::as_const(screenTextureObjectStore)[index];
auto &renderedScreenTextureObjects = sortedScreenTextureObjectCache[index][{&camera, camera.layerMask}];
if (!renderedScreenTextureObjects.empty())
return renderedScreenTextureObjects;
renderedScreenTextureObjects = screenTextureObjects;
if (!renderedScreenTextureObjects.empty()) {
// render furthest to nearest.
std::sort(renderedScreenTextureObjects.begin(), renderedScreenTextureObjects.end(), furthestToNearestCompare);
}
return renderedScreenTextureObjects;
}
const QVector<QSSGBakedLightingModel> &QSSGLayerRenderData::getSortedBakedLightingModels()
{
if (!renderedBakedLightingModels.empty() || renderedCameras.isEmpty())
return renderedBakedLightingModels;
if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest) && !bakedLightingModels.empty()) {
renderedBakedLightingModels = bakedLightingModels;
for (QSSGBakedLightingModel &lm : renderedBakedLightingModels) {
// sort nearest to furthest (front to back)
std::sort(lm.renderables.begin(), lm.renderables.end(), nearestToFurthestCompare);
}
}
return renderedBakedLightingModels;
}
const QSSGLayerRenderData::RenderableItem2DEntries &QSSGLayerRenderData::getRenderableItem2Ds()
{
if (!renderedItem2Ds.isEmpty() || renderedCameras.isEmpty())
return renderedItem2Ds;
// Maintain QML item order
renderedItem2Ds = { std::make_reverse_iterator(item2DsView.end()),
std::make_reverse_iterator(item2DsView.begin()) };
if (!renderedItem2Ds.isEmpty()) {
const QSSGRenderCameraDataList &cameraDatas(getCachedCameraDatas());
// with multiview this means using the left eye camera
const QSSGRenderCameraData &cameraDirectionAndPosition(cameraDatas[0]);
const QVector3D &cameraDirection = cameraDirectionAndPosition.direction;
const QVector3D &cameraPosition = cameraDirectionAndPosition.position;
const auto isItemNodeDistanceGreatThan = [this, cameraDirection, cameraPosition]
(const QSSGRenderItem2D *lhs, const QSSGRenderItem2D *rhs) {
if (!lhs->parent || !rhs->parent)
return false;
auto lhsGlobalTransform = getGlobalTransform(*lhs->parent);
auto rhsGlobalTransform = getGlobalTransform(*rhs->parent);
const QVector3D lhsDifference = QSSGRenderNode::getGlobalPos(lhsGlobalTransform) - cameraPosition;
const float lhsCameraDistanceSq = QVector3D::dotProduct(lhsDifference, cameraDirection);
const QVector3D rhsDifference = QSSGRenderNode::getGlobalPos(rhsGlobalTransform) - cameraPosition;
const float rhsCameraDistanceSq = QVector3D::dotProduct(rhsDifference, cameraDirection);
return lhsCameraDistanceSq > rhsCameraDistanceSq;
};
// Render furthest to nearest items (parent nodes).
std::stable_sort(renderedItem2Ds.begin(), renderedItem2Ds.end(), isItemNodeDistanceGreatThan);
}
return renderedItem2Ds;
}
// Depth Write List
void QSSGLayerRenderData::updateSortedDepthObjectsListImp(const QSSGRenderCamera &camera, size_t index)
{
auto &depthWriteObjects = sortedDepthWriteCache[index][{&camera, camera.layerMask}];
auto &depthPrepassObjects = sortedOpaqueDepthPrepassCache[index][{&camera, camera.layerMask}];
if (!depthWriteObjects.isEmpty() || !depthPrepassObjects.isEmpty())
return;
if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)) {
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Opaque)) != 0) {
const auto &sortedOpaqueObjects = getSortedOpaqueRenderableObjects(camera, index); // front to back
for (const auto &opaqueObject : sortedOpaqueObjects) {
const auto depthMode = opaqueObject.obj->depthWriteMode;
if (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly)
depthWriteObjects.append(opaqueObject);
else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
depthPrepassObjects.append(opaqueObject);
}
}
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Transparent)) != 0) {
const auto &sortedTransparentObjects = getSortedTransparentRenderableObjects(camera, index); // back to front
for (const auto &transparentObject : sortedTransparentObjects) {
const auto depthMode = transparentObject.obj->depthWriteMode;
if (depthMode == QSSGDepthDrawMode::Always)
depthWriteObjects.append(transparentObject);
else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
depthPrepassObjects.append(transparentObject);
}
}
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::ScreenTexture)) != 0) {
const auto &sortedScreenTextureObjects = getSortedScreenTextureRenderableObjects(camera, index); // back to front
for (const auto &screenTextureObject : sortedScreenTextureObjects) {
const auto depthMode = screenTextureObject.obj->depthWriteMode;
if (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly)
depthWriteObjects.append(screenTextureObject);
else if (depthMode == QSSGDepthDrawMode::OpaquePrePass)
depthPrepassObjects.append(screenTextureObject);
}
}
}
}
const std::unique_ptr<QSSGPerFrameAllocator> &QSSGLayerRenderData::perFrameAllocator(QSSGRenderContextInterface &ctx)
{
return ctx.perFrameAllocator();
}
void QSSGLayerRenderData::saveRenderState(const QSSGRenderer &renderer)
{
QSSG_CHECK(!savedRenderState.has_value());
const auto &rhiCtx = renderer.contextInterface()->rhiContext();
savedRenderState = std::make_optional<SavedRenderState>({ renderer.m_viewport,
renderer.m_scissorRect,
renderer.m_dpr,
rhiCtx->mainRenderPassDescriptor(),
rhiCtx->renderTarget(),
rhiCtx->commandBuffer(),
rhiCtx->mainPassSampleCount(),
rhiCtx->mainPassViewCount() });
}
void QSSGLayerRenderData::restoreRenderState(QSSGRenderer &renderer)
{
QSSG_ASSERT(savedRenderState.has_value(), return);
renderer.m_viewport = savedRenderState->viewport;
renderer.m_scissorRect = savedRenderState->scissorRect;
renderer.m_dpr = savedRenderState->dpr;
const auto &rhiCtx = renderer.contextInterface()->rhiContext();
auto *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
rhiCtxD->setMainRenderPassDescriptor(savedRenderState->mainRenderPassDescriptor);
rhiCtxD->setRenderTarget(savedRenderState->renderTarget);
rhiCtxD->setCommandBuffer(savedRenderState->commandBuffer);
rhiCtxD->setMainPassSampleCount(savedRenderState->mainPassSampleCount);
rhiCtxD->setMainPassViewCount(savedRenderState->mainPassViewCount);
savedRenderState.reset();
}
static constexpr quint16 PREP_CTX_INDEX_MASK = 0xffff;
static constexpr QSSGPrepContextId createPrepId(size_t index, quint32 frame) { return QSSGPrepContextId { ((quint64(frame) << 32) | index ) * quint64(index <= std::numeric_limits<quint16>::max()) }; }
static constexpr size_t getPrepContextIndex(QSSGPrepContextId id) { return (static_cast<quint64>(id) & PREP_CTX_INDEX_MASK); }
static constexpr bool verifyPrepContext(QSSGPrepContextId id, const QSSGRenderer &renderer) { return (getPrepContextIndex(id) > 0) && ((static_cast<quint64>(id) >> 32) == renderer.frameCount()); }
QSSGPrepContextId QSSGLayerRenderData::getOrCreateExtensionContext(const QSSGRenderExtension &ext, QSSGRenderCamera *camera, quint32 slot)
{
const auto frame = renderer->frameCount();
const auto index = extContexts.size();
// Sanity check... Shouldn't get anywhere close to the max in real world usage (unless somethings broken).
QSSG_ASSERT_X(index < PREP_CTX_INDEX_MASK - 1, "Reached maximum entries!", return QSSGPrepContextId::Invalid);
auto it = std::find_if(extContexts.cbegin(), extContexts.cend(), [&ext, slot](const ExtensionContext &e){ return (e.owner == &ext) && (e.slot == slot); });
if (it == extContexts.cend()) {
extContexts.push_back(ExtensionContext{ ext, camera, index, slot });
it = extContexts.cbegin() + index;
renderableModelStore.emplace_back();
modelContextStore.emplace_back();
renderableObjectStore.emplace_back();
screenTextureObjectStore.emplace_back();
opaqueObjectStore.emplace_back();
transparentObjectStore.emplace_back();
sortedOpaqueObjectCache.emplace_back();
sortedTransparentObjectCache.emplace_back();
sortedScreenTextureObjectCache.emplace_back();
sortedOpaqueDepthPrepassCache.emplace_back();
sortedDepthWriteCache.emplace_back();
QSSG_ASSERT(renderableModelStore.size() == extContexts.size(), renderableModelStore.resize(extContexts.size()));
QSSG_ASSERT(modelContextStore.size() == extContexts.size(), modelContextStore.resize(extContexts.size()));
QSSG_ASSERT(renderableObjectStore.size() == extContexts.size(), renderableObjectStore.resize(extContexts.size()));
QSSG_ASSERT(screenTextureObjectStore.size() == extContexts.size(), screenTextureObjectStore.resize(extContexts.size()));
QSSG_ASSERT(opaqueObjectStore.size() == extContexts.size(), opaqueObjectStore.resize(extContexts.size()));
QSSG_ASSERT(transparentObjectStore.size() == extContexts.size(), transparentObjectStore.resize(extContexts.size()));
QSSG_ASSERT(sortedOpaqueObjectCache.size() == extContexts.size(), sortedOpaqueObjectCache.resize(extContexts.size()));
QSSG_ASSERT(sortedTransparentObjectCache.size() == extContexts.size(), sortedTransparentObjectCache.resize(extContexts.size()));
QSSG_ASSERT(sortedScreenTextureObjectCache.size() == extContexts.size(), sortedScreenTextureObjectCache.resize(extContexts.size()));
QSSG_ASSERT(sortedOpaqueDepthPrepassCache.size() == extContexts.size(), sortedOpaqueDepthPrepassCache.resize(extContexts.size()));
QSSG_ASSERT(sortedDepthWriteCache.size() == extContexts.size(), sortedDepthWriteCache.resize(extContexts.size()));
}
return createPrepId(it->index, frame);
}
static void createRenderablesHelper(QSSGLayerRenderData &layer, const QSSGRenderNode::ChildList &children, QSSGLayerRenderData::RenderableNodeEntries &renderables, QSSGRenderHelpers::CreateFlags createFlags)
{
const bool steal = ((createFlags & QSSGRenderHelpers::CreateFlag::Steal) != 0);
for (auto &chld : children) {
if (chld.type == QSSGRenderGraphObject::Type::Model) {
const auto &renderModel = static_cast<const QSSGRenderModel &>(chld);
auto &renderableModels = layer.renderableModels;
if (auto it = std::find_if(renderableModels.cbegin(), renderableModels.cend(), [&renderModel](const QSSGRenderableNodeEntry &e) { return (e.node == &renderModel); }); it != renderableModels.cend()) {
renderables.emplace_back(*it);
if (steal)
renderableModels.erase(it);
}
}
createRenderablesHelper(layer, chld.children, renderables, createFlags);
}
}
QSSGRenderablesId QSSGLayerRenderData::createRenderables(QSSGPrepContextId prepId, const QSSGNodeIdList &nodes, QSSGRenderHelpers::CreateFlags createFlags)
{
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT(index < renderableModelStore.size(), return {});
auto &renderables = renderableModelStore[index];
if (renderables.size() != 0) {
qWarning() << "Renderables already created for this context - Previous renderables will be overwritten";
renderables.clear();
}
renderables.reserve(nodes.size());
// We now create the renderable node entries for all the models.
// NOTE: The nodes are not complete at this point...
const bool steal = ((createFlags & QSSGRenderHelpers::CreateFlag::Steal) != 0);
for (const auto &nodeId : nodes) {
auto *node = QSSGRenderGraphObjectUtils::getNode<QSSGRenderNode>(nodeId);
if (node && node->type == QSSGRenderGraphObject::Type::Model) {
auto *renderModel = static_cast<QSSGRenderModel *>(node);
// NOTE: Not ideal.
if (auto it = std::find_if(renderableModels.cbegin(), renderableModels.cend(), [renderModel](const QSSGRenderableNodeEntry &e) { return (e.node == renderModel); }); it != renderableModels.cend()) {
auto &inserted = renderables.emplace_back(*it);
inserted.overridden = {};
if (steal)
it->overridden |= QSSGRenderableNodeEntry::Overridden::Disabled;
} else {
renderables.emplace_back(*renderModel);
}
}
if (node && ((createFlags & QSSGRenderHelpers::CreateFlag::Recurse) != 0)) {
const auto &children = node->children;
createRenderablesHelper(*this, children, renderables, createFlags);
}
}
// NOTE: Modifying the renderables list isn't ideal and should be done differently
// but for now this is the easiest way to get the job done.
// We still need to let the layer know it should reset the list once a new
// frame starts.
renderablesModifiedByExtension = true;
return (renderables.size() != 0) ? static_cast<QSSGRenderablesId>(prepId) : QSSGRenderablesId{ QSSGRenderablesId::Invalid };
}
void QSSGLayerRenderData::setGlobalTransform(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, const QMatrix4x4 &globalTransform)
{
const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderables id", return);
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return);
auto &renderables = renderableModelStore[index];
auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
if (it != renderables.cend()) {
it->extOverrides.globalTransform = globalTransform;
it->overridden |= QSSGRenderableNodeEntry::Overridden::GlobalTransform;
}
}
QMatrix4x4 QSSGLayerRenderData::getGlobalTransform(QSSGPrepContextId prepId, const QSSGRenderModel &model)
{
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return {});
QMatrix4x4 ret = getGlobalTransform(model);
auto &renderables = renderableModelStore[index];
auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
if (it != renderables.cend() && (it->overridden & QSSGRenderableNodeEntry::Overridden::GlobalTransform))
ret = it->extOverrides.globalTransform;
return ret;
}
void QSSGLayerRenderData::setGlobalOpacity(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, float opacity)
{
const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderables id", return);
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return);
auto &renderables = renderableModelStore[index];
auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
if (it != renderables.cend()) {
it->extOverrides.globalOpacity = opacity;
it->overridden |= QSSGRenderableNodeEntry::Overridden::GlobalOpacity;
}
}
float QSSGLayerRenderData::getGlobalOpacity(QSSGPrepContextId prepId, const QSSGRenderModel &model)
{
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid prep id", return {});
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT_X(index < renderableModelStore.size(), "Missing call to createRenderables()?", return {});
float ret = getGlobalOpacity(model);
auto &renderables = renderableModelStore[index];
auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
if (it != renderables.cend() && (it->overridden & QSSGRenderableNodeEntry::Overridden::GlobalOpacity))
ret = it->extOverrides.globalOpacity;
return ret;
}
void QSSGLayerRenderData::setModelMaterials(QSSGRenderablesId renderablesId, const QSSGRenderModel &model, const QList<QSSGResourceId> &materials)
{
const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderable id", return);
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT(index < renderableModelStore.size(), return);
// Sanity check
if (materials.size() > 0 && !QSSG_GUARD(QSSGRenderGraphObject::isMaterial(QSSGRenderGraphObjectUtils::getResource(materials.at(0))->type)))
return;
auto &renderables = renderableModelStore[index];
auto it = std::find_if(renderables.cbegin(), renderables.cend(), [&model](const QSSGRenderableNodeEntry &e) { return e.node == &model; });
if (it != renderables.cend()) {
it->extOverrides.materials.resize(materials.size());
std::memcpy(it->extOverrides.materials.data(), materials.data(), it->extOverrides.materials.size() * sizeof(QSSGRenderGraphObject *));
it->overridden |= QSSGRenderableNodeEntry::Overridden::Materials;
}
}
void QSSGLayerRenderData::setModelMaterials(const QSSGRenderablesId renderablesId, const QList<QSSGResourceId> &materials)
{
const auto prepId = static_cast<QSSGPrepContextId>(renderablesId);
QSSG_ASSERT_X(verifyPrepContext(prepId, *renderer), "Expired or invalid renderablesId or renderables id", return);
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT(index < renderableModelStore.size(), return);
auto &renderables = renderableModelStore[index];
for (auto &renderable : renderables) {
auto &renderableMaterials = renderable.extOverrides.materials;
renderableMaterials.resize(materials.size());
std::memcpy(renderableMaterials.data(), materials.data(), renderableMaterials.size() * sizeof(QSSGRenderGraphObject *));
renderable.overridden |= QSSGRenderableNodeEntry::Overridden::Materials;
}
}
QSSGPrepResultId QSSGLayerRenderData::prepareModelsForRender(QSSGRenderContextInterface &contextInterface,
QSSGPrepContextId prepId,
QSSGRenderablesId renderablesId,
float lodThreshold)
{
QSSG_ASSERT_X(renderablesId != QSSGRenderablesId::Invalid && verifyPrepContext(prepId, *renderer),
"Expired or invalid prep or renderables id", return QSSGPrepResultId::Invalid);
const size_t index = getPrepContextIndex(prepId);
QSSG_ASSERT(index < renderableModelStore.size(), return {});
const auto &extContext = extContexts.at(index);
QSSG_ASSERT_X(extContext.camera != nullptr, "No camera set!", return {});
const auto vp = contextInterface.renderer()->viewport();
const float dpr = contextInterface.renderer()->dpr();
const float ssaaMultiplier = layer.isSsaaEnabled() ? layer.ssaaMultiplier : 1.0f;
QSSGRenderCamera::calculateProjectionInternal(*extContext.camera, vp, { dpr, ssaaMultiplier } );
auto &renderables = renderableModelStore[index];
static const auto prepareModelMaterials = [](const RenderableNodeEntries &renderables) {
for (auto &renderable : renderables) {
if ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Materials) == 0
&& (renderable.overridden & QSSGRenderableNodeEntry::Overridden::Disabled) == 0) {
renderable.extOverrides.materials = static_cast<QSSGRenderModel *>(renderable.node)->materials;
}
}
};
prepareModelMaterials(renderables);
// ### multiview
QSSGRenderCameraList camera({ extContext.camera });
QSSGRenderCameraDataList cameraData({ getCameraRenderData(extContext.camera) });
auto &modelContexts = modelContextStore[index];
QSSG_ASSERT(modelContexts.isEmpty(), modelContexts.clear());
auto &renderableObjects = renderableObjectStore[index];
QSSG_ASSERT(renderableObjects.isEmpty(), renderableObjects.clear());
auto &opaqueObjects = opaqueObjectStore[index];
QSSG_ASSERT(opaqueObjects.isEmpty(), opaqueObjects.clear());
auto &transparentObjects = transparentObjectStore[index];
QSSG_ASSERT(transparentObjects.isEmpty(), transparentObjects.clear());
auto &screenTextureObjects = screenTextureObjectStore[index];
QSSG_ASSERT(screenTextureObjects.isEmpty(), screenTextureObjects.clear());
bool wasDirty = prepareModelsForRender(contextInterface,
renderables,
layerPrepResult.flags,
camera,
cameraData,
modelContexts,
opaqueObjects,
transparentObjects,
screenTextureObjects,
lodThreshold);
(void)wasDirty;
return static_cast<QSSGPrepResultId>(prepId);
}
static constexpr size_t pipelineStateIndex(QSSGRenderablesFilter filter)
{
switch (filter) {
case QSSGRenderablesFilter::All:
return 0;
case QSSGRenderablesFilter::Opaque:
return 1;
case QSSGRenderablesFilter::Transparent:
return 2;
}
// GCC 8.x does not treat __builtin_unreachable() as constexpr
# if !defined(Q_CC_GNU_ONLY) || (Q_CC_GNU >= 900)
// NOLINTNEXTLINE(qt-use-unreachable-return): Triggers on Clang, breaking GCC 8
Q_UNREACHABLE();
# endif
return 0;
}
void QSSGLayerRenderData::prepareRenderables(QSSGRenderContextInterface &ctx,
QSSGPrepResultId prepId,
QRhiRenderPassDescriptor *renderPassDescriptor,
const QSSGRhiGraphicsPipelineState &ps,
QSSGRenderablesFilters filter)
{
QSSG_ASSERT_X(verifyPrepContext(static_cast<QSSGPrepContextId>(prepId), *renderer), "Expired or invalid result id", return);
const size_t index = getPrepContextIndex(static_cast<QSSGPrepContextId>(prepId));
QSSG_ASSERT(index < renderableObjectStore.size() && index < extContexts.size(), return);
auto &extCtx = extContexts[index];
QSSG_ASSERT(extCtx.camera, return);
extCtx.filter |= filter;
QSSGShaderFeatures featureSet = getShaderFeatures();
QSSGPassKey passKey { reinterpret_cast<void *>(quintptr(extCtx.owner) ^ extCtx.slot) }; // TODO: Pass this along
if ((filter & QSSGRenderablesFilter::Opaque) != 0) {
auto psCpy = ps;
if (filter == QSSGRenderablesFilter::All) { // If 'All' we set our defaults
psCpy.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
}
const auto &sortedRenderables = getSortedOpaqueRenderableObjects(*extCtx.camera, index);
OpaquePass::prep(ctx, *this, passKey, psCpy, featureSet, renderPassDescriptor, sortedRenderables);
const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Opaque);
extCtx.ps[psIndex] = psCpy;
}
if ((filter & QSSGRenderablesFilter::Transparent) != 0) {
auto psCpy = ps;
if (filter == QSSGRenderablesFilter::All) { // If 'All' we set our defaults
// transparent objects (or, without LayerEnableDepthTest, all objects)
psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, true);
psCpy.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, false);
}
const auto &sortedRenderables = getSortedTransparentRenderableObjects(*extCtx.camera, index);
TransparentPass::prep(ctx, *this, passKey, psCpy, featureSet, renderPassDescriptor, sortedRenderables);
const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Transparent);
extCtx.ps[psIndex] = psCpy;
}
}
void QSSGLayerRenderData::renderRenderables(QSSGRenderContextInterface &ctx, QSSGPrepResultId prepId)
{
QSSG_ASSERT_X(verifyPrepContext(static_cast<QSSGPrepContextId>(prepId), *renderer), "Expired or invalid result id", return);
const size_t index = getPrepContextIndex(static_cast<QSSGPrepContextId>(prepId));
QSSG_ASSERT(index < renderableObjectStore.size() && index < extContexts.size(), return);
const auto &extCtx = extContexts.at(index);
const auto filter = extCtx.filter;
if ((filter & QSSGRenderablesFilter::Opaque) != 0) {
const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Opaque);
const auto &ps = extCtx.ps[psIndex];
const auto &sortedRenderables = getSortedOpaqueRenderableObjects(*extCtx.camera, index);
OpaquePass::render(ctx, ps, sortedRenderables);
}
if ((filter & QSSGRenderablesFilter::Transparent) != 0) {
const size_t psIndex = pipelineStateIndex(QSSGRenderablesFilter::Transparent);
const auto &ps = extCtx.ps[psIndex];
const auto &sortedRenderables = getSortedTransparentRenderableObjects(*extCtx.camera, index);
TransparentPass::render(ctx, ps, sortedRenderables);
}
}
const QSSGRenderableObjectList &QSSGLayerRenderData::getSortedRenderedDepthWriteObjects(const QSSGRenderCamera &camera, size_t index)
{
updateSortedDepthObjectsListImp(camera, index);
return sortedDepthWriteCache[index][{&camera, camera.layerMask}];
}
const QSSGRenderableObjectList &QSSGLayerRenderData::getSortedrenderedOpaqueDepthPrepassObjects(const QSSGRenderCamera &camera, size_t index)
{
updateSortedDepthObjectsListImp(camera, index);
return sortedOpaqueDepthPrepassCache[index][{&camera, camera.layerMask}];
}
void QSSGLayerRenderData::getShadowCastingObjects(const QSSGRenderCamera &camera,
QSSGRenderableObjectList &outObjects,
QSSGBounds3 &outBoundsCasting,
QSSGBounds3 &outBoundsReceiving)
{
constexpr int index = 0;
const QSSGRenderableObjectList transparentObjects = std::as_const(transparentObjectStore)[index];
const QSSGRenderableObjectList opaqueObjects = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)
? std::as_const(opaqueObjectStore)[index]
: QSSGRenderableObjectList();
const QSSGRenderableObjectList screenTextureObjects = std::as_const(screenTextureObjectStore)[index];
outObjects.clear();
outObjects.reserve(transparentObjects.size() + opaqueObjects.size() + screenTextureObjects.size());
if (layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest)) {
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Opaque)) != 0) {
for (const auto &opaqueObject : std::as_const(opaqueObjects)) {
const auto depthMode = opaqueObject.obj->depthWriteMode;
if (opaqueObject.obj->renderableFlags.castsShadows() && opaqueObject.tag.value() & camera.layerMask
&& (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly
|| depthMode == QSSGDepthDrawMode::OpaquePrePass))
outObjects.push_back(opaqueObject);
}
}
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::Transparent)) != 0) {
for (const auto &transparentObject : std::as_const(transparentObjects)) {
const auto depthMode = transparentObject.obj->depthWriteMode;
if (transparentObject.obj->renderableFlags.castsShadows() && transparentObject.tag.value() & camera.layerMask
&& (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaquePrePass))
outObjects.push_back(transparentObject);
}
}
if (hasDepthWriteObjects || (depthPrepassObjectsState & DepthPrepassObjectStateT(DepthPrepassObject::ScreenTexture)) != 0) {
for (const auto &screenTextureObject : std::as_const(screenTextureObjects)) {
const auto depthMode = screenTextureObject.obj->depthWriteMode;
if (screenTextureObject.obj->renderableFlags.castsShadows() && screenTextureObject.tag.value() & camera.layerMask
&& (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly
|| depthMode == QSSGDepthDrawMode::OpaquePrePass))
outObjects.push_back(screenTextureObject);
}
}
}
outBoundsCasting = QSSGBounds3();
outBoundsReceiving = QSSGBounds3();
const auto &clippingFrustum = getCameraRenderData(&camera).clippingFrustum;
const bool doCulling = clippingFrustum.has_value();
for (const auto handles : { &opaqueObjects, &transparentObjects, &screenTextureObjects }) {
// Since we may have nodes that are not a child of the camera parent we go through all
// the opaque objects and include them in the bounds. Failing to do this can result in
// too small bounds.
for (const QSSGRenderableObjectHandle &handle : *handles) {
const QSSGRenderableObject &obj = *handle.obj;
if (!(handle.tag.value() & camera.layerMask))
continue;
// We skip objects not casting or receiving shadows since they don't influence or need to be covered by the shadow map
if (obj.renderableFlags.castsShadows()) {
if (!obj.globalBoundsInstancing.isEmpty())
outBoundsCasting.include(obj.globalBoundsInstancing);
else
outBoundsCasting.include(obj.globalBounds);
}
if (obj.renderableFlags.receivesShadows()) {
// We only cull receiving shadow objects from the bounds
if (doCulling && isObjectCullable(*clippingFrustum, obj))
; // skip
else if (!obj.globalBoundsInstancing.isEmpty())
outBoundsReceiving.include(obj.globalBoundsInstancing);
else
outBoundsReceiving.include(obj.globalBounds);
}
}
}
}
/**
* Usage: T *ptr = RENDER_FRAME_NEW<T>(context, arg0, arg1, ...); is equivalent to: T *ptr = new T(arg0, arg1, ...);
* so RENDER_FRAME_NEW() takes the RCI + T's arguments
*/
template <typename T, typename... Args>
[[nodiscard]] inline T *RENDER_FRAME_NEW(QSSGRenderContextInterface &ctx, Args&&... args)
{
static_assert(std::is_trivially_destructible_v<T>, "Objects allocated using the per-frame allocator needs to be trivially destructible!");
return new (QSSGLayerRenderData::perFrameAllocator(ctx)->allocate(sizeof(T)))T(std::forward<Args>(args)...);
}
template <typename T>
[[nodiscard]] inline QSSGDataRef<T> RENDER_FRAME_NEW_BUFFER(QSSGRenderContextInterface &ctx, size_t count)
{
static_assert(std::is_trivially_destructible_v<T>, "Objects allocated using the per-frame allocator needs to be trivially destructible!");
const size_t asize = sizeof(T) * count;
return { reinterpret_cast<T *>(QSSGLayerRenderData::perFrameAllocator(ctx)->allocate(asize)), qsizetype(count) };
}
void QSSGLayerRenderData::prepareImageForRender(QSSGRenderImage &inImage,
QSSGRenderableImage::Type inMapType,
QSSGRenderableImage *&ioFirstImage,
QSSGRenderableImage *&ioNextImage,
QSSGRenderableObjectFlags &ioFlags,
QSSGShaderDefaultMaterialKey &inShaderKey,
quint32 inImageIndex,
QSSGRenderDefaultMaterial *inMaterial)
{
QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
const auto &bufferManager = contextInterface.bufferManager();
if (inImage.clearDirty())
ioFlags |= QSSGRenderableObjectFlag::Dirty;
// This is where the QRhiTexture gets created, if not already done. Note
// that the bufferManager is per-QQuickWindow, and so per-render-thread.
// Hence using the same Texture (backed by inImage as the backend node) in
// multiple windows will work by each scene in each window getting its own
// QRhiTexture. And that's why the QSSGRenderImageTexture cannot be a
// member of the QSSGRenderImage. Conceptually this matches what we do for
// models (QSSGRenderModel -> QSSGRenderMesh retrieved from the
// bufferManager in each prepareModelForRender, etc.).
const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(&inImage);
if (texture.m_texture) {
if (texture.m_flags.hasTransparency()
&& (inMapType == QSSGRenderableImage::Type::Diffuse // note: Type::BaseColor is skipped here intentionally
|| inMapType == QSSGRenderableImage::Type::Opacity
|| inMapType == QSSGRenderableImage::Type::Translucency))
{
ioFlags |= QSSGRenderableObjectFlag::HasTransparency;
}
QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, inMapType, inImage, texture);
QSSGShaderKeyImageMap &theKeyProp = defaultMaterialShaderKeyProperties.m_imageMaps[inImageIndex];
theKeyProp.setEnabled(inShaderKey, true);
switch (inImage.m_mappingMode) {
case QSSGRenderImage::MappingModes::Normal:
break;
case QSSGRenderImage::MappingModes::Environment:
theKeyProp.setEnvMap(inShaderKey, true);
break;
case QSSGRenderImage::MappingModes::LightProbe:
theKeyProp.setLightProbe(inShaderKey, true);
break;
}
bool hasA = false;
bool hasG = false;
bool hasB = false;
//### TODO: More formats
switch (texture.m_texture->format()) {
case QRhiTexture::Format::RED_OR_ALPHA8:
hasA = !renderer->contextInterface()->rhiContext()->rhi()->isFeatureSupported(QRhi::RedOrAlpha8IsRed);
break;
case QRhiTexture::Format::R8:
// Leave BGA as false
break;
default:
hasA = true;
hasG = true;
hasB = true;
break;
}
if (inImage.isImageTransformIdentity())
theKeyProp.setIdentityTransform(inShaderKey, true);
if (inImage.m_indexUV == 1)
theKeyProp.setUsesUV1(inShaderKey, true);
if (texture.m_flags.isLinear())
theKeyProp.setLinear(inShaderKey, true);
if (texture.m_flags.isPreMultipliedAlpha())
theKeyProp.setPreMultipliedAlpha(inShaderKey, true);
if (ioFirstImage == nullptr)
ioFirstImage = theImage;
else
ioNextImage->m_nextImage = theImage;
ioNextImage = theImage;
if (inMaterial && inImageIndex >= QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst) {
QSSGRenderDefaultMaterial::TextureChannelMapping value = QSSGRenderDefaultMaterial::R;
const quint32 scIndex = inImageIndex - QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst;
QSSGShaderKeyTextureChannel &channelKey = defaultMaterialShaderKeyProperties.m_textureChannels[scIndex];
switch (inImageIndex) {
case QSSGShaderDefaultMaterialKeyProperties::OpacityMap:
value = inMaterial->opacityChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::RoughnessMap:
value = inMaterial->roughnessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::MetalnessMap:
value = inMaterial->metalnessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::OcclusionMap:
value = inMaterial->occlusionChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap:
value = inMaterial->translucencyChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::HeightMap:
value = inMaterial->heightChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::ClearcoatMap:
value = inMaterial->clearcoatChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::ClearcoatRoughnessMap:
value = inMaterial->clearcoatRoughnessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::SheenRoughnessMap:
value = inMaterial->sheenRoughnessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::IridescenceMap:
value = inMaterial->iridescenceChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::IridescenceThicknessMap:
value = inMaterial->iridescenceThicknessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::TransmissionMap:
value = inMaterial->transmissionChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::ThicknessMap:
value = inMaterial->thicknessChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::BaseColorMap:
value = inMaterial->baseColorChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap:
value = inMaterial->specularAmountChannel;
break;
case QSSGShaderDefaultMaterialKeyProperties::EmissiveMap:
value = inMaterial->emissiveChannel;
break;
default:
break;
}
bool useDefault = false;
switch (value) {
case QSSGRenderDefaultMaterial::TextureChannelMapping::G:
useDefault = !hasG;
break;
case QSSGRenderDefaultMaterial::TextureChannelMapping::B:
useDefault = !hasB;
break;
case QSSGRenderDefaultMaterial::TextureChannelMapping::A:
useDefault = !hasA;
break;
default:
break;
}
if (useDefault)
value = QSSGRenderDefaultMaterial::R; // Always Fallback to Red
channelKey.setTextureChannel(QSSGShaderKeyTextureChannel::TexturChannelBits(value), inShaderKey);
}
}
}
void QSSGLayerRenderData::setVertexInputPresence(const QSSGRenderableObjectFlags &renderableFlags,
QSSGShaderDefaultMaterialKey &key)
{
quint32 vertexAttribs = 0;
if (renderableFlags.hasAttributePosition())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Position;
if (renderableFlags.hasAttributeNormal())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Normal;
if (renderableFlags.hasAttributeTexCoord0())
vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord0;
if (renderableFlags.hasAttributeTexCoord1())
vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoord1;
if (renderableFlags.hasAttributeTexCoordLightmap())
vertexAttribs |= QSSGShaderKeyVertexAttribute::TexCoordLightmap;
if (renderableFlags.hasAttributeTangent())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Tangent;
if (renderableFlags.hasAttributeBinormal())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Binormal;
if (renderableFlags.hasAttributeColor())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Color;
if (renderableFlags.hasAttributeJointAndWeight())
vertexAttribs |= QSSGShaderKeyVertexAttribute::JointAndWeight;
defaultMaterialShaderKeyProperties.m_vertexAttributes.setValue(key, vertexAttribs);
}
QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareDefaultMaterialForRender(
QSSGRenderDefaultMaterial &inMaterial,
QSSGRenderableObjectFlags &inExistingFlags,
float inOpacity, bool hasAnyLights,
bool anyLightHasShadows,
QSSGLayerRenderPreparationResultFlags &ioFlags)
{
QSSGRenderDefaultMaterial *theMaterial = &inMaterial;
QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
retval.renderableFlags = inExistingFlags;
QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
retval.opacity = inOpacity;
float &subsetOpacity(retval.opacity);
if (theMaterial->isDirty())
renderableFlags |= QSSGRenderableObjectFlag::Dirty;
subsetOpacity *= theMaterial->opacity;
QSSGRenderableImage *firstImage = nullptr;
const bool lighting = theMaterial->lighting != QSSGRenderDefaultMaterial::MaterialLighting::NoLighting;
defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, lighting);
if (lighting) {
defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
const bool validSky = layer.skyMaterial != nullptr && layer.skyMaterial->enableIBL;
defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr || validSky);
}
defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
// isDoubleSided
defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, theMaterial->cullMode == QSSGCullFaceMode::Disabled);
// default materials never define their on position
defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, false);
// default materials dont make use of raw projection or inverse projection matrices
defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, false);
defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, false);
// nor they do rely on VAR_COLOR
defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, false);
// alpha Mode
defaultMaterialShaderKeyProperties.m_alphaMode.setValue(theGeneratedKey, theMaterial->alphaMode);
// vertex attribute presence flags
setVertexInputPresence(renderableFlags, theGeneratedKey);
// set the flag indicating the need for gl_PointSize
defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
// propagate the flag indicating the presence of a lightmap
defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderDefaultMaterial::Type::PrincipledMaterial);
defaultMaterialShaderKeyProperties.m_specularGlossyEnabled.setValue(theGeneratedKey, theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial);
// debug modes
defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
// fog
defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
// multiview
defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey, layer.viewCount >= 2);
if (!defaultMaterialShaderKeyProperties.m_hasIbl.getValue(theGeneratedKey) && theMaterial->iblProbe) {
features.set(QSSGShaderFeatures::Feature::LightProbe, true);
defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, true);
// features.set(ShaderFeatureDefines::enableIblFov(),
// m_Renderer.GetLayerRenderData()->m_Layer.m_ProbeFov < 180.0f );
}
if (subsetOpacity >= QSSGRendererPrivate::minimumRenderOpacity) {
// Set the semi-transparency flag as specified in PrincipledMaterial's
// blendMode and alphaMode:
// - the default SourceOver blendMode does not imply alpha blending on
// its own,
// - but other blendMode values do,
// - an alphaMode of Blend guarantees blending to be enabled regardless
// of anything else.
// Additionally:
// - Opacity and texture map alpha are handled elsewhere (that's when a
// blendMode of SourceOver or an alphaMode of Default/Opaque can in the
// end still result in HasTransparency),
// - the presence of an opacityMap guarantees alpha blending regardless
// of its content.
if (theMaterial->blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver
|| theMaterial->opacityMap
|| theMaterial->alphaMode == QSSGRenderDefaultMaterial::Blend)
{
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
}
const bool specularEnabled = theMaterial->isSpecularEnabled();
const bool metalnessEnabled = theMaterial->isMetalnessEnabled();
defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, specularEnabled || metalnessEnabled);
defaultMaterialShaderKeyProperties.m_specularModel.setSpecularModel(theGeneratedKey, theMaterial->specularModel);
defaultMaterialShaderKeyProperties.m_diffuseModel.setDiffuseModel(theGeneratedKey, theMaterial->diffuseModel);
defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isFresnelScaleBiasEnabled());
defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatFresnelScaleBiasEnabled());
defaultMaterialShaderKeyProperties.m_fresnelEnabled.setValue(theGeneratedKey, theMaterial->isFresnelEnabled());
defaultMaterialShaderKeyProperties.m_baseColorSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isBaseColorSingleChannelEnabled());
defaultMaterialShaderKeyProperties.m_specularSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isSpecularAmountSingleChannelEnabled());
defaultMaterialShaderKeyProperties.m_emissiveSingleChannelEnabled.setValue(theGeneratedKey, theMaterial->isEmissiveSingleChannelEnabled());
defaultMaterialShaderKeyProperties.m_invertOpacityMapValue.setValue(theGeneratedKey, theMaterial->isInvertOpacityMapValue());
defaultMaterialShaderKeyProperties.m_vertexColorsEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsEnabled());
defaultMaterialShaderKeyProperties.m_vertexColorsMaskEnabled.setValue(theGeneratedKey, theMaterial->isVertexColorsMaskEnabled());
defaultMaterialShaderKeyProperties.m_vertexColorRedMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorRedMask.toInt()));
defaultMaterialShaderKeyProperties.m_vertexColorGreenMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorGreenMask.toInt()));
defaultMaterialShaderKeyProperties.m_vertexColorBlueMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorBlueMask.toInt()));
defaultMaterialShaderKeyProperties.m_vertexColorAlphaMask.setValue(theGeneratedKey, quint16(theMaterial->vertexColorAlphaMask.toInt()));
defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, theMaterial->isClearcoatEnabled());
defaultMaterialShaderKeyProperties.m_sheenEnabled.setValue(theGeneratedKey, theMaterial->isSheenEnabled());
defaultMaterialShaderKeyProperties.m_anisotropyEnabled.setValue(theGeneratedKey, theMaterial->isAnisotropyEnabled());
defaultMaterialShaderKeyProperties.m_iridescenceEnabled.setValue(theGeneratedKey, theMaterial->isIridescenceEnabled());
defaultMaterialShaderKeyProperties.m_dispersionEnabled.setValue(theGeneratedKey, theMaterial->isDispersionEnabled());
defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, theMaterial->isTransmissionEnabled());
// Run through the material's images and prepare them for render.
// this may in fact set pickable on the renderable flags if one of the images
// links to a sub presentation or any offscreen rendered object.
QSSGRenderableImage *nextImage = nullptr;
#define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent) \
if ((img)) \
prepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags, \
theGeneratedKey, shadercomponent, &inMaterial)
if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
theMaterial->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
QSSGRenderableImage::Type::BaseColor,
QSSGShaderDefaultMaterialKeyProperties::BaseColorMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->occlusionMap,
QSSGRenderableImage::Type::Occlusion,
QSSGShaderDefaultMaterialKeyProperties::OcclusionMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->heightMap,
QSSGRenderableImage::Type::Height,
QSSGShaderDefaultMaterialKeyProperties::HeightMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatMap,
QSSGRenderableImage::Type::Clearcoat,
QSSGShaderDefaultMaterialKeyProperties::ClearcoatMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatRoughnessMap,
QSSGRenderableImage::Type::ClearcoatRoughness,
QSSGShaderDefaultMaterialKeyProperties::ClearcoatRoughnessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->clearcoatNormalMap,
QSSGRenderableImage::Type::ClearcoatNormal,
QSSGShaderDefaultMaterialKeyProperties::ClearcoatNormalMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->sheenColorMap,
QSSGRenderableImage::Type::SheenColor,
QSSGShaderDefaultMaterialKeyProperties::SheenColorMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->sheenRoughnessMap,
QSSGRenderableImage::Type::SheenRoughness,
QSSGShaderDefaultMaterialKeyProperties::SheenRoughnessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->anisotropyMap,
QSSGRenderableImage::Type::Anisotropy,
QSSGShaderDefaultMaterialKeyProperties::AnisotropyMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->iridescenceMap,
QSSGRenderableImage::Type::Iridescence,
QSSGShaderDefaultMaterialKeyProperties::IridescenceMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->iridescenceThicknessMap,
QSSGRenderableImage::Type::IridescenceThickness,
QSSGShaderDefaultMaterialKeyProperties::IridescenceThicknessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->transmissionMap,
QSSGRenderableImage::Type::Transmission,
QSSGShaderDefaultMaterialKeyProperties::TransmissionMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->thicknessMap,
QSSGRenderableImage::Type::Thickness,
QSSGShaderDefaultMaterialKeyProperties::ThicknessMap);
if (theMaterial->type == QSSGRenderGraphObject::Type::PrincipledMaterial) {
CHECK_IMAGE_AND_PREPARE(theMaterial->metalnessMap,
QSSGRenderableImage::Type::Metalness,
QSSGShaderDefaultMaterialKeyProperties::MetalnessMap);
}
} else {
CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
QSSGRenderableImage::Type::Diffuse,
QSSGShaderDefaultMaterialKeyProperties::DiffuseMap);
}
CHECK_IMAGE_AND_PREPARE(theMaterial->emissiveMap, QSSGRenderableImage::Type::Emissive, QSSGShaderDefaultMaterialKeyProperties::EmissiveMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->specularReflection,
QSSGRenderableImage::Type::Specular,
QSSGShaderDefaultMaterialKeyProperties::SpecularMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->roughnessMap,
QSSGRenderableImage::Type::Roughness,
QSSGShaderDefaultMaterialKeyProperties::RoughnessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->opacityMap, QSSGRenderableImage::Type::Opacity, QSSGShaderDefaultMaterialKeyProperties::OpacityMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->bumpMap, QSSGRenderableImage::Type::Bump, QSSGShaderDefaultMaterialKeyProperties::BumpMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->specularMap,
QSSGRenderableImage::Type::SpecularAmountMap,
QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->normalMap, QSSGRenderableImage::Type::Normal, QSSGShaderDefaultMaterialKeyProperties::NormalMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->translucencyMap,
QSSGRenderableImage::Type::Translucency,
QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap);
}
#undef CHECK_IMAGE_AND_PREPARE
if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
subsetOpacity = 0.0f;
// You can still pick against completely transparent objects(or rather their bounding
// box)
// you just don't render them.
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
}
if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
subsetOpacity = 1.f;
else
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
if (inMaterial.isTransmissionEnabled()) {
ioFlags.setRequiresScreenTexture(true);
ioFlags.setRequiresMipmapsForScreenTexture(true);
renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
}
if (renderableFlags.hasTransparency()) {
if (orderIndependentTransparencyEnabled)
defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
if (layer.oitMethodDirty)
renderableFlags |= QSSGRenderableObjectFlag::Dirty;
}
retval.firstImage = firstImage;
if (retval.renderableFlags.isDirty())
retval.dirty = true;
if (retval.dirty)
renderer->addMaterialDirtyClear(&inMaterial);
return retval;
}
QSSGDefaultMaterialPreparationResult QSSGLayerRenderData::prepareCustomMaterialForRender(
QSSGRenderCustomMaterial &inMaterial, QSSGRenderableObjectFlags &inExistingFlags,
float inOpacity, bool alreadyDirty, bool hasAnyLights, bool anyLightHasShadows,
QSSGLayerRenderPreparationResultFlags &ioFlags)
{
QSSGDefaultMaterialPreparationResult retval(QSSGShaderDefaultMaterialKey(qHash(features)));
retval.renderableFlags = inExistingFlags;
QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
retval.opacity = inOpacity;
float &subsetOpacity(retval.opacity);
if (subsetOpacity < QSSGRendererPrivate::minimumRenderOpacity) {
subsetOpacity = 0.0f;
// You can still pick against completely transparent objects(or rather their bounding
// box)
// you just don't render them.
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
renderableFlags |= QSSGRenderableObjectFlag::CompletelyTransparent;
}
if (subsetOpacity > 1.f - QSSGRendererPrivate::minimumRenderOpacity)
subsetOpacity = 1.f;
else
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
defaultMaterialShaderKeyProperties.m_hasLighting.setValue(theGeneratedKey, true);
defaultMaterialShaderKeyProperties.m_hasPunctualLights.setValue(theGeneratedKey, hasAnyLights);
defaultMaterialShaderKeyProperties.m_hasShadows.setValue(theGeneratedKey, anyLightHasShadows);
defaultMaterialShaderKeyProperties.m_hasIbl.setValue(theGeneratedKey, layer.lightProbe != nullptr || layer.skyMaterial != nullptr);
defaultMaterialShaderKeyProperties.m_specularEnabled.setValue(theGeneratedKey, true);
defaultMaterialShaderKeyProperties.m_specularAAEnabled.setValue(theGeneratedKey, layer.specularAAEnabled);
// isDoubleSided
defaultMaterialShaderKeyProperties.m_isDoubleSided.setValue(theGeneratedKey, inMaterial.m_cullMode == QSSGCullFaceMode::Disabled);
// Custom Materials are always Metallic Roughness Workflow
defaultMaterialShaderKeyProperties.m_metallicRoughnessEnabled.setValue(theGeneratedKey, true);
// Does the material override the position output
const bool overridesPosition = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::OverridesPosition);
defaultMaterialShaderKeyProperties.m_overridesPosition.setValue(theGeneratedKey, overridesPosition);
// Optional usage of PROJECTION_MATRIX and/or INVERSE_PROJECTION_MATRIX
const bool usesProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ProjectionMatrix);
defaultMaterialShaderKeyProperties.m_usesProjectionMatrix.setValue(theGeneratedKey, usesProjectionMatrix);
const bool usesInvProjectionMatrix = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::InverseProjectionMatrix);
defaultMaterialShaderKeyProperties.m_usesInverseProjectionMatrix.setValue(theGeneratedKey, usesInvProjectionMatrix);
// vertex attribute presence flags
setVertexInputPresence(renderableFlags, theGeneratedKey);
// set the flag indicating the need for gl_PointSize
defaultMaterialShaderKeyProperties.m_usesPointsTopology.setValue(theGeneratedKey, renderableFlags.isPointsTopology());
// propagate the flag indicating the presence of a lightmap
defaultMaterialShaderKeyProperties.m_lightmapEnabled.setValue(theGeneratedKey, renderableFlags.rendersWithLightmap());
// debug modes
defaultMaterialShaderKeyProperties.m_debugMode.setValue(theGeneratedKey, int(layer.debugMode));
// fog
defaultMaterialShaderKeyProperties.m_fogEnabled.setValue(theGeneratedKey, layer.fog.enabled);
// multiview
defaultMaterialShaderKeyProperties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
defaultMaterialShaderKeyProperties.m_usesViewIndex.setValue(theGeneratedKey,
inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ViewIndex));
// Knowing whether VAR_COLOR is used becomes relevant when there is no
// custom vertex shader, but VAR_COLOR is present in the custom fragment
// snippet, because that case needs special care.
const bool usesVarColor = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::VarColor);
defaultMaterialShaderKeyProperties.m_usesVarColor.setValue(theGeneratedKey, usesVarColor);
const bool usesClearcoat = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Clearcoat);
defaultMaterialShaderKeyProperties.m_clearcoatEnabled.setValue(theGeneratedKey, usesClearcoat);
const bool usesSheen = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Sheen);
defaultMaterialShaderKeyProperties.m_sheenEnabled.setValue(theGeneratedKey, usesSheen);
const bool usesAnisotropy = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Anisotropy);
defaultMaterialShaderKeyProperties.m_anisotropyEnabled.setValue(theGeneratedKey, usesAnisotropy);
const bool usesIridescence = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Iridescence);
defaultMaterialShaderKeyProperties.m_iridescenceEnabled.setValue(theGeneratedKey, usesIridescence);
const bool usesDispersion = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Dispersion);
defaultMaterialShaderKeyProperties.m_dispersionEnabled.setValue(theGeneratedKey, usesDispersion);
const bool usesClearcoatFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ClearcoatFresnelScaleBias);
defaultMaterialShaderKeyProperties.m_clearcoatFresnelScaleBiasEnabled.setValue(theGeneratedKey, usesClearcoatFresnelScaleBias);
const bool usesFresnelScaleBias = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::FresnelScaleBias);
defaultMaterialShaderKeyProperties.m_fresnelScaleBiasEnabled.setValue(theGeneratedKey, usesFresnelScaleBias);
const bool usesTransmission = inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Transmission);
defaultMaterialShaderKeyProperties.m_transmissionEnabled.setValue(theGeneratedKey, usesTransmission);
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenTexture)) {
ioFlags.setRequiresScreenTexture(true);
renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
}
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::ScreenMipTexture)) {
ioFlags.setRequiresScreenTexture(true);
ioFlags.setRequiresMipmapsForScreenTexture(true);
renderableFlags |= QSSGRenderableObjectFlag::RequiresScreenTexture;
}
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::DepthTexture))
ioFlags.setRequiresDepthTexture(true);
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::NormalTexture)) {
ioFlags.setRequiresNormalTexture(true);
renderableFlags |= QSSGRenderableObjectFlag::RequiresNormalTexture;
}
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::AoTexture)) {
ioFlags.setRequiresDepthTexture(true);
ioFlags.setRequiresSsaoPass(true);
}
if (orderIndependentTransparencyEnabled && renderableFlags.hasTransparency())
defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
retval.firstImage = nullptr;
if (inMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::MotionVectorTexture))
ioFlags.setRequiresMotionVectorPass(true);
if (retval.dirty || alreadyDirty)
renderer->addMaterialDirtyClear(&inMaterial);
return retval;
}
void QSSGLayerRenderData::setLightmapTexture(const QSSGModelContext &modelContext, QRhiTexture *lightmapTexture)
{
lightmapTextures[&modelContext] = lightmapTexture;
}
QRhiTexture *QSSGLayerRenderData::getLightmapTexture(const QSSGModelContext &modelContext) const
{
QRhiTexture *ret = nullptr;
if (modelContext.model.hasLightmap()) {
const auto it = lightmapTextures.constFind(&modelContext);
ret = (it != lightmapTextures.cend()) ? *it : nullptr;
}
return ret;
}
void QSSGLayerRenderData::setBonemapTexture(const QSSGModelContext &modelContext, QRhiTexture *bonemapTexture)
{
bonemapTextures[&modelContext] = bonemapTexture;
}
QRhiTexture *QSSGLayerRenderData::getBonemapTexture(const QSSGModelContext &modelContext) const
{
QRhiTexture *ret = nullptr;
if (modelContext.model.usesBoneTexture()) {
const auto it = bonemapTextures.constFind(&modelContext);
ret = (it != bonemapTextures.cend()) ? *it : nullptr;
}
return ret;
}
static bool hasCustomBlendMode(const QSSGRenderCustomMaterial &material)
{
// Check SrcOver
// srcAlpha is same for all
if (material.m_srcAlphaBlend != QRhiGraphicsPipeline::One)
return true;
// SrcOver srcColor is SrcAlpha
if (material.m_srcBlend != QRhiGraphicsPipeline::SrcAlpha)
return true;
if (material.m_dstBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha
&& material.m_dstAlphaBlend == QRhiGraphicsPipeline::OneMinusSrcAlpha)
return false;
return true;
}
// inModel is const to emphasize the fact that its members cannot be written
// here: in case there is a scene shared between multiple View3Ds in different
// QQuickWindows, each window may run this in their own render thread, while
// inModel is the same.
bool QSSGLayerRenderData::prepareModelsForRender(QSSGRenderContextInterface &contextInterface,
const RenderableNodeEntries &renderableModels,
QSSGLayerRenderPreparationResultFlags &ioFlags,
const QSSGRenderCameraList &allCameras,
const QSSGRenderCameraDataList &allCameraData,
TModelContextPtrList &modelContexts,
QSSGRenderableObjectList &opaqueObjects,
QSSGRenderableObjectList &transparentObjects,
QSSGRenderableObjectList &screenTextureObjects,
float lodThreshold)
{
const auto &rhiCtx = contextInterface.rhiContext();
const auto &bufferManager = contextInterface.bufferManager();
const auto &debugDrawSystem = contextInterface.debugDrawSystem();
const bool maybeDebugDraw = debugDrawSystem && debugDrawSystem->isEnabled();
bool wasDirty = false;
for (const QSSGRenderableNodeEntry &renderable : renderableModels) {
if ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Disabled) != 0)
continue;
const QSSGRenderModel &model = *static_cast<QSSGRenderModel *>(renderable.node);
const auto &lights = renderable.lights;
QSSGRenderMesh *theMesh = modelData->getMesh(model);
if (!theMesh)
continue;
const bool altGlobalTransform = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalTransform) != 0);
const auto &globalTransform = altGlobalTransform ? renderable.extOverrides.globalTransform : getGlobalTransform(model);
QMatrix3x3 normalMatrix { Qt::Uninitialized };
QSSGLayerRenderData::ModelViewProjections mvps;
if (altGlobalTransform) {
QSSGRenderNode::calculateNormalMatrix(globalTransform, normalMatrix);
size_t mvpCount = 0;
for (const auto &cameraData : allCameraData) {
QSSGRenderNode::calculateMVPAndNormalMatrix(globalTransform, cameraData.viewProjection, mvps[mvpCount++], normalMatrix);
}
} else {
if (model.usesBoneTexture()) {
// FIXME:
// For skinning, node's global transformation will be ignored and
// an identity matrix will be used for the normalMatrix
size_t mvpCount = 0;
for (const QSSGRenderCameraData &cameraData : allCameraData) {
mvps[mvpCount++] = cameraData.viewProjection;
normalMatrix = QMatrix3x3();
}
} else {
normalMatrix = getNormalMatrix(model);
mvps = getModelMvps(model);
}
}
const bool altModelOpacity = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::GlobalOpacity) != 0);
const float modelGlobalOpacity = altModelOpacity ? renderable.extOverrides.globalOpacity : getGlobalOpacity(model);
QSSGModelContext &theModelContext = *RENDER_FRAME_NEW<QSSGModelContext>(contextInterface, model, globalTransform, normalMatrix, mvps);
modelContexts.push_back(&theModelContext);
// We might over-allocate here, as the material list technically can contain an invalid (nullptr) material.
// We'll fix that by adjusting the size at the end for now...
const auto &meshSubsets = theMesh->subsets;
const auto meshSubsetCount = meshSubsets.size();
theModelContext.subsets = RENDER_FRAME_NEW_BUFFER<QSSGSubsetRenderable>(contextInterface, meshSubsetCount);
// Prepare boneTexture for skinning
if (model.skin) {
auto boneTexture = bufferManager->loadSkinmap(model.skin);
setBonemapTexture(theModelContext, boneTexture.m_texture);
} else if (model.skeleton) {
auto boneTexture = bufferManager->loadSkinmap(&(model.skeleton->boneTexData));
setBonemapTexture(theModelContext, boneTexture.m_texture);
}
// many renderableFlags are the same for all the subsets
QSSGRenderableObjectFlags renderableFlagsForModel;
bool usesBlendParticles = particlesEnabled && theModelContext.model.particleBuffer != nullptr
&& model.particleBuffer->particleCount();
if (meshSubsetCount > 0) {
const QSSGRenderSubset &theSubset = meshSubsets.at(0);
renderableFlagsForModel.setMotionVectorParticipant(model.motionVectorEnabled && !usesBlendParticles);
renderableFlagsForModel.setCastsShadows(model.castsShadows);
renderableFlagsForModel.setReceivesShadows(model.receivesShadows);
renderableFlagsForModel.setReceivesReflections(model.receivesReflections);
renderableFlagsForModel.setCastsReflections(model.castsReflections);
renderableFlagsForModel.setUsedInBakedLighting(model.usedInBakedLighting);
if (model.hasLightmap()) {
QSSGRenderImageTexture lmImageTexture = bufferManager->loadLightmap(model);
if (lmImageTexture.m_texture) {
renderableFlagsForModel.setRendersWithLightmap(true);
setLightmapTexture(theModelContext, lmImageTexture.m_texture);
}
}
// TODO: This should be a oneshot thing, move the flags over!
// With the RHI we need to be able to tell the material shader
// generator to not generate vertex input attributes that are not
// provided by the mesh. (because unlike OpenGL, other graphics
// APIs may treat unbound vertex inputs as a fatal error)
bool hasJoint = false;
bool hasWeight = false;
bool hasMorphTarget = theSubset.rhi.targetsTexture != nullptr;
for (const QSSGRhiInputAssemblerState::InputSemantic &sem : std::as_const(theSubset.rhi.ia.inputs)) {
if (sem == QSSGRhiInputAssemblerState::PositionSemantic) {
renderableFlagsForModel.setHasAttributePosition(true);
} else if (sem == QSSGRhiInputAssemblerState::NormalSemantic) {
renderableFlagsForModel.setHasAttributeNormal(true);
} else if (sem == QSSGRhiInputAssemblerState::TexCoord0Semantic) {
renderableFlagsForModel.setHasAttributeTexCoord0(true);
} else if (sem == QSSGRhiInputAssemblerState::TexCoord1Semantic) {
renderableFlagsForModel.setHasAttributeTexCoord1(true);
} else if (sem == QSSGRhiInputAssemblerState::TexCoordLightmapSemantic) {
renderableFlagsForModel.setHasAttributeTexCoordLightmap(true);
} else if (sem == QSSGRhiInputAssemblerState::TangentSemantic) {
renderableFlagsForModel.setHasAttributeTangent(true);
} else if (sem == QSSGRhiInputAssemblerState::BinormalSemantic) {
renderableFlagsForModel.setHasAttributeBinormal(true);
} else if (sem == QSSGRhiInputAssemblerState::ColorSemantic) {
renderableFlagsForModel.setHasAttributeColor(true);
// For skinning, we will set the HasAttribute only
// if the mesh has both joint and weight
} else if (sem == QSSGRhiInputAssemblerState::JointSemantic) {
hasJoint = true;
} else if (sem == QSSGRhiInputAssemblerState::WeightSemantic) {
hasWeight = true;
}
}
renderableFlagsForModel.setHasAttributeJointAndWeight(hasJoint && hasWeight);
renderableFlagsForModel.setHasAttributeMorphTarget(hasMorphTarget);
}
QSSGRenderableObjectList bakedLightingObjects;
const bool anyLightHasShadows = std::find_if(lights.begin(),
lights.end(),
[](const QSSGShaderLight &light) { return light.shadows; })
!= lights.end();
const bool hasAnyLights = !lights.isEmpty();
QSSGRenderLight::SoftShadowQuality maxSoftShadowQuality = QSSGRenderLight::SoftShadowQuality::Hard;
if (anyLightHasShadows) {
// Iterate the light list to find the maximum shadow quality of lights that cast shadows
for (const QSSGShaderLight &light : lights) {
if (light.shadows && light.light->m_softShadowQuality > maxSoftShadowQuality)
maxSoftShadowQuality = light.light->m_softShadowQuality;
}
}
// Subset(s)
auto &renderableSubsets = theModelContext.subsets;
const bool hasMaterialOverrides = ((renderable.overridden & QSSGRenderableNodeEntry::Overridden::Materials) != 0);
const auto &materials = hasMaterialOverrides ? renderable.extOverrides.materials : modelData->getMaterials(model);
const auto materialCount = materials.size();
QSSGRenderGraphObject *lastMaterial = !materials.isEmpty() ? materials.last() : nullptr;
int idx = 0, subsetIdx = 0;
for (; idx < meshSubsetCount; ++idx) {
// If the materials list < size of subsets, then use the last material for the rest
QSSGRenderGraphObject *theMaterialObject = (idx >= materialCount) ? lastMaterial : materials[idx];
if (!theMaterialObject)
continue;
const QSSGRenderSubset &theSubset = meshSubsets.at(idx);
QSSGRenderableObjectFlags renderableFlags = renderableFlagsForModel;
float subsetOpacity = modelGlobalOpacity;
renderableFlags.setPointsTopology(theSubset.rhi.ia.topology == QRhiGraphicsPipeline::Points);
QSSGRenderableObject *theRenderableObject = &renderableSubsets[subsetIdx++];
const bool usesInstancing = theModelContext.model.instancing()
&& rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
if (usesInstancing && theModelContext.model.instanceTable->hasTransparency())
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
if (theModelContext.model.hasTransparency)
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
// Level Of Detail
quint32 subsetLevelOfDetail = 0;
if (!theSubset.lods.isEmpty() && lodThreshold > 0.0f) {
// Accounts for FOV
float lodDistanceMultiplier = camerasView[0]->getLevelOfDetailMultiplier();
float distanceThreshold = 0.0f;
const auto scale = QSSGUtils::mat44::getScale(globalTransform);
float modelScale = qMax(scale.x(), qMax(scale.y(), scale.z()));
QSSGBounds3 transformedBounds = theSubset.bounds;
if (camerasView[0]->type != QSSGRenderGraphObject::Type::OrthographicCamera) {
transformedBounds.transform(globalTransform);
if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
debugDrawSystem->drawBounds(transformedBounds, QColor(Qt::red));
const QMatrix4x4 cameraGlobalTranform = getGlobalTransform(*camerasView[0]);
const QVector3D cameraNormal = QSSGRenderNode::getScalingCorrectDirection(cameraGlobalTranform);
const QVector3D cameraPosition = QSSGRenderNode::getGlobalPos(cameraGlobalTranform);
const QSSGPlane cameraPlane = QSSGPlane(cameraPosition, cameraNormal);
const QVector3D lodSupportMin = transformedBounds.getSupport(-cameraNormal);
const QVector3D lodSupportMax = transformedBounds.getSupport(cameraNormal);
if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
debugDrawSystem->drawPoint(lodSupportMin, QColor("orange"));
const float distanceMin = cameraPlane.distance(lodSupportMin);
const float distanceMax = cameraPlane.distance(lodSupportMax);
if (distanceMin * distanceMax < 0.0)
distanceThreshold = 0.0;
else if (distanceMin >= 0.0)
distanceThreshold = distanceMin;
else if (distanceMax <= 0.0)
distanceThreshold = -distanceMax;
} else {
// Orthographic Projection
distanceThreshold = 1.0;
}
int currentLod = -1;
if (model.levelOfDetailBias > 0.0f) {
// At this stage, the distance and position of the most detailed instance are unknown;
// therefore, the level of detail is managed manually using instancingLodFactor.
// lods only holds the simplified levels, so map the factor over lodsCount + 1
// levels: 0 selects the original mesh (currentLod == -1), 1 the coarsest level.
int lodsCount = theSubset.lods.size();
if (model.instanceTable && lodsCount) {
currentLod = qRound(lodsCount * qBound(0.f, model.instancingLodFactor, 1.f)) - 1;
} else {
const float threshold = distanceThreshold * lodDistanceMultiplier;
const float modelBias = 1 / model.levelOfDetailBias;
for (qsizetype i = 0; i < lodsCount; ++i) {
float subsetDistance = theSubset.lods[i].distance * modelScale * modelBias;
float screenSize = subsetDistance / threshold;
if (screenSize > lodThreshold)
break;
currentLod = i;
}
}
}
if (currentLod == -1)
subsetLevelOfDetail = 0;
else
subsetLevelOfDetail = currentLod + 1;
if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLod))
debugDrawSystem->drawBounds(transformedBounds, QSSGDebugDrawSystem::levelOfDetailColor(subsetLevelOfDetail));
}
QVector3D theModelCenter(theSubset.bounds.center());
theModelCenter = QSSGUtils::mat44::transform(globalTransform, theModelCenter);
if (maybeDebugDraw && debugDrawSystem->isEnabled(QSSGDebugDrawSystem::Mode::MeshLodNormal)) {
const QMatrix4x4 allCamera0GlobalTransform = getGlobalTransform(*allCameras[0]);
debugDrawSystem->debugNormals(*bufferManager, theModelContext, theSubset, subsetLevelOfDetail, (theModelCenter - QSSGRenderNode::getGlobalPos(allCamera0GlobalTransform)).length() * 0.01);
}
auto checkF32TypeIndex = [&rhiCtx](QRhiVertexInputAttribute::Format f) {
if ((f == QRhiVertexInputAttribute::Format::Float4)
|| (f == QRhiVertexInputAttribute::Format::Float3)
|| (f == QRhiVertexInputAttribute::Format::Float2)
|| (f == QRhiVertexInputAttribute::Format::Float)) {
return true;
}
if (!rhiCtx->rhi()->isFeatureSupported(QRhi::IntAttributes))
qWarning() << "WARN: Model has non-integer type indices for skinning but current RHI backend doesn't support it!";
return false;
};
if (theMaterialObject->type == QSSGRenderGraphObject::Type::DefaultMaterial ||
theMaterialObject->type == QSSGRenderGraphObject::Type::PrincipledMaterial ||
theMaterialObject->type == QSSGRenderGraphObject::Type::SpecularGlossyMaterial) {
QSSGRenderDefaultMaterial &theMaterial(static_cast<QSSGRenderDefaultMaterial &>(*theMaterialObject));
QSSGDefaultMaterialPreparationResult theMaterialPrepResult(prepareDefaultMaterialForRender(theMaterial, renderableFlags, subsetOpacity, hasAnyLights, anyLightHasShadows, ioFlags));
QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
subsetOpacity = theMaterialPrepResult.opacity;
QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
wasDirty |= theMaterialPrepResult.dirty;
renderableFlags = theMaterialPrepResult.renderableFlags;
if (renderableFlags.hasTransparency())
ioFlags.setHasCustomBlendMode(theMaterial.blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver);
// Blend particles
defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, usesBlendParticles);
if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
// Skin
const auto boneCount = model.skin ? model.skin->boneCount :
model.skeleton ? model.skeleton->boneCount : 0;
defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
}
// SoftShadow quality
defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
// Instancing
defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
// Morphing
defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
theSubset.rhi.ia.targetCount);
defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::DefaultMaterialMeshSubset,
renderableFlags,
theModelCenter,
renderer,
theSubset,
theModelContext,
subsetOpacity,
subsetLevelOfDetail,
theMaterial,
firstImage,
theGeneratedKey,
lights,
anyLightHasShadows);
wasDirty = wasDirty || renderableFlags.isDirty();
} else if (theMaterialObject->type == QSSGRenderGraphObject::Type::CustomMaterial) {
QSSGRenderCustomMaterial &theMaterial(static_cast<QSSGRenderCustomMaterial &>(*theMaterialObject));
const auto &theMaterialSystem(contextInterface.customMaterialSystem());
wasDirty |= theMaterialSystem->prepareForRender(theModelContext.model, theSubset, theMaterial);
if (theMaterial.m_renderFlags.testFlag(QSSGRenderCustomMaterial::RenderFlag::Blending))
ioFlags.setHasCustomBlendMode(!hasCustomBlendMode(theMaterial));
QSSGDefaultMaterialPreparationResult theMaterialPrepResult(
prepareCustomMaterialForRender(theMaterial, renderableFlags, subsetOpacity, wasDirty,
hasAnyLights, anyLightHasShadows, ioFlags));
QSSGShaderDefaultMaterialKey &theGeneratedKey(theMaterialPrepResult.materialKey);
subsetOpacity = theMaterialPrepResult.opacity;
QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
renderableFlags = theMaterialPrepResult.renderableFlags;
if (model.particleBuffer && model.particleBuffer->particleCount())
defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, true);
else
defaultMaterialShaderKeyProperties.m_blendParticles.setValue(theGeneratedKey, false);
if (defaultMaterialShaderKeyProperties.m_orderIndependentTransparency.getValue(theGeneratedKey) == int(QSSGRenderLayer::OITMethod::LinkedList))
defaultMaterialShaderKeyProperties.m_oitMSAA.setValue(theGeneratedKey, rhiCtx->mainPassSampleCount() > 1);
// SoftShadow quality
defaultMaterialShaderKeyProperties.m_shadowSoftness.setShadowSoftness(theGeneratedKey, maxSoftShadowQuality);
// Skin
const auto boneCount = model.skin ? model.skin->boneCount :
model.skeleton ? model.skeleton->boneCount : 0;
defaultMaterialShaderKeyProperties.m_boneCount.setValue(theGeneratedKey, boneCount);
if (auto idJoint = theSubset.rhi.ia.inputs.indexOf(QSSGRhiInputAssemblerState::JointSemantic); idJoint != -1) {
const auto attr = theSubset.rhi.ia.inputLayout.attributeAt(idJoint);
defaultMaterialShaderKeyProperties.m_usesFloatJointIndices.setValue(theGeneratedKey, checkF32TypeIndex(attr->format()));
}
// Instancing
bool usesInstancing = theModelContext.model.instancing()
&& rhiCtx->rhi()->isFeatureSupported(QRhi::Instancing);
defaultMaterialShaderKeyProperties.m_usesInstancing.setValue(theGeneratedKey, usesInstancing);
// Morphing
defaultMaterialShaderKeyProperties.m_targetCount.setValue(theGeneratedKey,
theSubset.rhi.ia.targetCount);
defaultMaterialShaderKeyProperties.m_targetPositionOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::PositionSemantic]);
defaultMaterialShaderKeyProperties.m_targetNormalOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::NormalSemantic]);
defaultMaterialShaderKeyProperties.m_targetTangentOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TangentSemantic]);
defaultMaterialShaderKeyProperties.m_targetBinormalOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::BinormalSemantic]);
defaultMaterialShaderKeyProperties.m_targetTexCoord0Offset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord0Semantic]);
defaultMaterialShaderKeyProperties.m_targetTexCoord1Offset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::TexCoord1Semantic]);
defaultMaterialShaderKeyProperties.m_targetColorOffset.setValue(theGeneratedKey,
theSubset.rhi.ia.targetOffsets[QSSGRhiInputAssemblerState::ColorSemantic]);
if (theMaterial.m_iblProbe)
theMaterial.m_iblProbe->clearDirty();
new (theRenderableObject) QSSGSubsetRenderable(QSSGSubsetRenderable::Type::CustomMaterialMeshSubset,
renderableFlags,
theModelCenter,
renderer,
theSubset,
theModelContext,
subsetOpacity,
subsetLevelOfDetail,
theMaterial,
firstImage,
theGeneratedKey,
lights,
anyLightHasShadows);
}
if (theRenderableObject) // NOTE: Should just go in with the ctor args
theRenderableObject->camdistSq = getCameraDistanceSq(*theRenderableObject, allCameraData[0]);
}
// If the indices don't match then something's off and we need to adjust the subset renderable list size.
if (Q_UNLIKELY(idx != subsetIdx))
renderableSubsets.mSize = subsetIdx; // subsetIdx == next_subsetIdx == size
for (auto &ro : renderableSubsets) {
const auto depthMode = ro.depthWriteMode;
hasDepthWriteObjects |= (depthMode == QSSGDepthDrawMode::Always || depthMode == QSSGDepthDrawMode::OpaqueOnly);
enum ObjectType : quint8 { ScreenTexture, Transparent, Opaque };
static constexpr DepthPrepassObject ppState[][2] = { {DepthPrepassObject::None, DepthPrepassObject::ScreenTexture},
{DepthPrepassObject::None, DepthPrepassObject::Transparent},
{DepthPrepassObject::None, DepthPrepassObject::Opaque} };
if (ro.renderableFlags.requiresScreenTexture()) {
depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::ScreenTexture][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
screenTextureObjects.push_back({&ro, ro.camdistSq, model.tag});
} else if (ro.renderableFlags.hasTransparency()) {
depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Transparent][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
transparentObjects.push_back({&ro, ro.camdistSq, model.tag});
} else {
depthPrepassObjectsState |= DepthPrepassObjectStateT(ppState[ObjectType::Opaque][size_t(depthMode == QSSGDepthDrawMode::OpaquePrePass)]);
opaqueObjects.push_back({&ro, ro.camdistSq, model.tag});
}
if (ro.renderableFlags.usedInBakedLighting())
bakedLightingObjects.push_back({&ro, ro.camdistSq, model.tag});
}
if (!bakedLightingObjects.isEmpty())
bakedLightingModels.push_back(QSSGBakedLightingModel(&model, bakedLightingObjects));
}
return wasDirty;
}
bool QSSGLayerRenderData::prepareParticlesForRender(const RenderableNodeEntries &renderableParticles, const QSSGRenderCameraData &cameraData, QSSGLayerRenderPreparationResultFlags &ioFlags)
{
QSSG_ASSERT(particlesEnabled, return false);
QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
bool dirty = false;
//
auto &opaqueObjects = opaqueObjectStore[0];
auto &transparentObjects = transparentObjectStore[0];
auto &screenTextureObjects = screenTextureObjectStore[0];
for (auto &renderable : renderableParticles) {
QSSGShaderParticleMaterialKeyProperties &properties = particleMaterialShaderKeyProperties;
QSSGRenderParticles &particles = *static_cast<QSSGRenderParticles *>(renderable.node);
QSSGShaderParticleMaterialKey &theGeneratedKey(particles.materialKey);
const auto &lights = renderable.lights;
QSSGRenderableObjectFlags renderableFlags;
renderableFlags.setCastsShadows(false);
renderableFlags.setReceivesShadows(false);
renderableFlags.setHasAttributePosition(true);
renderableFlags.setHasAttributeNormal(true);
renderableFlags.setHasAttributeTexCoord0(true);
renderableFlags.setHasAttributeColor(true);
renderableFlags.setHasTransparency(particles.m_hasTransparency);
renderableFlags.setCastsReflections(particles.m_castsReflections);
if (particles.m_hasTransparency && particles.m_blendMode != QSSGRenderParticles::BlendMode::SourceOver)
ioFlags.setHasCustomBlendMode(true);
properties.m_isLineParticle.setValue(theGeneratedKey, particles.m_featureLevel >= QSSGRenderParticles::FeatureLevel::Line);
const bool animated = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimated
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Animated
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight;
const bool mapped = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMapped
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::Mapped
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight;
const bool lit = particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::AnimatedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::MappedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineMappedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::LineAnimatedVLight
|| particles.m_featureLevel == QSSGRenderParticles::FeatureLevel::SimpleVLight;
properties.m_isAnimated.setValue(theGeneratedKey, animated);
properties.m_isMapped.setValue(theGeneratedKey, mapped);
properties.m_hasLighting.setValue(theGeneratedKey, lit);
properties.m_viewCount.setValue(theGeneratedKey, layer.viewCount);
if (renderableFlags.hasTransparency() && orderIndependentTransparencyEnabled) {
properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(layer.oitMethod));
if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList)
properties.m_oitMSAA.setValue(theGeneratedKey, contextInterface.rhiContext()->mainPassSampleCount() > 1);
} else {
properties.m_orderIndependentTransparency.setValue(theGeneratedKey, int(0));
}
float opacity = getGlobalOpacity(particles);
const QMatrix4x4 globalTransform = getGlobalTransform(particles);
QVector3D center(particles.m_particleBuffer.bounds().center());
center = QSSGUtils::mat44::transform(globalTransform, center);
QSSGRenderableImage *firstImage = nullptr;
if (particles.m_sprite) {
const auto &bufferManager = contextInterface.bufferManager();
if (particles.m_sprite->clearDirty())
dirty = true;
const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_sprite);
QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_sprite, texture);
firstImage = theImage;
properties.m_isSpriteLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
}
QSSGRenderableImage *colorTable = nullptr;
if (particles.m_colorTable) {
const auto &bufferManager = contextInterface.bufferManager();
if (particles.m_colorTable->clearDirty())
dirty = true;
const QSSGRenderImageTexture texture = bufferManager->loadRenderImage(particles.m_colorTable);
QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(contextInterface, QSSGRenderableImage::Type::Diffuse, *particles.m_colorTable, texture);
colorTable = theImage;
properties.m_isColorTableLinear.setValue(theGeneratedKey, texture.m_flags.isLinear());
}
if (opacity > 0.0f && particles.m_particleBuffer.particleCount()) {
auto *theRenderableObject = RENDER_FRAME_NEW<QSSGParticlesRenderable>(contextInterface,
renderableFlags,
center,
renderer,
globalTransform,
particles,
firstImage,
colorTable,
lights,
opacity,
theGeneratedKey);
if (theRenderableObject) {
if (theRenderableObject->renderableFlags.requiresScreenTexture())
screenTextureObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
else if (theRenderableObject->renderableFlags.hasTransparency())
transparentObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
else
opaqueObjects.push_back({theRenderableObject, getCameraDistanceSq(*theRenderableObject, cameraData), particles.tag});
}
}
}
return dirty;
}
void QSSGLayerRenderData::prepareResourceLoaders()
{
QSSGRenderContextInterface &contextInterface = *renderer->contextInterface();
const auto &bufferManager = contextInterface.bufferManager();
for (const auto resourceLoader : std::as_const(layer.resourceLoaders))
bufferManager->processResourceLoader(static_cast<QSSGRenderResourceLoader *>(resourceLoader));
}
void QSSGLayerRenderData::prepareReflectionProbesForRender()
{
const auto probeCount = reflectionProbesView.size();
requestReflectionMapManager(); // ensure that we have a reflection map manager
for (int i = 0; i < probeCount; i++) {
QSSGRenderReflectionProbe* probe = reflectionProbesView[i];
QMatrix4x4 probeTransform = getGlobalTransform(*probe);
int reflectionObjectCount = 0;
QVector3D probeExtent = probe->boxSize / 2;
QSSGBounds3 probeBound = QSSGBounds3::centerExtents(QSSGRenderNode::getGlobalPos(probeTransform) + probe->boxOffset, probeExtent);
const auto injectProbe = [&](const QSSGRenderableObjectHandle &handle) {
if (handle.obj->renderableFlags.testFlag(QSSGRenderableObjectFlag::ReceivesReflections)
&& !(handle.obj->type == QSSGRenderableObject::Type::Particles)) {
QSSGSubsetRenderable* renderableObj = static_cast<QSSGSubsetRenderable*>(handle.obj);
QSSGBounds3 nodeBound = renderableObj->bounds;
QVector4D vmin(nodeBound.minimum, 1.0);
QVector4D vmax(nodeBound.maximum, 1.0);
const QMatrix4x4 &renderableTransform = renderableObj->modelContext.globalTransform;
vmin = renderableTransform * vmin;
vmax = renderableTransform * vmax;
nodeBound.minimum = vmin.toVector3D();
nodeBound.maximum = vmax.toVector3D();
if (probeBound.intersects(nodeBound)) {
QVector3D nodeBoundCenter = nodeBound.center();
QVector3D probeBoundCenter = probeBound.center();
float distance = nodeBoundCenter.distanceToPoint(probeBoundCenter);
if (renderableObj->reflectionProbeIndex == -1 || distance < renderableObj->distanceFromReflectionProbe) {
renderableObj->reflectionProbeIndex = i;
renderableObj->distanceFromReflectionProbe = distance;
renderableObj->reflectionProbe.parallaxCorrection = probe->parallaxCorrection;
renderableObj->reflectionProbe.probeCubeMapCenter = QSSGRenderNode::getGlobalPos(probeTransform);
renderableObj->reflectionProbe.probeBoxMax = probeBound.maximum;
renderableObj->reflectionProbe.probeBoxMin = probeBound.minimum;
renderableObj->reflectionProbe.enabled = true;
reflectionObjectCount++;
}
}
}
};
const auto &transparentObjects = std::as_const(transparentObjectStore[0]);
const auto &opaqueObjects = std::as_const(opaqueObjectStore[0]);
const auto &screenTextureObjects = std::as_const(screenTextureObjectStore[0]);
for (const auto &handle : std::as_const(transparentObjects))
injectProbe(handle);
for (const auto &handle : std::as_const(opaqueObjects))
injectProbe(handle);
for (const auto &handle : std::as_const(screenTextureObjects))
injectProbe(handle);
if (probe->texture)
reflectionMapManager->addTexturedReflectionMapEntry(i, *probe);
else if (reflectionObjectCount > 0)
reflectionMapManager->addReflectionMapEntry(i, *probe);
}
}
static bool scopeLight(QSSGRenderNode *node, QSSGRenderNode *lightScope)
{
// check if the node is parent of the lightScope
while (node) {
if (node == lightScope)
return true;
node = node->parent;
}
return false;
}
static const int REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES = 5200; // 325 vec4s
static inline int effectiveMaxLightCount(const QSSGShaderFeatures &features)
{
if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
return QSSG_REDUCED_MAX_NUM_LIGHTS;
return QSSG_MAX_NUM_LIGHTS;
}
static inline int effectiveMaxDirectionalLightCount(const QSSGShaderFeatures &features)
{
if (features.isSet(QSSGShaderFeatures::Feature::ReduceMaxNumLights))
return QSSG_REDUCED_MAX_NUM_DIRECTIONAL_LIGHTS;
return QSSG_MAX_NUM_DIRECTIONAL_LIGHTS;
}
static void updateDirtySkeletons(const QSSGLayerRenderData &renderData, const QSSGLayerRenderData::QSSGModelsView &renderableNodes)
{
// First model using skeleton clears the dirty flag so we need another mechanism
// to tell to the other models the skeleton is dirty.
QSet<QSSGRenderSkeleton *> dirtySkeletons;
for (const auto &modelNode : std::as_const(renderableNodes)) {
QSSGRenderSkeleton *skeletonNode = modelNode->skeleton;
bool hcj = false;
if (skeletonNode) {
const bool dirtySkeleton = dirtySkeletons.contains(skeletonNode);
const bool hasDirtyNonJoints = (skeletonNode->containsNonJointNodes
&& (hasDirtyNonJointNodes(skeletonNode, hcj) || dirtySkeleton));
if (skeletonNode->skinningDirty || hasDirtyNonJoints) {
Q_ASSERT(!skeletonNode->isDirty(QSSGRenderNode::DirtyFlag::GlobalValuesDirty));
skeletonNode->boneTransformsDirty = false;
if (hasDirtyNonJoints && !dirtySkeleton)
dirtySkeletons.insert(skeletonNode);
skeletonNode->skinningDirty = false;
const qsizetype dataSize = BONEDATASIZE4ID(skeletonNode->maxIndex);
if (skeletonNode->boneData.size() < dataSize)
skeletonNode->boneData.resize(dataSize);
skeletonNode->containsNonJointNodes = false;
for (auto &child : skeletonNode->children)
collectBoneTransforms(renderData, &child, skeletonNode, modelNode->inverseBindPoses);
}
skeletonNode->boneCount = skeletonNode->boneData.size() / 2 / 4 / 16;
const int boneTexWidth = qCeil(qSqrt(skeletonNode->boneCount * 4 * 2));
skeletonNode->boneTexData.setSize(QSize(boneTexWidth, boneTexWidth));
skeletonNode->boneData.resize(boneTexWidth * boneTexWidth * 16);
skeletonNode->boneTexData.setTextureData(skeletonNode->boneData);
}
const int numMorphTarget = modelNode->morphTargets.size();
for (int i = 0; i < numMorphTarget; ++i) {
auto morphTarget = static_cast<const QSSGRenderMorphTarget *>(modelNode->morphTargets.at(i));
modelNode->morphWeights[i] = morphTarget->weight;
modelNode->morphAttributes[i] = morphTarget->attributes;
if (i > MAX_MORPH_TARGET_INDEX_SUPPORTS_NORMALS)
modelNode->morphAttributes[i] &= 0x1; // MorphTarget.Position
else if (i > MAX_MORPH_TARGET_INDEX_SUPPORTS_TANGENTS)
modelNode->morphAttributes[i] &= 0x3; // MorphTarget.Position | MorphTarget.Normal
}
}
dirtySkeletons.clear();
}
QSSGNodeIdList QSSGLayerRenderData::filter(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
quint32 layerMask,
quint32 typeMask)
{
QSSGNodeIdList res;
for (auto *n : layerNodes) {
// Check mask
if (((quint32(n->type) & typeMask) == typeMask) && n->tag.isSet(layerMask))
res.push_back(QSSGNodeId(reinterpret_cast<quintptr>(n)));
}
return res;
}
void QSSGLayerRenderData::categorizeAndFilterNodes(const QSSGGlobalRenderNodeData::LayerNodeView &layerNodes,
QSSGLayerRenderData::NodeCollection &nodeCollection,
quint32 layerMask)
{
auto &modelsView = nodeCollection.modelsView;
auto &particlesView = nodeCollection.particlesView;
auto &item2DsView = nodeCollection.item2DsView;
auto &camerasView = nodeCollection.camerasView;
auto &lightsView = nodeCollection.lightsView;
auto &reflectionProbesView = nodeCollection.reflectionProbesView;
auto &nonCategorizedView = nodeCollection.nonCategorizedView;
auto &layerNodesCategorized = nodeCollection.layerNodesCategorized;
modelsView.clear();
particlesView.clear();
item2DsView.clear();
camerasView.clear();
lightsView.clear();
reflectionProbesView.clear();
nonCategorizedView.clear();
enum NodeType : size_t { Model = 0, Particles, Item2D, Camera, ImportedCamera, Light, ReflectionProbe, Other, Inactive };
const auto nodeType = [layerMask](QSSGRenderNode *node) -> NodeType {
if (!(node->getGlobalState(QSSGRenderNode::GlobalState::Active)))
return NodeType::Inactive;
if (!(node->tag.isSet(layerMask)))
return NodeType::Other;
switch (node->type) {
case QSSGRenderGraphObject::Type::Model: return NodeType::Model;
case QSSGRenderGraphObject::Type::Particles: return NodeType::Particles;
case QSSGRenderGraphObject::Type::Item2D: return NodeType::Item2D;
case QSSGRenderGraphObject::Type::ReflectionProbe: return NodeType::ReflectionProbe;
default: break;
}
if (QSSGRenderGraphObject::isCamera(node->type)) {
// NOTE: To keep compatibility with old code, we collect shared and non-shared cameras differently,
// so that shared cameras (import scene) are picked after non-shared ones.
const bool isImported = node->getGlobalState(QSSGRenderNode::GlobalState::Imported);
constexpr NodeType cameraTypes[2] { NodeType::Camera, NodeType::ImportedCamera };
return cameraTypes[size_t(isImported)];
}
if (QSSGRenderGraphObject::isLight(node->type))
return NodeType::Light;
return NodeType::Other;
};
// sort nodes by type - We could do this on insert, but it's not given that it would be beneficial.
// Depending on how we want to handle the nodes later it might just not give us anything
// so, keep it simple for now.
// We could also speed up this by having the pointer and the type in the same struct and sort without
// indirection. However, that' slightly less convenient and the idea here is that we don't process
// this list unless things change, which is not something that should happen often if the user is
// concerned about performance, as it means we need to reevaluate the whole scene anyway.
{
// Sort the nodes by type (we copy the pointers to avoid sorting the original list,
// which is stored based on the nodes' order in the world tree).
layerNodesCategorized = { layerNodes.begin(), layerNodes.end() };
// NOTE: Due to the ordering of item2ds, we need to use stable_sort.
std::stable_sort(layerNodesCategorized.begin(), layerNodesCategorized.end(), [nodeType](QSSGRenderNode *a, QSSGRenderNode *b) {
return nodeType(a) < nodeType(b);
});
}
// Group nodes by type inline and keep track of the individual parts using QSSGDataViews
const LayerNodeStatResult stat = statLayerNodes(layerNodesCategorized, layerMask);
// Go through the sorted nodes and create the views
size_t next = 0;
if (stat.modelCount > 0) {
modelsView = QSSGModelsView((QSSGRenderModel **)(layerNodesCategorized.data() + next), stat.modelCount);
next = modelsView.size();
}
if (stat.particlesCount > 0) {
particlesView = QSSGParticlesView((QSSGRenderParticles **)(layerNodesCategorized.data() + next), stat.particlesCount);
next += particlesView.size();
}
if (stat.item2DCount > 0) {
item2DsView = QSSGItem2DsView((QSSGRenderItem2D **)(layerNodesCategorized.data() + next), stat.item2DCount);
next += item2DsView.size();
}
if (stat.cameraCount > 0) {
camerasView = QSSGCamerasView((QSSGRenderCamera **)(layerNodesCategorized.data() + next), stat.cameraCount);
next += camerasView.size();
}
if (stat.lightCount > 0) {
lightsView = QSSGLightsView((QSSGRenderLight **)(layerNodesCategorized.data() + next), stat.lightCount);
next += lightsView.size();
}
if (stat.reflectionProbeCount > 0) {
reflectionProbesView = QSSGReflectionProbesView((QSSGRenderReflectionProbe **)(layerNodesCategorized.data() + next), stat.reflectionProbeCount);
next += reflectionProbesView.size();
}
if (stat.otherCount > 0) {
nonCategorizedView = QSSGNonCategorizedView((QSSGRenderNode **)(layerNodesCategorized.data() + next), stat.otherCount);
next += nonCategorizedView.size();
(void)next;
}
}
void QSSGLayerRenderData::updateFilteredLayerNodes(quint32 layerMask)
{
categorizeAndFilterNodes(layerNodes, nodeCollection, layerMask);
// FIXME: Compatability with old code (Will remove later).
// NOTE: see resetForFrame() as well for extensions usage
renderableModels.clear();
renderableParticles.clear();
renderableModels.reserve(modelsView.size());
renderableParticles.reserve(particlesView.size());
renderableModels = {modelsView.begin(), modelsView.end()};
renderableParticles = {particlesView.begin(), particlesView.end()};
}
void QSSGLayerRenderData::prepareForRender()
{
QSSG_ASSERT_X(layerPrepResult.isNull(), "Prep-result was not reset for render!", layerPrepResult = {});
QRect theViewport(renderer->viewport());
// NOTE: The renderer won't change in practice (after being set the first time), but just update
// it anyways.
frameData.m_ctx = renderer->contextInterface();
frameData.clear();
// Create base pipeline state
ps = {}; // Reset
ps.viewport = { float(theViewport.x()), float(theViewport.y()), float(theViewport.width()), float(theViewport.height()), 0.0f, 1.0f };
if (layer.scissorRect.isValid()) {
ps.flags |= QSSGRhiGraphicsPipelineState::Flag::UsesScissor;
ps.scissor = { layer.scissorRect.x(),
theViewport.height() - (layer.scissorRect.y() + layer.scissorRect.height()),
layer.scissorRect.width(),
layer.scissorRect.height() };
}
ps.depthFunc = QRhiGraphicsPipeline::LessOrEqual;
ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled, false);
// Enable Wireframe mode
ps.polygonMode = layer.wireframeMode ? QRhiGraphicsPipeline::Line : QRhiGraphicsPipeline::Fill;
bool wasDirty = false;
bool wasDataDirty = false;
wasDirty = layer.isDirty();
const bool shouldDisableInternalPasses = (layer.renderOverrides & size_t(QSSGRenderLayer::RenderOverrides::DisableInternalPasses)) != 0;
wasDirty |= (shouldDisableInternalPasses != disableMainPasses);
disableMainPasses = shouldDisableInternalPasses;
// NOTE: Prep-state is implicitly set to "DataPrep"
layerPrepResult = { theViewport, layer };
// SSAO
const bool SSAOEnabled = layer.ssaoEnabled();
layerPrepResult.flags.setRequiresSsaoPass(SSAOEnabled);
features.set(QSSGShaderFeatures::Feature::Ssao, SSAOEnabled);
// Effects
bool requiresDepthTexture = SSAOEnabled;
bool requiresNormalTexture = false;
bool requiresMotionVectorTexture = false;
for (QSSGRenderEffect *theEffect = layer.firstEffect; theEffect; theEffect = theEffect->m_nextEffect) {
if (theEffect->isDirty()) {
wasDirty = true;
theEffect->clearDirty();
}
if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesDepthTexture))
requiresDepthTexture = true;
if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesNormalTexture))
requiresNormalTexture = true;
if (theEffect->testFlag(QSSGRenderEffect::Flags::UsesMotionVectorTexture))
requiresMotionVectorTexture = true;
}
const auto &rhiCtx = renderer->contextInterface()->rhiContext();
orderIndependentTransparencyEnabled = (layer.oitMethod != QSSGRenderLayer::OITMethod::None);
if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
orderIndependentTransparencyEnabled = rhiCtx->rhi()->isFeatureSupported(QRhi::PerRenderTargetBlending);
if (rhiCtx->mainPassSampleCount() > 1)
orderIndependentTransparencyEnabled &= rhiCtx->rhi()->isFeatureSupported(QRhi::TexelFetch) && rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
qCWarning(lcQuick3DRender) << "WeightedBlended OIT is requested, but it is not supported.";
oitWarningUnsupportedShown = true;
}
} else if (layer.oitMethod == QSSGRenderLayer::OITMethod::LinkedList) {
orderIndependentTransparencyEnabled = rhiCtx->rhi()->isTextureFormatSupported(QRhiTexture::RGBA32UI, QRhiTexture::UsedWithLoadStore);
if (rhiCtx->mainPassSampleCount() > 1)
orderIndependentTransparencyEnabled &= rhiCtx->rhi()->isFeatureSupported(QRhi::SampleVariables);
if (!orderIndependentTransparencyEnabled && !oitWarningUnsupportedShown) {
qCWarning(lcQuick3DRender) << "LinkedList OIT is requested, but it is not supported.";
oitWarningUnsupportedShown = true;
}
}
if (layer.oitMethodDirty) {
oitRenderContext.reset();
for (auto &renderResult : renderResults)
renderResult.reset();
}
layerPrepResult.flags.setRequiresDepthTexture(requiresDepthTexture);
layerPrepResult.flags.setRequiresNormalTexture(requiresNormalTexture);
layerPrepResult.flags.setRequiresMotionVectorPass(requiresMotionVectorTexture);
// Tonemapping. Except when there are effects, then it is up to the
// last pass of the last effect to perform tonemapping.
if (!layer.firstEffect)
QSSGLayerRenderData::setTonemapFeatures(features, layer.tonemapMode);
// We may not be able to have an array of 15 light struct elements in
// the shaders. Switch on the reduced-max-number-of-lights feature
// if necessary. In practice this is relevant with OpenGL ES 3.0 or
// 2.0, because there are still implementations in use that only
// support the spec mandated minimum of 224 vec4s (so 3584 bytes).
if (rhiCtx->rhi()->resourceLimit(QRhi::MaxUniformBufferRange) < REDUCED_MAX_LIGHT_COUNT_THRESHOLD_BYTES) {
features.set(QSSGShaderFeatures::Feature::ReduceMaxNumLights, true);
static bool notified = false;
if (!notified) {
notified = true;
qCDebug(lcQuick3DRender, "Qt Quick 3D maximum number of lights has been reduced from %d to %d due to the graphics driver's limitations",
QSSG_MAX_NUM_LIGHTS, QSSG_REDUCED_MAX_NUM_LIGHTS);
}
}
// IBL source configuration
const auto &lightProbeSettings = layer.lightProbeSettings;
if (layer.lightProbe) {
if (layer.lightProbe->m_format == QSSGRenderTextureFormat::Unknown) {
// Choose on a format that makes sense for a light probe
// At this point it's just a suggestion
if (renderer->contextInterface()->rhiContext()->rhi()->isTextureFormatSupported(QRhiTexture::RGBA16F))
layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBA16F;
else
layer.lightProbe->m_format = QSSGRenderTextureFormat::RGBE8;
}
if (layer.lightProbe->clearDirty())
wasDataDirty = true;
features.set(QSSGShaderFeatures::Feature::LightProbe, true);
features.set(QSSGShaderFeatures::Feature::IblOrientation, !lightProbeSettings.probeOrientation.isIdentity());
if (layer.lightProbe->m_format.format == QSSGRenderTextureFormat::Format::RGBE8)
features.set(QSSGShaderFeatures::Feature::RGBELightProbe, true);
} else if (layer.skyMaterial) {
if (layer.skyMaterial->enableIBL) {
features.set(QSSGShaderFeatures::Feature::LightProbe, true);
features.set(QSSGShaderFeatures::Feature::IblOrientation, !lightProbeSettings.probeOrientation.isIdentity());
}
layerPrepResult.flags.setRequiresSkyMaterialPass(true);
}
const bool forceIblExposureValues = (features.isSet(QSSGShaderFeatures::Feature::LightProbe)
&& layer.tonemapMode == QSSGRenderLayer::TonemapMode::Custom);
features.set(QSSGShaderFeatures::Feature::ForceIblExposure, forceIblExposureValues);
frameData.m_ctx->bufferManager()->setLightmapSource(layer.lightmapSource);
// Update the node data version for this layer.
// This version should only change if the world tree was re-indexed.
version = nodeData->version();
// We're using/updating the node data directly.
// NOTE: These are the transforms and opacities for all nodes for all layers/views.
// We'll just use or update the ones the one for this layer.
auto &globalTransforms = nodeData->globalTransforms;
auto &globalOpacities = nodeData->globalOpacities;
auto &instanceTransforms = nodeData->instanceTransforms;
///////// 2 - START LAYER
QSSGRenderDataHelpers::GlobalStateResultT globalStateResult = QSSGRenderDataHelpers::GlobalStateResult::None;
const bool layerTreeWasDirty = layer.isDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
layer.clearDirty(QSSGRenderLayer::DirtyFlag::TreeDirty);
if (layerTreeWasDirty) {
wasDataDirty = true;
layerNodes = nodeData->getLayerNodeView(layer);
} else {
using GlobalStateResult = QSSGRenderDataHelpers::GlobalStateResult;
// If the active state or tags of a node changed, it may affect the filtered node list for this layer. If so, we need to re-filter the nodes.
constexpr QSSGRenderDataHelpers::GlobalStateResultT FilterAffectingChanges = GlobalStateResult::ActiveChanged
| GlobalStateResult::TagChanged;
bool sharedStateChanged = false;
for (auto &node : layerNodes) {
const auto result = QSSGRenderDataHelpers::updateGlobalNodeState(node, version);
globalStateResult |= result;
// If the node is shared and the change affects the filtered node list, we need to re-filter the nodes for this layer.
// The reason we need to do that is because the first layer processing the node will clear the dirty flag making the
// change invisible to the other layers sharing the node (we'll call bumpStateGeneration() further down).
const bool affectsFilter = ((result & FilterAffectingChanges) != 0);
const bool isShared = node->getGlobalState(QSSGRenderNode::GlobalState::Imported);
sharedStateChanged |= (affectsFilter && isShared);
}
if (sharedStateChanged)
nodeData->bumpStateGeneration();
bool transformAndOpacityDirty = false;
for (auto &node : layerNodes)
transformAndOpacityDirty |= QSSGRenderDataHelpers::calcGlobalNodeData<QSSGRenderDataHelpers::Strategy::Update>(node, version, globalTransforms, globalOpacities);
// FIXME: We shouldn't need to re-create all the instance transforms even when instancing isn't used...
if (transformAndOpacityDirty) {
for (const auto &node : layerNodes)
wasDataDirty |= QSSGRenderDataHelpers::calcInstanceTransforms(node, version, globalTransforms, instanceTransforms);
}
wasDataDirty |= transformAndOpacityDirty;
}
// If the viewport visibility changed, we mark the data as dirty to ensure a proper update
// happens. This is important because if there are shared imported nodes between layers,
// they might have been updated by one of the other layers and therefore considered "clean", causing
// things like progressiveAA to not work correctly when toggling the visibility of a layer.
const bool viewportVisibilityDirty = layer.isDirty(QSSGRenderLayer::DirtyFlag::VisibilityDirty);
layer.clearDirty(QSSGRenderLayer::DirtyFlag::VisibilityDirty);
if (viewportVisibilityDirty)
wasDataDirty = true;
// Check if we have an explicit camera!
// NOTE: We only do layering if we have an explicit camera!!!
const bool hasExplicitCamera = (layer.explicitCameras.size() != 0);
bool cameraLayerMaskDirty = false;
quint32 layerMask = QSSGRenderCamera::LayerMaskAll;
if (hasExplicitCamera) {
QSSGRenderCamera *explicitCamera = layer.explicitCameras[0];
// For now we add the reserved bits to the camera layer mask.
// In the future we might want to control which reserved bits are added.
layerMask = explicitCamera->tag.value() | QSSGRenderCamera::ReservedLayerMask;
cameraLayerMaskDirty = explicitCamera->isDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
explicitCamera->clearDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
}
// Pick up active and tag changes on shared nodes that were consumed by another layer.
const bool sharedStateDirty = (stateGeneration != nodeData->stateGeneration());
stateGeneration = nodeData->stateGeneration();
const bool restatNodes = (layerTreeWasDirty
|| (globalStateResult & (QSSGRenderDataHelpers::GlobalStateResult::ActiveChanged | QSSGRenderDataHelpers::GlobalStateResult::TagChanged))
|| cameraLayerMaskDirty
|| sharedStateDirty);
if (restatNodes)
updateFilteredLayerNodes(layerMask);
// Cameras
// 1. If there's an explicit camera set and it's active (visible) we'll use that.
// 2. ... if the explicitly set camera is not visible, no further attempts will be done.
// 3. If no explicit camera is set, we'll search and pick the first active camera.
QSSGRenderCamera::Configuration cameraConfig { renderer->dpr(), layer.isSsaaEnabled() ? layer.ssaaMultiplier : 1.0f };
renderedCameras.clear();
if (!layer.explicitCameras.isEmpty()) {
for (QSSGRenderCamera *cam : std::as_const(layer.explicitCameras)) {
// 1.
if (cam->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
const bool computeFrustumSucceeded = cam->calculateProjection(theViewport, cameraConfig);
if (Q_LIKELY(computeFrustumSucceeded))
renderedCameras.append(cam);
else
qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
}
}
// 2.
} else if (QSSG_GUARD_X(layer.viewCount == 1, "Multiview rendering requires explicit cameras to be set!.")) {
// NOTE: This path can never be hit with multiview, hence the guard.
// (Multiview will always have explicit cameras set.)
// 3.
for (auto iter = camerasView.begin(); renderedCameras.isEmpty() && iter != camerasView.end(); iter++) {
QSSGRenderCamera *theCamera = *iter;
if (theCamera->getGlobalState(QSSGRenderCamera::GlobalState::Active)) {
const bool computeFrustumSucceeded = theCamera->calculateProjection(theViewport, cameraConfig);
if (Q_LIKELY(computeFrustumSucceeded))
renderedCameras.append(theCamera);
else
qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
}
}
// Now check if the camera has a layer mask, if it does we need to filter the nodes again!
// Issue a warning letting the user know that they should set an explicit camera!
// If you are reading this due to seeing the warning: Not requiring an explicit camera was
// a mistake, but we kept it for compatability reasons unfortunately.
if (renderedCameras.size() > 0 && (renderedCameras[0]->layerMask & QSSGRenderCamera::LayerMaskUserAll) != QSSGRenderCamera::LayerMaskUserAll) {
if (!nonExplicitCameraWithLayerMaskWarningShown) {
nonExplicitCameraWithLayerMaskWarningShown = true;
qWarning() << "Scenes with non-explicit cameras with a layer detected!"
" Set the camera explicitly to avoid unnecessary evaluation of scene nodes!";
}
// Now filter the nodes, again.
layerMask = renderedCameras[0]->layerMask;
updateFilteredLayerNodes(layerMask);
}
}
float meshLodThreshold = 1.0f;
if (!renderedCameras.isEmpty())
meshLodThreshold = renderedCameras[0]->levelOfDetailPixelThreshold / theViewport.width();
layer.renderedCamerasMutex.lock();
layer.renderedCameras = renderedCameras;
layer.renderedCamerasMutex.unlock();
// Meshes, materials, MVP, and normal matrices for the models
const QSSGRenderCameraDataList &renderCameraData = getCachedCameraDatas();
modelData->updateModelData(modelsView, renderer, renderCameraData);
// Item2Ds
item2DData->updateItem2DData(item2DsView, renderer, renderCameraData);
// ResourceLoaders
prepareResourceLoaders();
// Skeletons
updateDirtySkeletons(*this, modelsView);
// Lights
int directionalLightsCount = 0;
int positionalLightsCount = 0;
const int maxLightCount = effectiveMaxLightCount(features);
const int maxDirectionalLights = effectiveMaxDirectionalLightCount(features);
QSSGShaderLightList renderableLights;
int shadowMapCount = 0;
bool hasScopedLights = false;
// Determine which lights will actually Render
// Determine how many lights will need shadow maps
// NOTE: This culling is specific to our Forward renderer
{
auto it = std::make_reverse_iterator(lightsView.end());
const auto end = it + qMin(maxLightCount, lightsView.size());
for (; it != end; ++it) {
QSSGRenderLight *renderLight = (*it);
QMatrix4x4 renderLightTransform = getGlobalTransform(*renderLight);
if (renderLight->type == QSSGRenderGraphObject::Type::DirectionalLight)
directionalLightsCount++;
else
positionalLightsCount++;
if (positionalLightsCount > maxLightCount)
continue;
if (directionalLightsCount > maxDirectionalLights)
continue;
hasScopedLights |= (renderLight->m_scope != nullptr);
const bool castShadows = renderLight->m_castShadow && !renderLight->m_fullyBaked;
shadowMapCount += int(castShadows);
const auto &direction = renderLight->getScalingCorrectDirection(renderLightTransform);
renderableLights.push_back(QSSGShaderLight{ renderLight, castShadows, direction });
}
}
const bool showLightCountWarning = !tooManyLightsWarningShown && (positionalLightsCount > maxLightCount);
if (showLightCountWarning) {
qWarning("Too many lights in scene, maximum is %d", maxLightCount);
tooManyLightsWarningShown = true;
}
const bool showDirectionalLightCountWarning = !tooManyDirectionalLightsWarningShown && (directionalLightsCount > maxDirectionalLights);
if (showDirectionalLightCountWarning) {
qWarning("Too many directional lights in scene, maximum is %d", maxDirectionalLights);
tooManyDirectionalLightsWarningShown = true;
}
if (shadowMapCount > 0) { // Setup Shadow Maps Entries for Lights casting shadows
requestShadowMapManager(); // Ensure we have a shadow map manager
layerPrepResult.flags.setRequiresShadowMapPass(true);
// Any light with castShadow=true triggers shadow mapping
// in the generated shaders. The fact that some (or even
// all) objects may opt out from receiving shadows plays no
// role here whatsoever.
features.set(QSSGShaderFeatures::Feature::Ssm, true);
shadowMapManager->addShadowMaps(renderableLights);
} else if (shadowMapManager) {
// No shadows but a shadow manager so clear old resources
shadowMapManager->releaseCachedResources();
}
// Give each renderable a copy of the lights available
// Also setup scoping for scoped lights
QSSG_ASSERT(globalLights.isEmpty(), globalLights.clear());
if (hasScopedLights) { // Filter out scoped lights from the global lights list
for (const auto &shaderLight : std::as_const(renderableLights)) {
if (!shaderLight.light->m_scope)
globalLights.push_back(shaderLight);
}
const auto prepareLightsWithScopedLights = [&renderableLights, this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
QSSGShaderLightList filteredLights;
for (const auto &light : std::as_const(renderableLights)) {
if (light.light->m_scope && !scopeLight(theNodeEntry.node, light.light->m_scope))
continue;
filteredLights.push_back(light);
}
if (filteredLights.isEmpty()) { // Node without scoped lights, just reference the global light list.
theNodeEntry.lights = QSSGDataView(globalLights);
} else {
// This node has scoped lights, i.e., it's lights differ from the global list
// we therefore create a bespoke light list for it. Technically this might be the same for
// more then this one node, but the overhead for tracking that is not worth it.
auto customLightList = RENDER_FRAME_NEW_BUFFER<QSSGShaderLight>(*renderer->contextInterface(), filteredLights.size());
std::copy(filteredLights.cbegin(), filteredLights.cend(), customLightList.begin());
theNodeEntry.lights = customLightList;
}
}
};
prepareLightsWithScopedLights(renderableModels);
prepareLightsWithScopedLights(renderableParticles);
} else { // Just a simple copy
globalLights = renderableLights;
// No scoped lights, all nodes can just reference the global light list.
const auto prepareLights = [this](QVector<QSSGRenderableNodeEntry> &renderableNodes) {
for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
theNodeEntry.lights = QSSGDataView(globalLights);
}
};
prepareLights(renderableModels);
prepareLights(renderableParticles);
}
{
// Give user provided passes a chance to modify the renderable data before starting
// Note: All non-active extensions should be filtered out by now
Q_STATIC_ASSERT(USERPASSES == size_t(QSSGRenderLayer::RenderExtensionStage::Count));
for (size_t i = 0; i != size_t(QSSGRenderLayer::RenderExtensionStage::Count); ++i) {
const auto &renderExtensions = layer.renderExtensions[i];
auto &userPass = userPasses[i];
for (auto rit = renderExtensions.crbegin(), rend = renderExtensions.crend(); rit != rend; ++rit) {
if ((*rit)->prepareData(frameData)) {
wasDirty |= true;
userPass.extensions.push_back(*rit);
}
}
}
}
// User render passes
{
const auto &userRenderPassManager = requestUserRenderPassManager();
userRenderPassManager->updateUserPassOrder(layerTreeWasDirty); // NOTE: If the tree was dirty we need to force an update.
userRenderPasses.userPasses = userRenderPassManager->scheduledUserPasses();
}
auto &opaqueObjects = opaqueObjectStore[0];
auto &transparentObjects = transparentObjectStore[0];
auto &screenTextureObjects = screenTextureObjectStore[0];
if (!renderedCameras.isEmpty()) { // NOTE: We shouldn't really get this far without a camera...
wasDirty |= prepareModelsForRender(*renderer->contextInterface(), renderableModels, layerPrepResult.flags, renderedCameras, getCachedCameraDatas(), modelContexts, opaqueObjects, transparentObjects, screenTextureObjects, meshLodThreshold);
if (particlesEnabled) {
const auto &cameraDatas = getCachedCameraDatas();
wasDirty |= prepareParticlesForRender(renderableParticles, cameraDatas[0], layerPrepResult.flags);
}
// If there's item2Ds we set wasDirty.
wasDirty |= (item2DsView.size() != 0);
}
if (orderIndependentTransparencyEnabled) {
// OIT blending mode must be SourceOver and have transparent objects
if (transparentObjects.size() > 0 && !layerPrepResult.flags.hasCustomBlendMode()) {
if (layer.oitMethod == QSSGRenderLayer::OITMethod::WeightedBlended) {
if (rhiCtx->mainPassSampleCount() > 1)
layerPrepResult.flags.setRequiresDepthTextureMS(true);
else
layerPrepResult.flags.setRequiresDepthTexture(true);
}
} else {
orderIndependentTransparencyEnabled = false;
if (!oitWarningInvalidBlendModeShown) {
qCWarning(lcQuick3DRender) << "Order Independent Transparency requested, but disabled due to invalid blend modes.";
qCWarning(lcQuick3DRender) << "Use SourceOver blend mode for Order Independent Transparency.";
oitWarningInvalidBlendModeShown = true;
}
}
}
layer.oitMethodDirty = false;
prepareReflectionProbesForRender();
wasDirty = wasDirty || wasDataDirty;
layerPrepResult.flags.setWasDirty(wasDirty);
layerPrepResult.flags.setLayerDataDirty(wasDataDirty);
//
const bool animating = wasDirty;
if (animating)
layer.progAAPassIndex = 0;
const bool progressiveAA = layer.isProgressiveAAEnabled() && !animating;
layer.progressiveAAIsActive = progressiveAA;
const bool temporalAA = layer.isTemporalAAEnabled() && !progressiveAA;
layer.temporalAAIsActive = temporalAA;
QVector2D vertexOffsetsAA;
if (progressiveAA && layer.progAAPassIndex > 0 && layer.progAAPassIndex < quint32(layer.antialiasingQuality)) {
int idx = layer.progAAPassIndex - 1;
vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
}
if (temporalAA) {
if (layer.temporalAAMode == QSSGRenderLayer::TAAMode::MotionVector && layer.tempAAPassIndex > 0) {
if (layer.tempAAPassIndex >= quint32(MAX_AA_LEVELS) + 1)
layer.tempAAPassIndex = 1;
int idx = layer.tempAAPassIndex - 1;
vertexOffsetsAA = s_ProgressiveAAVertexOffsets[idx] / QVector2D{ float(theViewport.width()/2.0), float(theViewport.height()/2.0) };
layer.currentAndLastJitter = QVector4D(vertexOffsetsAA, layer.currentAndLastJitter.x(), layer.currentAndLastJitter.y());
layerPrepResult.flags.setRequiresMotionVectorPass(true);
layerPrepResult.flags.setRequiresDepthTexture(true);
}else {
const int t = 1 - 2 * (layer.tempAAPassIndex % 2);
const float f = t * layer.temporalAAStrength;
vertexOffsetsAA = { f / float(theViewport.width()/2.0), f / float(theViewport.height()/2.0) };
}
}
if (!renderedCameras.isEmpty()) {
if (temporalAA || progressiveAA /*&& !vertexOffsetsAA.isNull()*/) {
QMatrix4x4 offsetProjection = renderedCameras[0]->projection;
QMatrix4x4 invProjection = renderedCameras[0]->projection.inverted();
if (renderedCameras[0]->type == QSSGRenderCamera::Type::OrthographicCamera) {
offsetProjection(0, 3) -= vertexOffsetsAA.x();
offsetProjection(1, 3) -= vertexOffsetsAA.y();
} else if (renderedCameras[0]->type == QSSGRenderCamera::Type::PerspectiveCamera) {
offsetProjection(0, 2) += vertexOffsetsAA.x();
offsetProjection(1, 2) += vertexOffsetsAA.y();
}
for (auto &modelContext : std::as_const(modelContexts)) {
for (int mvpIdx = 0; mvpIdx < renderedCameras.count(); ++mvpIdx)
modelContext->modelViewProjections[mvpIdx] = offsetProjection * invProjection * modelContext->modelViewProjections[mvpIdx];
}
}
}
const bool hasItem2Ds = (item2DsView.size() > 0);
const bool layerEnableDepthTest = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthTest);
const bool layerEnabledDepthPrePass = layer.layerFlags.testFlag(QSSGRenderLayer::LayerFlag::EnableDepthPrePass);
const bool depthTestEnableDefault = layerEnableDepthTest && (!opaqueObjects.isEmpty() || depthPrepassObjectsState || hasDepthWriteObjects);
const bool zPrePassForced = (depthPrepassObjectsState != 0);
zPrePassActive = zPrePassForced || (layerEnabledDepthPrePass && layerEnableDepthTest && (hasDepthWriteObjects || hasItem2Ds));
const bool depthWriteEnableDefault = depthTestEnableDefault && (!layerEnabledDepthPrePass || !zPrePassActive);
ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled, depthTestEnableDefault);
ps.flags.setFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled, depthWriteEnableDefault);
// All data prep is done, from now on the layer content shouldn't change.
layerPrepResult.setState(QSSGLayerRenderPreparationResult::State::Done);
// Prepare passes
QSSG_ASSERT(activePasses.isEmpty(), activePasses.clear());
// If needed, generate a depth texture with the opaque objects. This
// and normal and the SSAO texture must come first since other passes may want to
// expose these textures to their shaders.
if (layerPrepResult.flags.requiresDepthTexture())
activePasses.push_back(&depthMapPass);
if (layerPrepResult.flags.requiresDepthTextureMS())
activePasses.push_back(&depthMapPassMS);
if (layerPrepResult.flags.requiresNormalTexture())
activePasses.push_back(&normalPass);
// Screen space ambient occlusion. Relies on the depth texture and generates an AO map.
if (layerPrepResult.flags.requiresSsaoPass())
activePasses.push_back(&ssaoMapPass);
// Shadows. Generates a 2D or cube shadow map. (opaque + pre-pass transparent objects)
if (layerPrepResult.flags.requiresShadowMapPass())
activePasses.push_back(&shadowMapPass);
if (zPrePassActive && !disableMainPasses)
activePasses.push_back(&zPrePassPass);
// Screen texture with opaque objects.
if (layerPrepResult.flags.requiresScreenTexture())
activePasses.push_back(&screenMapPass);
// Layer has SkyMaterial.
if (layerPrepResult.flags.requiresSkyMaterialPass())
activePasses.push_back(&skyMaterialPass);
// Reflection pass
if (reflectionProbesView.size() != 0)
activePasses.push_back(&reflectionMapPass);
auto &textureExtensionPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::TextureProviders)];
if (textureExtensionPass.hasData())
activePasses.push_back(&textureExtensionPass);
auto &underlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Underlay)];
if (underlayPass.hasData())
activePasses.push_back(&underlayPass);
// Generated User render passes (QML)
if (userRenderPasses.hasData())
activePasses.push_back(&userRenderPasses);
const bool hasOpaqueObjects = (opaqueObjects.size() > 0);
if (hasOpaqueObjects && !disableMainPasses)
activePasses.push_back(&opaquePass);
// MotionVector Pass
if (layerPrepResult.flags.requiresMotionVectorPass())
activePasses.push_back(&motionVectorMapPass);
// NOTE: When the a screen texture is used, the skybox pass will be called twice. First from
// the screen texture pass and later as part of the normal run through the list.
if (renderer->contextInterface()->rhiContext()->rhi()->isFeatureSupported(QRhi::TexelFetch)) {
if (layer.background == QSSGRenderLayer::Background::SkyBoxCubeMap && layer.skyBoxCubeMap && !disableMainPasses)
activePasses.push_back(&skyboxCubeMapPass);
else if (layer.background == QSSGRenderLayer::Background::SkyBox && layer.lightProbe && !disableMainPasses)
activePasses.push_back(&skyboxPass);
else if (layer.background == QSSGRenderLayer::Background::SkyMaterial && layer.skyMaterial && !disableMainPasses) {
if (layer.skyMaterial->skyboxMode != QSSGRenderSkyMaterial::SkyboxMode::Cubemap)
activePasses.push_back(&skyMaterialBackgroundPass);
else
activePasses.push_back(&skyboxPass);
}
}
if (hasItem2Ds && !disableMainPasses)
activePasses.push_back(&item2DPass);
if (layerPrepResult.flags.requiresScreenTexture())
activePasses.push_back(&reflectionPass);
// Note: Transparent pass includeds opaque objects when layerEnableDepthTest is false.
if ((transparentObjects.size() > 0 || (!layerEnableDepthTest && hasOpaqueObjects)) && !disableMainPasses) {
if (orderIndependentTransparencyEnabled) {
activePasses.push_back(&oitRenderPass);
activePasses.push_back(&oitCompositePass);
oitRenderPass.setMethod(layer.oitMethod);
oitCompositePass.setMethod(layer.oitMethod);
} else {
activePasses.push_back(&transparentPass);
}
}
auto &overlayPass = userPasses[size_t(QSSGRenderLayer::RenderExtensionStage::Overlay)];
if (overlayPass.hasData())
activePasses.push_back(&overlayPass);
if (layer.gridEnabled)
activePasses.push_back(&infiniteGridPass);
if (const auto &dbgDrawSystem = renderer->contextInterface()->debugDrawSystem(); dbgDrawSystem && dbgDrawSystem->isEnabled() && !disableMainPasses)
activePasses.push_back(&debugDrawPass);
}
template<typename T>
static void clearTable(std::vector<T> &entry)
{
for (auto &e : entry)
e.clear();
}
void QSSGLayerRenderData::resolveLayerIblTexture(QSSGPassKey passKey)
{
if (layer.skyMaterial) {
// In the ScreenSpace skybox modes the visible background is evaluated directly on
// screen (SkyMaterialBackgroundPass), so the cube is only produced when something
// actually consumes it: image-based lighting, reflection probes, the cube-sampling
// background (Cubemap mode), or a screen-read texture (SkyboxPass draws the cube
// into it for e.g. refraction).
const bool needCube = layer.skyMaterial->enableIBL
|| layer.skyMaterial->skyboxMode == QSSGRenderSkyMaterial::SkyboxMode::Cubemap
|| reflectionProbesView.size() != 0 || layerPrepResult.flags.requiresScreenTexture();
if (needCube)
skyMaterialTexture = requestSkyMaterialManager()->resolve(layer.skyMaterial, passKey);
else
skyMaterialTexture = {};
}
}
void QSSGLayerRenderData::resetForFrame()
{
for (const auto &pass : activePasses)
pass->resetForFrame();
activePasses.clear();
bakedLightingModels.clear();
layerPrepResult = {};
renderedCameraData.reset();
renderedItem2Ds.clear();
renderedBakedLightingModels.clear();
lightmapTextures.clear();
bonemapTextures.clear();
globalLights.clear();
modelContexts.clear();
features = QSSGShaderFeatures();
skyMaterialTexture = { };
hasDepthWriteObjects = false;
depthPrepassObjectsState = { DepthPrepassObjectStateT(DepthPrepassObject::None) };
zPrePassActive = false;
savedRenderState.reset();
clearTable(renderableModelStore);
clearTable(modelContextStore);
clearTable(renderableObjectStore);
clearTable(opaqueObjectStore);
clearTable(transparentObjectStore);
clearTable(screenTextureObjectStore);
clearTable(sortedOpaqueObjectCache);
clearTable(sortedTransparentObjectCache);
clearTable(sortedScreenTextureObjectCache);
clearTable(sortedOpaqueDepthPrepassCache);
clearTable(sortedDepthWriteCache);
// Until we have a better solution for extensions...
if (renderablesModifiedByExtension) {
renderableModels.clear();
renderableParticles.clear();
renderableModels.reserve(modelsView.size());
renderableParticles.reserve(particlesView.size());
renderableModels = {modelsView.begin(), modelsView.end()};
renderableParticles = {particlesView.begin(), particlesView.end()};
renderablesModifiedByExtension = false;
}
if (userRenderPassManager)
userRenderPassManager->resetForFrame();
}
QSSGLayerRenderPreparationResult::QSSGLayerRenderPreparationResult(const QRectF &inViewport, QSSGRenderLayer &inLayer)
: layer(&inLayer)
, m_state(State::DataPrep)
{
viewport = inViewport;
}
bool QSSGLayerRenderPreparationResult::isLayerVisible() const
{
return viewport.height() >= 2.0f && viewport.width() >= 2.0f;
}
QSize QSSGLayerRenderPreparationResult::textureDimensions() const
{
const auto size = viewport.size().toSize();
return QSize(QSSGRendererUtil::nextMultipleOf4(size.width()), QSSGRendererUtil::nextMultipleOf4(size.height()));
}
QSSGLayerRenderData::QSSGLayerRenderData(QSSGRenderLayer &inLayer, QSSGRenderer &inRenderer)
: layer(inLayer)
, renderer(&inRenderer)
, orderIndependentTransparencyEnabled(false)
, particlesEnabled(checkParticleSupport(inRenderer.contextInterface()->rhi()))
{
depthMapPassMS.setMultisamplingEnabled(true);
Q_ASSERT(extContexts.size() == 1);
// Set-up the world root node and create the data store for the models.
auto *root = layer.rootNode;
nodeData = root->globalNodeData();
modelData = std::make_unique<QSSGRenderModelData>(nodeData);
item2DData = std::make_unique<QSSGRenderItem2DData>(nodeData);
inRenderer.registerItem2DData(*item2DData);
}
QSSGLayerRenderData::~QSSGLayerRenderData()
{
for (auto &renderResult : renderResults)
renderResult.reset();
oitRenderContext.reset();
if (userRenderPassManager)
userRenderPassManager->releaseAll();
// Draw call data for content rendered by this layer is keyed on the pass
// objects owned here; release those entries now so content that outlives
// the layer, e.g. models and materials in a shared import scene, does not
// leave stale entries behind as views come and go.
if (const auto &rhiCtx = renderer->contextInterface()->rhiContext(); rhiCtx && rhiCtx->isValid()) {
auto *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx.get());
const QSSGRenderPass *passes[] = { &shadowMapPass, &reflectionMapPass, &zPrePassPass, &ssaoMapPass,
&depthMapPass, &depthMapPassMS, &skyMaterialPass, &screenMapPass,
&reflectionPass, &item2DPass, &skyboxPass, &skyMaterialBackgroundPass,
&skyboxCubeMapPass, &userRenderPasses, &opaquePass, &transparentPass,
&oitRenderPass, &oitCompositePass, &infiniteGridPass, &debugDrawPass,
&normalPass, &motionVectorMapPass };
for (const QSSGRenderPass *pass : passes)
rhiCtxD->cleanupDrawCallDataForCid(pass);
for (const auto &userPass : userPasses)
rhiCtxD->cleanupDrawCallDataForCid(&userPass);
// Extension-driven renderables are keyed on the owning extension
// combined with the prep context slot (see prepareRenderables()), so
// release those per context as well.
for (const auto &extCtx : extContexts) {
if (extCtx.owner) {
const auto cid = reinterpret_cast<const void *>(quintptr(extCtx.owner) ^ extCtx.slot);
rhiCtxD->cleanupDrawCallDataForCid(cid);
}
}
}
renderer->unregisterItem2DData(*item2DData);
}
// Instance buffers are passed around as bytes: convert to entries in one place,
// clamped to what the buffer can actually hold.
static QSpan<const QSSGRenderInstanceTableEntry> instanceEntries(const void *data, qsizetype byteSize, int count)
{
const qsizetype available = byteSize / qsizetype(sizeof(QSSGRenderInstanceTableEntry));
return { reinterpret_cast<const QSSGRenderInstanceTableEntry *>(data),
qBound(qsizetype(0), qsizetype(count), available) };
}
static QSpan<QSSGRenderInstanceTableEntry> instanceEntries(QByteArray &data)
{
return { reinterpret_cast<QSSGRenderInstanceTableEntry *>(data.data()),
data.size() / qsizetype(sizeof(QSSGRenderInstanceTableEntry)) };
}
static void sortInstances(QSpan<QSSGRenderInstanceTableEntry> sortedInstances, QList<QSSGRhiSortData> &sortData,
QSpan<const QSSGRenderInstanceTableEntry> instances, const QVector3D &cameraDirection)
{
const int count = int(qMin(instances.size(), sortedInstances.size()));
sortData.resize(count);
// create sort data
for (int i = 0; i < count; ++i) {
const QSSGRenderInstanceTableEntry &instance = instances[i];
const QVector3D pos = QVector3D(instance.row0.w(), instance.row1.w(), instance.row2.w());
sortData[i] = {QVector3D::dotProduct(pos, cameraDirection), i};
}
// sort
std::sort(sortData.begin(), sortData.end(), [](const QSSGRhiSortData &a, const QSSGRhiSortData &b){
return a.d > b.d;
});
// copy instances
for (int i = 0; i < count; ++i)
sortedInstances[i] = instances[sortData[i].indexOrOffset];
}
static int cullLodInstances(QSpan<QSSGRenderInstanceTableEntry> lodInstances,
QSpan<const QSSGRenderInstanceTableEntry> instances,
const QVector3D &cameraPosition, float minThreshold, float maxThreshold)
{
const qsizetype count = qMin(instances.size(), lodInstances.size());
int realSize = 0;
for (qsizetype i = 0; i < count; ++i) {
const QSSGRenderInstanceTableEntry &instance = instances[i];
const float x = cameraPosition.x() - instance.row0.w();
const float y = cameraPosition.y() - instance.row1.w();
const float z = cameraPosition.z() - instance.row2.w();
const float distanceSq = x * x + y * y + z * z;
if (distanceSq >= minThreshold * minThreshold && (maxThreshold < 0 || distanceSq < maxThreshold * maxThreshold))
lodInstances[realSize++] = instance;
}
return realSize;
}
bool QSSGLayerRenderData::prepareInstancing(QSSGRhiContext *rhiCtx,
QSSGSubsetRenderable *renderable,
const QVector3D &cameraDirection,
const QVector3D &cameraPosition,
float minThreshold,
float maxThreshold)
{
QSSGRhiContextPrivate *rhiCtxD = QSSGRhiContextPrivate::get(rhiCtx);
auto &modelContext = renderable->modelContext;
auto &instanceBuffer = renderable->instanceBuffer; // intentional ref2ptr
const QMatrix4x4 &renderableGlobalTransform = renderable->modelContext.globalTransform;
if (!modelContext.model.instancing() || instanceBuffer)
return instanceBuffer;
auto *table = modelContext.model.instanceTable;
bool usesLod = minThreshold >= 0 || maxThreshold >= 0;
QSSGRhiInstanceBufferData &instanceData(usesLod ? rhiCtxD->instanceBufferData(&modelContext.model) : rhiCtxD->instanceBufferData(table));
quint32 instanceBufferSize = table->dataSize();
// Create or resize the instance buffer ### if (instanceData.owned)
bool sortingChanged = table->isDepthSortingEnabled() != instanceData.sorting;
bool cameraDirectionChanged = !qFuzzyCompare(instanceData.sortedCameraDirection, cameraDirection);
bool cameraPositionChanged = !qFuzzyCompare(instanceData.cameraPosition, cameraPosition);
bool updateInstanceBuffer = table->serial() != instanceData.serial || sortingChanged || (cameraDirectionChanged && table->isDepthSortingEnabled());
bool instanceBufferSizeChanged = instanceData.buffer && instanceBufferSize != instanceData.buffer->size();
bool updateForLod = (cameraPositionChanged || instanceBufferSizeChanged) && usesLod;
if (sortingChanged && !table->isDepthSortingEnabled()) {
instanceData.sortedData.clear();
instanceData.sortData.clear();
instanceData.sortedCameraDirection = {};
}
instanceData.sorting = table->isDepthSortingEnabled();
if (instanceData.buffer && instanceData.buffer->size() < instanceBufferSize) {
updateInstanceBuffer = true;
// qDebug() << "Resizing instance buffer";
instanceData.buffer->setSize(instanceBufferSize);
instanceData.buffer->create();
}
if (!instanceData.buffer) {
// qDebug() << "Creating instance buffer";
updateInstanceBuffer = true;
instanceData.buffer = rhiCtx->rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, instanceBufferSize);
instanceData.buffer->create();
}
if (updateInstanceBuffer || updateForLod) {
Q_ASSERT(table->stride() == sizeof(QSSGRenderInstanceTableEntry));
const void *data = nullptr;
if (table->isDepthSortingEnabled()) {
if (updateInstanceBuffer) {
QMatrix4x4 invGlobalTransform = renderableGlobalTransform.inverted();
instanceData.sortedData.resize(table->dataSize());
sortInstances(instanceEntries(instanceData.sortedData),
instanceData.sortData,
instanceEntries(table->constData(), table->dataSize(), table->count()),
invGlobalTransform.map(cameraDirection).normalized());
}
data = instanceData.sortedData.constData();
instanceData.sortedCameraDirection = cameraDirection;
} else {
data = table->constData();
}
if (data) {
if (updateForLod) {
// Either the depth sorted copy or the table itself, both with count() entries.
const auto instances = table->isDepthSortingEnabled()
? instanceEntries(instanceData.sortedData.constData(), instanceData.sortedData.size(), table->count())
: instanceEntries(table->constData(), table->dataSize(), table->count());
instanceData.lodData.resize(table->dataSize());
modelContext.model.instanceLodCount = cullLodInstances(instanceEntries(instanceData.lodData),
instances,
cameraPosition,
minThreshold,
maxThreshold);
data = instanceData.lodData.constData();
// Force clear the buffer to upload empty instance data.
// If this step is skipped, the updateDynamicBuffer function will load data for all instances.
if (!modelContext.model.instanceLodCount)
instanceData.lodData.clear();
instanceBufferSize = modelContext.model.instanceLodCount * sizeof(QSSGRenderInstanceTableEntry);
}
QRhiResourceUpdateBatch *rub = rhiCtx->rhi()->nextResourceUpdateBatch();
rub->updateDynamicBuffer(instanceData.buffer, 0, instanceBufferSize, data);
rhiCtx->commandBuffer()->resourceUpdate(rub);
//qDebug() << "****** UPDATING INST BUFFER. Size" << instanceBufferSize;
} else {
qWarning() << "NO DATA IN INSTANCE TABLE";
}
instanceData.serial = table->serial();
instanceData.cameraPosition = cameraPosition;
}
instanceBuffer = instanceData.buffer;
return instanceBuffer;
}
QSSGFrameData &QSSGLayerRenderData::getFrameData()
{
return frameData;
}
void QSSGLayerRenderData::initializeLightmapBaking(QSSGLightmapBaker::Context &ctx)
{
ctx.callbacks.modelsToBake = [this]() { return getSortedBakedLightingModels(); };
lightmapBaker = std::make_unique<QSSGLightmapBaker>(ctx);
}
void QSSGLayerRenderData::maybeProcessLightmapBaking()
{
if (lightmapBaker) {
const QSSGLightmapBaker::Status status = lightmapBaker->process();
if (status == QSSGLightmapBaker::Status::Finished)
lightmapBaker.reset();
}
}
QSSGRenderGraphObject *QSSGLayerRenderData::getCamera(QSSGCameraId id) const
{
QSSGRenderGraphObject *ret = nullptr;
if (auto res = reinterpret_cast<QSSGRenderGraphObject *>(id))
ret = res;
return ret;
}
QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_)
{
if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
return renderedCameraData.value()[0];
if (camera_)
return getCameraDataImpl(camera_);
return {};
}
QSSGRenderCameraData QSSGLayerRenderData::getCameraRenderData(const QSSGRenderCamera *camera_) const
{
if ((!camera_ || camera_ == renderedCameras[0]) && renderedCameraData.has_value())
return renderedCameraData.value()[0];
if (camera_)
return getCameraDataImpl(camera_);
return {};
}
QSSGRenderContextInterface *QSSGLayerRenderData::contextInterface() const
{
return renderer ? renderer->contextInterface() : nullptr;
}
QSSGLayerRenderData::GlobalRenderProperties QSSGLayerRenderData::globalRenderProperties(const QSSGRenderContextInterface &ctx)
{
GlobalRenderProperties props {};
if (const auto &rhiCtx = ctx.rhiContext(); rhiCtx->isValid()) {
QRhi *rhi = rhiCtx->rhi();
props.isYUpInFramebuffer = rhi->isYUpInFramebuffer();
props.isYUpInNDC = rhi->isYUpInNDC();
props.isClipDepthZeroToOne = rhi->isClipDepthZeroToOne();
}
return props;
}
const QSSGRenderShadowMapPtr &QSSGLayerRenderData::requestShadowMapManager()
{
if (!shadowMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
shadowMapManager.reset(new QSSGRenderShadowMap(*renderer->contextInterface()));
return shadowMapManager;
}
const QSSGRenderReflectionMapPtr &QSSGLayerRenderData::requestReflectionMapManager()
{
if (!reflectionMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
reflectionMapManager.reset(new QSSGRenderReflectionMap(*renderer->contextInterface()));
return reflectionMapManager;
}
const QSSGRenderSkyMaterialManagerPtr &QSSGLayerRenderData::requestSkyMaterialManager()
{
if (!skyMaterialManager && QSSG_GUARD(renderer && renderer->contextInterface()))
skyMaterialManager.reset(new QSSGRenderSkyMaterialManager(*renderer->contextInterface()));
return skyMaterialManager;
}
const QSSGUserRenderPassManagerPtr &QSSGLayerRenderData::requestUserRenderPassManager()
{
if (!userRenderPassManager) {
userRenderPassManager = QSSGUserRenderPassManager::create();
renderer->contextInterface()->bufferManager()->registerUserRenderPassManager(userRenderPassManager);
}
return userRenderPassManager;
}
const QSSGRenderMotionVectorMapPtr &QSSGLayerRenderData::requestMotionVectorMapManager()
{
if (!motionVectorMapManager && QSSG_GUARD(renderer && renderer->contextInterface()))
motionVectorMapManager.reset(new QSSGRenderMotionVectorMap(*renderer->contextInterface()));
return motionVectorMapManager;
}
QT_END_NAMESPACE