blob: 4e83decff12c8e491ab95a4c2a00b4d91fd1e2de [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendererimpl_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlayer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendereffect_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderresourcemanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendereffectsystem_p.h>
#include <QtQuick3DRender/private/qssgrenderframebuffer_p.h>
#include <QtQuick3DRender/private/qssgrenderrenderbuffer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderresourcebufferobjects_p.h>
#include <QtQuick3DUtils/private/qssgperftimer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendercustommaterialsystem_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrendershadercache_p.h>
#include <QtQuick3DRuntimeRender/private/qssgperframeallocator_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
#ifdef Q_CC_MSVC
#pragma warning(disable : 4355)
#endif
QT_BEGIN_NAMESPACE
static void maybeQueueNodeForRender(QSSGRenderNode &inNode,
QVector<QSSGRenderableNodeEntry> &outRenderables,
QVector<QSSGRenderCamera *> &outCameras,
QVector<QSSGRenderLight *> &outLights,
quint32 &ioDFSIndex)
{
++ioDFSIndex;
inNode.dfsIndex = ioDFSIndex;
if (inNode.isRenderableType())
outRenderables.push_back(inNode);
else if (inNode.type == QSSGRenderGraphObject::Type::Camera)
outCameras.push_back(static_cast<QSSGRenderCamera *>(&inNode));
else if (inNode.type == QSSGRenderGraphObject::Type::Light)
outLights.push_back(static_cast<QSSGRenderLight *>(&inNode));
for (QSSGRenderNode *theChild = inNode.firstChild; theChild != nullptr; theChild = theChild->nextSibling)
maybeQueueNodeForRender(*theChild, outRenderables, outCameras, outLights, ioDFSIndex);
}
static inline bool hasValidLightProbe(QSSGRenderImage *inLightProbeImage)
{
return inLightProbeImage && inLightProbeImage->m_textureData.m_texture;
}
QSSGDefaultMaterialPreparationResult::QSSGDefaultMaterialPreparationResult(QSSGShaderDefaultMaterialKey inKey)
: firstImage(nullptr), opacity(1.0f), materialKey(inKey), dirty(false)
{
}
#define MAX_AA_LEVELS 8
QSSGLayerRenderPreparationData::QSSGLayerRenderPreparationData(QSSGRenderLayer &inLayer,
const QSSGRef<QSSGRendererImpl> &inRenderer)
: layer(inLayer)
, renderer(inRenderer)
, camera(nullptr)
, featuresDirty(true)
, featureSetHash(0)
, tooManyLightsError(false)
{
}
QSSGLayerRenderPreparationData::~QSSGLayerRenderPreparationData() = default;
void QSSGLayerRenderPreparationData::setShaderFeature(const char *theStr, bool inValue)
{
auto iter = features.cbegin();
const auto end = features.cend();
while (iter != end && iter->name != theStr)
++iter;
if (iter != end) {
if (iter->enabled != inValue) {
iter->enabled = inValue;
featuresDirty = true;
featureSetHash = 0;
}
} else {
features.push_back(QSSGShaderPreprocessorFeature{theStr, inValue});
featuresDirty = true;
featureSetHash = 0;
}
}
ShaderFeatureSetList QSSGLayerRenderPreparationData::getShaderFeatureSet()
{
if (featuresDirty) {
std::sort(features.begin(), features.end());
featuresDirty = false;
}
return features;
}
size_t QSSGLayerRenderPreparationData::getShaderFeatureSetHash()
{
if (!featureSetHash)
featureSetHash = hashShaderFeatureSet(getShaderFeatureSet());
return featureSetHash;
}
void QSSGLayerRenderPreparationData::createShadowMapManager()
{
shadowMapManager = QSSGRenderShadowMap::create(renderer->contextInterface());
}
QVector3D QSSGLayerRenderPreparationData::getCameraDirection()
{
if (!cameraDirection.hasValue()) {
if (camera)
cameraDirection = camera->getScalingCorrectDirection();
else
cameraDirection = QVector3D(0, 0, -1);
}
return *cameraDirection;
}
// Per-frame cache of renderable objects post-sort.
const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderPreparationData::getOpaqueRenderableObjects(bool performSort)
{
if (!renderedOpaqueObjects.empty() || camera == nullptr)
return renderedOpaqueObjects;
if (layer.flags.testFlag(QSSGRenderLayer::Flag::LayerEnableDepthTest) && !opaqueObjects.empty()) {
QVector3D theCameraDirection(getCameraDirection());
QVector3D theCameraPosition = camera->getGlobalPos();
renderedOpaqueObjects = opaqueObjects;
// Setup the object's sorting information
for (int idx = 0, end = renderedOpaqueObjects.size(); idx < end; ++idx) {
QSSGRenderableObjectHandle &theInfo = renderedOpaqueObjects[idx];
const QVector3D difference = theInfo.obj->worldCenterPoint - theCameraPosition;
theInfo.cameraDistanceSq = QVector3D::dotProduct(difference, theCameraDirection);
}
static const auto isRenderObjectPtrLessThan = [](const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) {
return lhs.cameraDistanceSq < rhs.cameraDistanceSq;
};
// Render nearest to furthest objects
if (performSort)
std::sort(renderedOpaqueObjects.begin(), renderedOpaqueObjects.end(), isRenderObjectPtrLessThan);
}
return renderedOpaqueObjects;
}
// If layer depth test is false, this may also contain opaque objects.
const QVector<QSSGRenderableObjectHandle> &QSSGLayerRenderPreparationData::getTransparentRenderableObjects()
{
if (!renderedTransparentObjects.empty() || camera == nullptr)
return renderedTransparentObjects;
renderedTransparentObjects = transparentObjects;
if (!layer.flags.testFlag(QSSGRenderLayer::Flag::LayerEnableDepthTest))
renderedTransparentObjects.append(opaqueObjects);
if (!renderedTransparentObjects.empty()) {
QVector3D theCameraDirection(getCameraDirection());
QVector3D theCameraPosition = camera->getGlobalPos();
// Setup the object's sorting information
for (quint32 idx = 0, end = renderedTransparentObjects.size(); idx < end; ++idx) {
QSSGRenderableObjectHandle &theInfo = renderedTransparentObjects[idx];
const QVector3D difference = theInfo.obj->worldCenterPoint - theCameraPosition;
theInfo.cameraDistanceSq = QVector3D::dotProduct(difference, theCameraDirection);
}
static const auto iSRenderObjectPtrGreatThan = [](const QSSGRenderableObjectHandle &lhs, const QSSGRenderableObjectHandle &rhs) {
return lhs.cameraDistanceSq > rhs.cameraDistanceSq;
};
// render furthest to nearest.
std::sort(renderedTransparentObjects.begin(), renderedTransparentObjects.end(), iSRenderObjectPtrGreatThan);
}
return renderedTransparentObjects;
}
const QVector<QSSGRenderableNodeEntry> &QSSGLayerRenderPreparationData::getRenderableItem2Ds()
{
if (!renderedItem2Ds.isEmpty() || camera == nullptr)
return renderedItem2Ds;
renderedItem2Ds = renderableItem2Ds;
const QVector3D cameraDirection(getCameraDirection());
const QVector3D cameraPosition = camera->getGlobalPos();
const auto isItemNodeDistanceGreatThan = [cameraDirection, cameraPosition]
(const QSSGRenderableNodeEntry &lhs, const QSSGRenderableNodeEntry &rhs) {
if (!lhs.node->parent || !rhs.node->parent)
return false;
const QVector3D lhsDifference = lhs.node->parent->position - cameraPosition;
const float lhsCameraDistanceSq = QVector3D::dotProduct(lhsDifference, cameraDirection);
const QVector3D rhsDifference = rhs.node->parent->position - cameraPosition;
const float rhsCameraDistanceSq = QVector3D::dotProduct(rhsDifference, cameraDirection);
return lhsCameraDistanceSq > rhsCameraDistanceSq;
};
const auto isItemZOrderLessThan = []
(const QSSGRenderableNodeEntry &lhs, const QSSGRenderableNodeEntry &rhs) {
if (lhs.node->parent && rhs.node->parent && lhs.node->parent == rhs.node->parent) {
// Same parent nodes, so sort with item z-ordering
QSSGRenderItem2D *lhsItem = static_cast<QSSGRenderItem2D *>(lhs.node);
QSSGRenderItem2D *rhsItem = static_cast<QSSGRenderItem2D *>(rhs.node);
return lhsItem->zOrder < rhsItem->zOrder;
}
return false;
};
// Render furthest to nearest items (parent nodes).
std::stable_sort(renderedItem2Ds.begin(), renderedItem2Ds.end(), isItemNodeDistanceGreatThan);
// Render items inside same node by item z-order.
// Note: stable_sort so item order in QML file is respected.
std::stable_sort(renderedItem2Ds.begin(), renderedItem2Ds.end(), isItemZOrderLessThan);
return renderedItem2Ds;
}
/**
* 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>
inline T *RENDER_FRAME_NEW(const QSSGRef<QSSGRenderContextInterface> &ctx, const Args&... args)
{
return new (ctx->perFrameAllocator().allocate(sizeof(T)))T(const_cast<Args &>(args)...);
}
QSSGShaderDefaultMaterialKey QSSGLayerRenderPreparationData::generateLightingKey(QSSGRenderDefaultMaterial::MaterialLighting inLightingType, bool receivesShadows)
{
const uint features = uint(getShaderFeatureSetHash());
QSSGShaderDefaultMaterialKey theGeneratedKey(features);
const bool lighting = inLightingType != QSSGRenderDefaultMaterial::MaterialLighting::NoLighting;
renderer->defaultMaterialShaderKeyProperties().m_hasLighting.setValue(theGeneratedKey, lighting);
if (lighting) {
const bool lightProbe = layer.lightProbe && layer.lightProbe->m_textureData.m_texture;
renderer->defaultMaterialShaderKeyProperties().m_hasIbl.setValue(theGeneratedKey, lightProbe);
quint32 numLights = quint32(globalLights.size());
if (Q_UNLIKELY(numLights > QSSGShaderDefaultMaterialKeyProperties::LightCount && !tooManyLightsError)) {
tooManyLightsError = true;
numLights = QSSGShaderDefaultMaterialKeyProperties::LightCount;
qCCritical(INVALID_OPERATION, "Too many lights on layer, max is %d", QSSGShaderDefaultMaterialKeyProperties::LightCount);
Q_ASSERT(false);
}
renderer->defaultMaterialShaderKeyProperties().m_lightCount.setValue(theGeneratedKey, numLights);
for (qint32 lightIdx = 0, lightEnd = globalLights.size(); lightIdx < lightEnd; ++lightIdx) {
QSSGRenderLight *theLight(globalLights[lightIdx]);
const bool isDirectional = theLight->m_lightType == QSSGRenderLight::Type::Directional;
const bool isArea = theLight->m_lightType == QSSGRenderLight::Type::Area;
const bool isSpot = theLight->m_lightType == QSSGRenderLight::Type::Spot;
const bool castShadowsArea = (theLight->m_lightType != QSSGRenderLight::Type::Area) && (theLight->m_castShadow) && receivesShadows;
renderer->defaultMaterialShaderKeyProperties().m_lightFlags[lightIdx].setValue(theGeneratedKey, !isDirectional);
renderer->defaultMaterialShaderKeyProperties().m_lightAreaFlags[lightIdx].setValue(theGeneratedKey, isArea);
renderer->defaultMaterialShaderKeyProperties().m_lightSpotFlags[lightIdx].setValue(theGeneratedKey, isSpot);
renderer->defaultMaterialShaderKeyProperties().m_lightShadowFlags[lightIdx].setValue(theGeneratedKey, castShadowsArea);
}
}
return theGeneratedKey;
}
void QSSGLayerRenderPreparationData::prepareImageForRender(QSSGRenderImage &inImage,
QSSGImageMapTypes inMapType,
QSSGRenderableImage *&ioFirstImage,
QSSGRenderableImage *&ioNextImage,
QSSGRenderableObjectFlags &ioFlags,
QSSGShaderDefaultMaterialKey &inShaderKey,
quint32 inImageIndex,
QSSGRenderDefaultMaterial *inMaterial)
{
const QSSGRef<QSSGRenderContextInterface> &contextInterface(renderer->contextInterface());
const QSSGRef<QSSGBufferManager> &bufferManager = contextInterface->bufferManager();
if (inImage.clearDirty(bufferManager))
ioFlags |= QSSGRenderableObjectFlag::Dirty;
if (inImage.m_textureData.m_texture) {
if (inImage.m_textureData.m_textureFlags.hasTransparency()
&& (inMapType == QSSGImageMapTypes::Diffuse || inMapType == QSSGImageMapTypes::Opacity
|| inMapType == QSSGImageMapTypes::Translucency)) {
ioFlags |= QSSGRenderableObjectFlag::HasTransparency;
}
// Textures used in general have linear characteristics.
// PKC -- The filters are properly set already. Setting them here only overrides what
// would
// otherwise be a correct setting.
// inImage.m_TextureData.m_Texture->SetMinFilter( QSSGRenderTextureMinifyingOp::Linear );
// inImage.m_TextureData.m_Texture->SetMagFilter( QSSGRenderTextureMagnifyingOp::Linear );
QSSGRenderableImage *theImage = RENDER_FRAME_NEW<QSSGRenderableImage>(renderer->contextInterface(), inMapType, inImage);
QSSGShaderKeyImageMap &theKeyProp = renderer->defaultMaterialShaderKeyProperties().m_imageMaps[inImageIndex];
theKeyProp.setEnabled(inShaderKey, true);
switch (inImage.m_mappingMode) {
default:
Q_ASSERT(false);
// fallthrough intentional
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;
switch (inImage.m_textureData.m_texture->textureDetails().format.format) {
case QSSGRenderTextureFormat::RG8:
case QSSGRenderTextureFormat::RG16F:
case QSSGRenderTextureFormat::RG32F:
hasG = true;
break;
case QSSGRenderTextureFormat::RGB8:
case QSSGRenderTextureFormat::RGB16F:
case QSSGRenderTextureFormat::RGB32F:
hasG = true;
hasB = true;
break;
case QSSGRenderTextureFormat::Alpha8:
hasA = true;
break;
case QSSGRenderTextureFormat::LuminanceAlpha8:
hasA = true;
hasG = true;
break;
default:
hasA = true;
hasG = true;
hasB = true;
break;
}
if (inImage.m_textureData.m_textureFlags.isInvertUVCoords())
theKeyProp.setInvertUVMap(inShaderKey, true);
if (inImage.isImageTransformIdentity())
theKeyProp.setIdentityTransform(inShaderKey, true);
if (ioFirstImage == nullptr)
ioFirstImage = theImage;
else
ioNextImage->m_nextImage = theImage;
// assume offscreen renderer produces non-premultiplied image
if (inImage.m_textureData.m_textureFlags.isPreMultiplied())
theKeyProp.setPremultiplied(inShaderKey, true);
QSSGShaderKeyTextureSwizzle &theSwizzleKeyProp = renderer->defaultMaterialShaderKeyProperties().m_textureSwizzle[inImageIndex];
theSwizzleKeyProp.setSwizzleMode(inShaderKey, inImage.m_textureData.m_texture->textureSwizzleMode(), true);
ioNextImage = theImage;
if (inMaterial && inImageIndex >= QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst) {
QSSGRenderDefaultMaterial::TextureChannelMapping value = QSSGRenderDefaultMaterial::R;
QSSGRenderDefaultMaterial::TextureChannelMapping defaultValues[5] = {QSSGRenderDefaultMaterial::R, QSSGRenderDefaultMaterial::G, QSSGRenderDefaultMaterial::B, QSSGRenderDefaultMaterial::R, QSSGRenderDefaultMaterial::A};
if (inMaterial->type == QSSGRenderGraphObject::Type::DefaultMaterial)
defaultValues[1] = defaultValues[2] = QSSGRenderDefaultMaterial::R;
const quint32 scIndex = inImageIndex - QSSGShaderDefaultMaterialKeyProperties::SingleChannelImagesFirst;
QSSGShaderKeyTextureChannel &channelKey = renderer->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;
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 = defaultValues[scIndex];
channelKey.setTextureChannel(QSSGShaderKeyTextureChannel::TexturChannelBits(value), inShaderKey);
}
}
}
void QSSGLayerRenderPreparationData::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.hasAttributeTangent())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Tangent;
if (renderableFlags.hasAttributeBinormal())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Binormal;
if (renderableFlags.hasAttributeColor())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Color;
renderer->defaultMaterialShaderKeyProperties().m_vertexAttributes.setValue(key, vertexAttribs);
}
QSSGDefaultMaterialPreparationResult QSSGLayerRenderPreparationData::prepareDefaultMaterialForRender(
QSSGRenderDefaultMaterial &inMaterial,
QSSGRenderableObjectFlags &inExistingFlags,
float inOpacity)
{
QSSGRenderDefaultMaterial *theMaterial = &inMaterial;
QSSGDefaultMaterialPreparationResult retval(generateLightingKey(theMaterial->lighting, inExistingFlags.receivesShadows()));
retval.renderableFlags = inExistingFlags;
QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
retval.opacity = inOpacity;
float &subsetOpacity(retval.opacity);
if (theMaterial->dirty.isDirty()) {
renderableFlags |= QSSGRenderableObjectFlag::Dirty;
}
subsetOpacity *= theMaterial->opacity;
QSSGRenderableImage *firstImage = nullptr;
// set wireframe mode
renderer->defaultMaterialShaderKeyProperties().m_wireframeMode.setValue(theGeneratedKey,
renderer->contextInterface()->wireframeMode());
// isDoubleSided
renderer->defaultMaterialShaderKeyProperties().m_isDoubleSided.setValue(theGeneratedKey, theMaterial->cullMode == QSSGCullFaceMode::Disabled);
// alpha Mode
renderer->defaultMaterialShaderKeyProperties().m_alphaMode.setValue(theGeneratedKey, theMaterial->alphaMode);
// vertex attribute presence flags
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.hasAttributeTangent())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Tangent;
if (renderableFlags.hasAttributeBinormal())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Binormal;
if (renderableFlags.hasAttributeColor())
vertexAttribs |= QSSGShaderKeyVertexAttribute::Color;
renderer->defaultMaterialShaderKeyProperties().m_vertexAttributes.setValue(theGeneratedKey, vertexAttribs);
if (theMaterial->iblProbe && checkLightProbeDirty(*theMaterial->iblProbe)) {
renderer->prepareImageForIbl(*theMaterial->iblProbe);
}
if (!renderer->defaultMaterialShaderKeyProperties().m_hasIbl.getValue(theGeneratedKey)) {
bool lightProbeValid = hasValidLightProbe(theMaterial->iblProbe);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::LightProbe), lightProbeValid);
renderer->defaultMaterialShaderKeyProperties().m_hasIbl.setValue(theGeneratedKey, lightProbeValid);
// setShaderFeature(ShaderFeatureDefines::enableIblFov(),
// m_Renderer.GetLayerRenderData()->m_Layer.m_ProbeFov < 180.0f );
}
if (subsetOpacity >= QSSG_RENDER_MINIMUM_RENDER_OPACITY) {
if (theMaterial->blendMode != QSSGRenderDefaultMaterial::MaterialBlendMode::SourceOver ||
theMaterial->opacityMap ||
theMaterial->alphaMode == QSSGRenderDefaultMaterial::Blend ||
theMaterial->alphaMode == QSSGRenderDefaultMaterial::Mask) {
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
}
const bool specularEnabled = theMaterial->isSpecularEnabled();
const bool metalnessEnabled = theMaterial->isMetalnessEnabled();
renderer->defaultMaterialShaderKeyProperties().m_specularEnabled.setValue(theGeneratedKey, (specularEnabled || metalnessEnabled));
if (specularEnabled || metalnessEnabled)
renderer->defaultMaterialShaderKeyProperties().m_specularModel.setSpecularModel(theGeneratedKey, theMaterial->specularModel);
renderer->defaultMaterialShaderKeyProperties().m_fresnelEnabled.setValue(theGeneratedKey, theMaterial->isFresnelEnabled());
renderer->defaultMaterialShaderKeyProperties().m_vertexColorsEnabled.setValue(theGeneratedKey,
theMaterial->isVertexColorsEnabled());
// 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) {
CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
QSSGImageMapTypes::BaseColor,
QSSGShaderDefaultMaterialKeyProperties::BaseColorMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->metalnessMap,
QSSGImageMapTypes::Metalness,
QSSGShaderDefaultMaterialKeyProperties::MetalnessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->occlusionMap,
QSSGImageMapTypes::Occlusion,
QSSGShaderDefaultMaterialKeyProperties::OcclusionMap);
} else {
CHECK_IMAGE_AND_PREPARE(theMaterial->colorMap,
QSSGImageMapTypes::Diffuse,
QSSGShaderDefaultMaterialKeyProperties::DiffuseMap);
}
CHECK_IMAGE_AND_PREPARE(theMaterial->emissiveMap, QSSGImageMapTypes::Emissive, QSSGShaderDefaultMaterialKeyProperties::EmissiveMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->specularReflection,
QSSGImageMapTypes::Specular,
QSSGShaderDefaultMaterialKeyProperties::SpecularMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->roughnessMap,
QSSGImageMapTypes::Roughness,
QSSGShaderDefaultMaterialKeyProperties::RoughnessMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->opacityMap, QSSGImageMapTypes::Opacity, QSSGShaderDefaultMaterialKeyProperties::OpacityMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->bumpMap, QSSGImageMapTypes::Bump, QSSGShaderDefaultMaterialKeyProperties::BumpMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->specularMap,
QSSGImageMapTypes::SpecularAmountMap,
QSSGShaderDefaultMaterialKeyProperties::SpecularAmountMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->normalMap, QSSGImageMapTypes::Normal, QSSGShaderDefaultMaterialKeyProperties::NormalMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->displacementMap,
QSSGImageMapTypes::Displacement,
QSSGShaderDefaultMaterialKeyProperties::DisplacementMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->translucencyMap,
QSSGImageMapTypes::Translucency,
QSSGShaderDefaultMaterialKeyProperties::TranslucencyMap);
CHECK_IMAGE_AND_PREPARE(theMaterial->lightmaps.m_lightmapIndirect,
QSSGImageMapTypes::LightmapIndirect,
QSSGShaderDefaultMaterialKeyProperties::LightmapIndirect);
CHECK_IMAGE_AND_PREPARE(theMaterial->lightmaps.m_lightmapRadiosity,
QSSGImageMapTypes::LightmapRadiosity,
QSSGShaderDefaultMaterialKeyProperties::LightmapRadiosity);
CHECK_IMAGE_AND_PREPARE(theMaterial->lightmaps.m_lightmapShadow,
QSSGImageMapTypes::LightmapShadow,
QSSGShaderDefaultMaterialKeyProperties::LightmapShadow);
}
#undef CHECK_IMAGE_AND_PREPARE
if (subsetOpacity < QSSG_RENDER_MINIMUM_RENDER_OPACITY) {
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 - QSSG_RENDER_MINIMUM_RENDER_OPACITY)
subsetOpacity = 1.f;
else
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
retval.firstImage = firstImage;
if (retval.renderableFlags.isDirty())
retval.dirty = true;
if (retval.dirty)
renderer->addMaterialDirtyClear(&inMaterial);
return retval;
}
QSSGDefaultMaterialPreparationResult QSSGLayerRenderPreparationData::prepareCustomMaterialForRender(QSSGRenderCustomMaterial &inMaterial,
QSSGRenderableObjectFlags &inExistingFlags,
float inOpacity, bool alreadyDirty)
{
QSSGDefaultMaterialPreparationResult retval(generateLightingKey(QSSGRenderDefaultMaterial::MaterialLighting::FragmentLighting, inExistingFlags.receivesShadows())); // always fragment lighting
retval.renderableFlags = inExistingFlags;
QSSGRenderableObjectFlags &renderableFlags(retval.renderableFlags);
QSSGShaderDefaultMaterialKey &theGeneratedKey(retval.materialKey);
retval.opacity = inOpacity;
float &subsetOpacity(retval.opacity);
// set wireframe mode
renderer->defaultMaterialShaderKeyProperties().m_wireframeMode.setValue(theGeneratedKey,
renderer->contextInterface()->wireframeMode());
if (subsetOpacity < QSSG_RENDER_MINIMUM_RENDER_OPACITY) {
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 - QSSG_RENDER_MINIMUM_RENDER_OPACITY)
subsetOpacity = 1.f;
else
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
QSSGRenderableImage *firstImage = nullptr;
QSSGRenderableImage *nextImage = nullptr;
#define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent) \
if ((img)) \
prepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags, \
theGeneratedKey, shadercomponent, nullptr)
CHECK_IMAGE_AND_PREPARE(inMaterial.m_displacementMap,
QSSGImageMapTypes::Displacement,
QSSGShaderDefaultMaterialKeyProperties::DisplacementMap);
CHECK_IMAGE_AND_PREPARE(inMaterial.m_lightmaps.m_lightmapIndirect,
QSSGImageMapTypes::LightmapIndirect,
QSSGShaderDefaultMaterialKeyProperties::LightmapIndirect);
CHECK_IMAGE_AND_PREPARE(inMaterial.m_lightmaps.m_lightmapRadiosity,
QSSGImageMapTypes::LightmapRadiosity,
QSSGShaderDefaultMaterialKeyProperties::LightmapRadiosity);
CHECK_IMAGE_AND_PREPARE(inMaterial.m_lightmaps.m_lightmapShadow,
QSSGImageMapTypes::LightmapShadow,
QSSGShaderDefaultMaterialKeyProperties::LightmapShadow);
#undef CHECK_IMAGE_AND_PREPARE
retval.firstImage = firstImage;
if (retval.dirty || alreadyDirty)
renderer->addMaterialDirtyClear(&inMaterial);
// register the custom material shaders with the dynamic object system if they
// are not registered already
const QSSGRef<QSSGDynamicObjectSystem> &theDynamicSystem(renderer->contextInterface()->dynamicObjectSystem());
for (auto shaderPath : inMaterial.shaders.keys())
theDynamicSystem->setShaderData(shaderPath,
inMaterial.shaders[shaderPath],
inMaterial.shaderInfo.type,
inMaterial.shaderInfo.version,
false,
false);
return retval;
}
bool QSSGLayerRenderPreparationData::prepareModelForRender(QSSGRenderModel &inModel,
const QMatrix4x4 &inViewProjection,
const QSSGOption<QSSGClippingFrustum> &inClipFrustum,
QSSGNodeLightEntryList &inScopedLights)
{
const QSSGRef<QSSGRenderContextInterface> &contextInterface(renderer->contextInterface());
const QSSGRef<QSSGBufferManager> &bufferManager = contextInterface->bufferManager();
QSSGRenderMesh *theMesh = nullptr;
// create custom mesh if set
if (inModel.meshPath.isNull() && inModel.geometry)
theMesh = inModel.geometry->createOrUpdate(bufferManager);
else
theMesh = bufferManager->loadMesh(inModel.meshPath);
if (theMesh == nullptr)
return false;
QSSGModelContext &theModelContext = *RENDER_FRAME_NEW<QSSGModelContext>(renderer->contextInterface(), inModel, inViewProjection);
modelContexts.push_back(&theModelContext);
bool subsetDirty = false;
// Completely transparent models cannot be pickable. But models with completely
// transparent materials still are. This allows the artist to control pickability
// in a somewhat fine-grained style.
const bool canModelBePickable = (inModel.globalOpacity > QSSG_RENDER_MINIMUM_RENDER_OPACITY)
&& (theModelContext.model.flags.testFlag(QSSGRenderModel::Flag::GloballyPickable));
if (canModelBePickable) {
// Check if there is BVH data, if not generate it
if (!theMesh->bvh && !inModel.meshPath.isNull()) {
theMesh->bvh = bufferManager->loadMeshBVH(inModel.meshPath);
if (theMesh->bvh) {
for (int i = 0; i < theMesh->bvh->roots.count(); ++i)
theMesh->subsets[i].bvhRoot = theMesh->bvh->roots.at(i);
}
}
}
const QSSGScopedLightsListScope lightsScope(globalLights, lightDirections, sourceLightDirections, inScopedLights);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::CgLighting), !globalLights.empty());
for (int idx = 0; idx < theMesh->subsets.size(); ++idx) {
// If the materials list < size of subsets, then use the last material for the rest
QSSGRenderGraphObject *theSourceMaterialObject = nullptr;
if (inModel.materials.isEmpty())
break;
if (idx + 1 > inModel.materials.count())
theSourceMaterialObject = inModel.materials.last();
else
theSourceMaterialObject = inModel.materials.at(idx);
QSSGRenderSubset &theOuterSubset(theMesh->subsets[idx]);
{
QSSGRenderSubset &theSubset(theOuterSubset);
QSSGRenderableObjectFlags renderableFlags;
float subsetOpacity = inModel.globalOpacity;
QVector3D theModelCenter(theSubset.bounds.center());
theModelCenter = mat44::transform(inModel.globalTransform, theModelCenter);
if (subsetOpacity >= QSSG_RENDER_MINIMUM_RENDER_OPACITY && inClipFrustum.hasValue()) {
// Check bounding box against the clipping planes
QSSGBounds3 theGlobalBounds = theSubset.bounds;
theGlobalBounds.transform(theModelContext.model.globalTransform);
if (!inClipFrustum->intersectsWith(theGlobalBounds))
subsetOpacity = 0.0f;
}
renderableFlags.setPickable(canModelBePickable);
// Casting and Receiving Shadows
renderableFlags.setCastsShadows(inModel.castsShadows);
renderableFlags.setReceivesShadows(inModel.receivesShadows);
for (const QByteArray &attr : qAsConst(theMesh->inputLayoutInputNames)) {
using namespace QSSGMeshUtilities;
if (attr == Mesh::getPositionAttrName())
renderableFlags.setHasAttributePosition(true);
else if (attr == Mesh::getNormalAttrName())
renderableFlags.setHasAttributeNormal(true);
else if (attr == Mesh::getUVAttrName())
renderableFlags.setHasAttributeTexCoord0(true);
else if (attr == Mesh::getUV2AttrName())
renderableFlags.setHasAttributeTexCoord1(true);
else if (attr == Mesh::getTexTanAttrName())
renderableFlags.setHasAttributeTangent(true);
else if (attr == Mesh::getTexBinormalAttrName())
renderableFlags.setHasAttributeBinormal(true);
else if (attr == Mesh::getColorAttrName())
renderableFlags.setHasAttributeColor(true);
}
QSSGRenderableObject *theRenderableObject = nullptr;
QSSGRenderGraphObject *theMaterialObject = theSourceMaterialObject;
if (theMaterialObject == nullptr)
continue;
// set tessellation
if (inModel.tessellationMode != TessellationModeValues::NoTessellation) {
theSubset.primitiveType = QSSGRenderDrawMode::Patches;
// set tessellation factor
theSubset.edgeTessFactor = inModel.edgeTessellation;
theSubset.innerTessFactor = inModel.innerTessellation;
// update the vertex ver patch count in the input assembler
// currently we only support triangle patches so count is always 3
theSubset.inputAssembler->setPatchVertexCount(3);
theSubset.inputAssemblerDepth->setPatchVertexCount(3);
// check wireframe mode
theSubset.wireframeMode = contextInterface->wireframeMode();
subsetDirty = subsetDirty | (theSubset.wireframeMode != inModel.wireframeMode);
inModel.wireframeMode = contextInterface->wireframeMode();
} else {
theSubset.primitiveType = theSubset.inputAssembler->drawMode();
theSubset.inputAssembler->setPatchVertexCount(1);
theSubset.inputAssemblerDepth->setPatchVertexCount(1);
// currently we allow wirframe mode only if tessellation is on
theSubset.wireframeMode = false;
subsetDirty = subsetDirty | (theSubset.wireframeMode != inModel.wireframeMode);
inModel.wireframeMode = false;
}
if (theMaterialObject == nullptr)
continue;
if (theMaterialObject->type == QSSGRenderGraphObject::Type::DefaultMaterial || theMaterialObject->type == QSSGRenderGraphObject::Type::PrincipledMaterial) {
QSSGRenderDefaultMaterial &theMaterial(static_cast<QSSGRenderDefaultMaterial &>(*theMaterialObject));
// vertexColor should be supported in both DefaultMaterial and PrincipleMaterial
// if the mesh has it.
theMaterial.vertexColorsEnabled = renderableFlags.hasAttributeColor();
QSSGDefaultMaterialPreparationResult theMaterialPrepResult(
prepareDefaultMaterialForRender(theMaterial, renderableFlags, subsetOpacity));
QSSGShaderDefaultMaterialKey theGeneratedKey = theMaterialPrepResult.materialKey;
subsetOpacity = theMaterialPrepResult.opacity;
QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
subsetDirty |= theMaterialPrepResult.dirty;
renderableFlags = theMaterialPrepResult.renderableFlags;
renderer->defaultMaterialShaderKeyProperties().m_tessellationMode.setTessellationMode(theGeneratedKey,
inModel.tessellationMode,
true);
QSSGDataView<QMatrix4x4> boneGlobals;
if (theSubset.joints.size()) {
Q_ASSERT(false);
}
theRenderableObject = RENDER_FRAME_NEW<QSSGSubsetRenderable>(renderer->contextInterface(),
renderableFlags,
theModelCenter,
renderer,
theSubset,
theMaterial,
theModelContext,
subsetOpacity,
firstImage,
theGeneratedKey,
boneGlobals);
subsetDirty = subsetDirty || renderableFlags.isDirty();
} else if (theMaterialObject->type == QSSGRenderGraphObject::Type::CustomMaterial) {
QSSGRenderCustomMaterial &theMaterial(static_cast<QSSGRenderCustomMaterial &>(*theMaterialObject));
const QSSGRef<QSSGMaterialSystem> &theMaterialSystem(contextInterface->customMaterialSystem());
subsetDirty |= theMaterialSystem->prepareForRender(theModelContext.model, theSubset, theMaterial);
QSSGDefaultMaterialPreparationResult theMaterialPrepResult(
prepareCustomMaterialForRender(theMaterial, renderableFlags, subsetOpacity, subsetDirty));
QSSGShaderDefaultMaterialKey theGeneratedKey = theMaterialPrepResult.materialKey;
subsetOpacity = theMaterialPrepResult.opacity;
QSSGRenderableImage *firstImage(theMaterialPrepResult.firstImage);
renderableFlags = theMaterialPrepResult.renderableFlags;
// prepare for render tells us if the object is transparent
if (theMaterial.m_hasTransparency)
renderableFlags |= QSSGRenderableObjectFlag::HasTransparency;
// prepare for render tells us if the object is transparent
if (theMaterial.m_hasRefraction)
renderableFlags |= QSSGRenderableObjectFlag::HasRefraction;
renderer->defaultMaterialShaderKeyProperties().m_tessellationMode.setTessellationMode(theGeneratedKey,
inModel.tessellationMode,
true);
if (theMaterial.m_iblProbe && checkLightProbeDirty(*theMaterial.m_iblProbe)) {
renderer->prepareImageForIbl(*theMaterial.m_iblProbe);
}
theRenderableObject = RENDER_FRAME_NEW<QSSGCustomMaterialRenderable>(renderer->contextInterface(),
renderableFlags,
theModelCenter,
renderer,
theSubset,
theMaterial,
theModelContext,
subsetOpacity,
firstImage,
theGeneratedKey);
}
if (theRenderableObject) {
theRenderableObject->scopedLights = inScopedLights;
// set tessellation
theRenderableObject->tessellationMode = inModel.tessellationMode;
if (theRenderableObject->renderableFlags.hasTransparency() || theRenderableObject->renderableFlags.hasRefraction()) {
transparentObjects.push_back(QSSGRenderableObjectHandle::create(theRenderableObject));
} else {
opaqueObjects.push_back(QSSGRenderableObjectHandle::create(theRenderableObject));
}
}
}
}
return subsetDirty;
}
bool QSSGLayerRenderPreparationData::prepareRenderablesForRender(const QMatrix4x4 &inViewProjection,
const QSSGOption<QSSGClippingFrustum> &inClipFrustum,
QSSGLayerRenderPreparationResultFlags &ioFlags)
{
Q_UNUSED(ioFlags)
QSSGStackPerfTimer perfTimer(renderer->contextInterface()->performanceTimer(), Q_FUNC_INFO);
viewProjection = inViewProjection;
bool wasDataDirty = false;
for (qint32 idx = 0, end = renderableNodes.size(); idx < end; ++idx) {
QSSGRenderableNodeEntry &theNodeEntry(renderableNodes[idx]);
QSSGRenderNode *theNode = theNodeEntry.node;
wasDataDirty = wasDataDirty || theNode->flags.testFlag(QSSGRenderNode::Flag::Dirty);
switch (theNode->type) {
case QSSGRenderGraphObject::Type::Model: {
QSSGRenderModel *theModel = static_cast<QSSGRenderModel *>(theNode);
theModel->calculateGlobalVariables();
if (theModel->flags.testFlag(QSSGRenderModel::Flag::GloballyActive)) {
bool wasModelDirty = prepareModelForRender(*theModel, inViewProjection, inClipFrustum, theNodeEntry.lights);
wasDataDirty = wasDataDirty || wasModelDirty;
}
} break;
case QSSGRenderGraphObject::Type::Item2D: {
QSSGRenderItem2D *theItem2D = static_cast<QSSGRenderItem2D *>(theNode);
theItem2D->calculateGlobalVariables();
if (theItem2D->flags.testFlag(QSSGRenderModel::Flag::GloballyActive)) {
theItem2D->MVP = inViewProjection * theItem2D->globalTransform;
// Pushing front to keep item order inside QML file
renderableItem2Ds.push_front(theNodeEntry);
}
} break;
default:
Q_ASSERT(false);
break;
}
}
return wasDataDirty;
}
bool QSSGLayerRenderPreparationData::checkLightProbeDirty(QSSGRenderImage &inLightProbe)
{
const QSSGRef<QSSGRenderContextInterface> &theContext(renderer->contextInterface());
const QSSGRef<QSSGBufferManager> &bufferManager = theContext->bufferManager();
return inLightProbe.clearDirty(bufferManager, true);
}
struct QSSGLightNodeMarker
{
QSSGRenderLight *light = nullptr;
quint32 lightIndex = 0;
quint32 firstValidIndex = 0;
quint32 justPastLastValidIndex = 0;
bool addOrRemove = false;
QSSGLightNodeMarker() = default;
QSSGLightNodeMarker(QSSGRenderLight &inLight, quint32 inLightIndex, QSSGRenderNode &inNode, bool aorm)
: light(&inLight), lightIndex(inLightIndex), addOrRemove(aorm)
{
if (inNode.type == QSSGRenderGraphObject::Type::Layer) {
firstValidIndex = 0;
justPastLastValidIndex = std::numeric_limits<quint32>::max();
} else {
firstValidIndex = inNode.dfsIndex;
QSSGRenderNode *lastChild = nullptr;
QSSGRenderNode *firstChild = inNode.firstChild;
// find deepest last child
while (firstChild) {
for (QSSGRenderNode *childNode = firstChild; childNode; childNode = childNode->nextSibling)
lastChild = childNode;
if (lastChild)
firstChild = lastChild->firstChild;
else
firstChild = nullptr;
}
if (lastChild)
// last valid index would be the last child index + 1
justPastLastValidIndex = lastChild->dfsIndex + 1;
else // no children.
justPastLastValidIndex = firstValidIndex + 1;
}
}
};
void QSSGLayerRenderPreparationData::prepareForRender(const QSize &inViewportDimensions)
{
QSSGStackPerfTimer perfTimer(renderer->contextInterface()->performanceTimer(), Q_FUNC_INFO);
if (layerPrepResult.hasValue())
return;
features.clear();
featureSetHash = 0;
QVector2D thePresentationDimensions((float)inViewportDimensions.width(), (float)inViewportDimensions.height());
QRect theViewport(renderer->contextInterface()->viewport());
QRect theScissor(renderer->contextInterface()->scissorRect());
if (theScissor.isNull() || (theScissor == theViewport)) {
theScissor = theViewport;
renderer->contextInterface()->renderContext()->setScissorTestEnabled(false);
} else {
renderer->contextInterface()->renderContext()->setScissorTestEnabled(true);
}
bool wasDirty = false;
bool wasDataDirty = false;
wasDirty = layer.flags.testFlag(QSSGRenderLayer::Flag::Dirty);
// The first pass is just to render the data.
quint32 maxNumAAPasses = layer.antialiasingMode == QSSGRenderLayer::AAMode::NoAA ? (quint32)0 : (quint32)(layer.antialiasingQuality) + 1;
maxNumAAPasses = qMin((quint32)(MAX_AA_LEVELS + 1), maxNumAAPasses);
QSSGRenderEffect *theLastEffect = nullptr;
// Uncomment the line below to disable all progressive AA.
// maxNumAAPasses = 0;
QSSGLayerRenderPreparationResult thePrepResult;
bool SSAOEnabled = (layer.aoStrength > 0.0f && layer.aoDistance > 0.0f);
bool SSDOEnabled = (layer.shadowStrength > 0.0f && layer.shadowDist > 0.0f);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::Ssao), SSAOEnabled);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::Ssdo), SSDOEnabled);
bool requiresDepthPrepass = (SSAOEnabled || SSDOEnabled);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::Ssm), false); // by default no shadow map generation
if (layer.flags.testFlag(QSSGRenderLayer::Flag::Active)) {
// Get the layer's width and height.
for (QSSGRenderEffect *theEffect = layer.firstEffect; theEffect; theEffect = theEffect->m_nextEffect) {
if (theEffect->flags.testFlag(QSSGRenderEffect::Flag::Dirty)) {
wasDirty = true;
theEffect->flags.setFlag(QSSGRenderEffect::Flag::Dirty, false);
}
if (theEffect->flags.testFlag(QSSGRenderEffect::Flag::Active)) {
theLastEffect = theEffect;
if (theEffect->requiresDepthTexture)
requiresDepthPrepass = true;
}
}
if (layer.flags.testFlag(QSSGRenderLayer::Flag::Dirty)) {
wasDirty = true;
layer.calculateGlobalVariables();
}
thePrepResult = QSSGLayerRenderPreparationResult(
QSSGLayerRenderHelper(theViewport,
theScissor,
layer));
thePrepResult.lastEffect = theLastEffect;
thePrepResult.maxAAPassIndex = maxNumAAPasses;
thePrepResult.flags.setRequiresDepthTexture(requiresDepthPrepass);
if (renderer->context()->renderContextType() != QSSGRenderContextType::GLES2)
thePrepResult.flags.setRequiresSsaoPass(SSAOEnabled);
if (thePrepResult.isLayerVisible()) {
if (layer.lightProbe && checkLightProbeDirty(*layer.lightProbe)) {
renderer->prepareImageForIbl(*layer.lightProbe);
wasDataDirty = true;
}
bool lightProbeValid = hasValidLightProbe(layer.lightProbe);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::LightProbe), lightProbeValid);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::IblFov), layer.probeFov < 180.0f);
if (lightProbeValid && layer.lightProbe2 && checkLightProbeDirty(*layer.lightProbe2)) {
renderer->prepareImageForIbl(*layer.lightProbe2);
wasDataDirty = true;
}
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::LightProbe2), lightProbeValid && hasValidLightProbe(layer.lightProbe2));
// Push nodes in reverse depth first order
// if (renderableNodes.empty()) {
// camerasAndLights.clear();
// quint32 dfsIndex = 0;
// for (QSSGRenderNode *theChild = layer.firstChild; theChild; theChild = theChild->nextSibling)
// MaybeQueueNodeForRender(*theChild, renderableNodes, camerasAndLights, dfsIndex);
// std::reverse(camerasAndLights.begin(), camerasAndLights.end());
// std::reverse(renderableNodes.begin(), renderableNodes.end());
// lightToNodeMap.clear();
// }
// ### TODO: Really this should only be done if renderableNodes is empty or dirty
// but we don't have a way to say it's dirty yet (new renderables added to the tree)
cameras.clear();
lights.clear();
renderableNodes.clear();
renderableItem2Ds.clear();
quint32 dfsIndex = 0;
for (QSSGRenderNode *theChild = layer.firstChild; theChild; theChild = theChild->nextSibling)
maybeQueueNodeForRender(*theChild, renderableNodes, cameras, lights, dfsIndex);
lightToNodeMap.clear();
globalLights.clear();
for (const auto &oo : qAsConst(opaqueObjects))
delete oo.obj;
opaqueObjects.clear();
for (const auto &to : qAsConst(transparentObjects))
delete to.obj;
transparentObjects.clear();
QVector<QSSGLightNodeMarker> theLightNodeMarkers;
sourceLightDirections.clear();
// Cameras
// First, check the activeCamera is GloballyActive
// and then if not, seek a GloballyActive one from the first
camera = layer.activeCamera;
if (camera != nullptr) {
wasDataDirty = wasDataDirty
|| camera->flags.testFlag(QSSGRenderNode::Flag::Dirty);
QSSGCameraGlobalCalculationResult theResult = thePrepResult.setupCameraForRender(*camera);
wasDataDirty = wasDataDirty || theResult.m_wasDirty;
if (!theResult.m_computeFrustumSucceeded)
qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
if (!camera->flags.testFlag(QSSGRenderCamera::Flag::GloballyActive))
camera = nullptr;
}
for (auto iter = cameras.cbegin();
(camera == nullptr) && (iter != cameras.cend()); iter++) {
QSSGRenderCamera *theCamera = *iter;
wasDataDirty = wasDataDirty
|| theCamera->flags.testFlag(QSSGRenderNode::Flag::Dirty);
QSSGCameraGlobalCalculationResult theResult = thePrepResult.setupCameraForRender(*theCamera);
wasDataDirty = wasDataDirty || theResult.m_wasDirty;
if (!theResult.m_computeFrustumSucceeded)
qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum");
if (theCamera->flags.testFlag(QSSGRenderCamera::Flag::GloballyActive))
camera = theCamera;
}
layer.renderedCamera = camera;
// Lights
for (auto rIt = lights.crbegin(); rIt != lights.crend(); rIt++) {
QSSGRenderLight *theLight = *rIt;
wasDataDirty = wasDataDirty || theLight->flags.testFlag(QSSGRenderNode::Flag::Dirty);
bool lightResult = theLight->calculateGlobalVariables();
wasDataDirty = lightResult || wasDataDirty;
// Note we setup the light index such that it is completely invariant of if
// the
// light is active or scoped.
quint32 lightIndex = (quint32)sourceLightDirections.size();
sourceLightDirections.push_back(QVector3D(0.0, 0.0, 0.0));
// Note we still need a light check when building the renderable light list.
// We also cannot cache shader-light bindings based on layers any more
// because
// the number of lights for a given renderable does not depend on the layer
// as it used to but
// additional perhaps on the light's scoping rules.
if (theLight->flags.testFlag(QSSGRenderLight::Flag::GloballyActive)) {
if (theLight->m_scope == nullptr) {
globalLights.push_back(theLight);
if (renderer->context()->renderContextType() != QSSGRenderContextType::GLES2
&& theLight->m_castShadow) {
if (!shadowMapManager)
createShadowMapManager();
// PKC -- use of "res" as an exponent of two is an annoying
// artifact of the XML interface
// I'll change this with an enum interface later on, but that's
// less important right now.
quint32 mapSize = 1 << theLight->m_shadowMapRes;
ShadowMapModes mapMode = (theLight->m_lightType != QSSGRenderLight::Type::Directional)
? ShadowMapModes::CUBE
: ShadowMapModes::VSM;
shadowMapManager->addShadowMapEntry(globalLights.size() - 1,
mapSize,
mapSize,
QSSGRenderTextureFormat::R16F,
1,
mapMode,
ShadowFilterValues::NONE);
thePrepResult.flags.setRequiresShadowMapPass(true);
setShaderFeature(QSSGShaderDefines::asString(QSSGShaderDefines::Ssm), true);
}
}
TLightToNodeMap::iterator iter = lightToNodeMap.insert(theLight, (QSSGRenderNode *)nullptr);
QSSGRenderNode *oldLightScope = iter.value();
QSSGRenderNode *newLightScope = theLight->m_scope;
if (oldLightScope != newLightScope) {
iter.value() = newLightScope;
if (oldLightScope)
theLightNodeMarkers.push_back(QSSGLightNodeMarker(*theLight, lightIndex, *oldLightScope, false));
if (newLightScope)
theLightNodeMarkers.push_back(QSSGLightNodeMarker(*theLight, lightIndex, *newLightScope, true));
}
if (newLightScope) {
sourceLightDirections.back() = theLight->getScalingCorrectDirection();
}
}
}
if (!theLightNodeMarkers.empty()) {
for (auto rIt = renderableNodes.rbegin();
rIt != renderableNodes.rend(); rIt++) {
QSSGRenderableNodeEntry &theNodeEntry(*rIt);
quint32 nodeDFSIndex = theNodeEntry.node->dfsIndex;
for (quint32 markerIdx = 0, markerEnd = theLightNodeMarkers.size(); markerIdx < markerEnd; ++markerIdx) {
QSSGLightNodeMarker &theMarker = theLightNodeMarkers[markerIdx];
if (nodeDFSIndex >= theMarker.firstValidIndex && nodeDFSIndex < theMarker.justPastLastValidIndex) {
if (theMarker.addOrRemove) {
QSSGNodeLightEntry *theNewEntry = new QSSGNodeLightEntry(theMarker.light, theMarker.lightIndex);
theNodeEntry.lights.push_back(*theNewEntry);
} else {
for (QSSGNodeLightEntryList::iterator lightIter = theNodeEntry.lights.begin(),
lightEnd = theNodeEntry.lights.end();
lightIter != lightEnd;
++lightIter) {
if (lightIter->light == theMarker.light) {
QSSGNodeLightEntry &theEntry = *lightIter;
theNodeEntry.lights.remove(theEntry);
delete &theEntry;
break;
}
}
}
}
}
}
}
if (camera) {
camera->calculateViewProjectionMatrix(viewProjection);
if (camera->enableFrustumClipping) {
QSSGClipPlane nearPlane;
QMatrix3x3 theUpper33(camera->globalTransform.normalMatrix());
QVector3D dir(mat33::transform(theUpper33, QVector3D(0, 0, -1)));
dir.normalize();
nearPlane.normal = dir;
QVector3D theGlobalPos = camera->getGlobalPos() + camera->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);
} else if (clippingFrustum.hasValue()) {
clippingFrustum.setEmpty();
}
} else
viewProjection = QMatrix4x4();
// Setup the light directions here.
for (qint32 lightIdx = 0, lightEnd = globalLights.size(); lightIdx < lightEnd; ++lightIdx) {
lightDirections.push_back(globalLights.at(lightIdx)->getScalingCorrectDirection());
}
modelContexts.clear();
bool renderablesDirty = prepareRenderablesForRender(viewProjection,
clippingFrustum,
thePrepResult.flags);
wasDataDirty = wasDataDirty || renderablesDirty;
}
}
wasDirty = wasDirty || wasDataDirty;
thePrepResult.flags.setWasDirty(wasDirty);
thePrepResult.flags.setLayerDataDirty(wasDataDirty);
layerPrepResult = thePrepResult;
// Per-frame cache of renderable objects post-sort.
getOpaqueRenderableObjects();
// If layer depth test is false, this may also contain opaque objects.
getTransparentRenderableObjects();
getCameraDirection();
}
void QSSGLayerRenderPreparationData::resetForFrame()
{
transparentObjects.clear();
opaqueObjects.clear();
layerPrepResult.setEmpty();
// The check for if the camera is or is not null is used
// to figure out if this layer was rendered at all.
camera = nullptr;
cameraDirection.setEmpty();
lightDirections.clear();
renderedOpaqueObjects.clear();
renderedTransparentObjects.clear();
renderedItem2Ds.clear();
}
QT_END_NAMESPACE