/**************************************************************************** ** ** Copyright (C) 2008-2012 NVIDIA Corporation. ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $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 "Qt3DSRender.h" #include "Qt3DSRenderer.h" #include "Qt3DSRendererImpl.h" #include "Qt3DSRenderLayer.h" #include "Qt3DSRenderEffect.h" #include "EASTL/sort.h" #include "Qt3DSRenderLight.h" #include "Qt3DSRenderCamera.h" #include "Qt3DSRenderScene.h" #include "Qt3DSRenderPresentation.h" #include "foundation/Qt3DSFoundation.h" #include "Qt3DSRenderContextCore.h" #include "Qt3DSRenderResourceManager.h" #include "Qt3DSTextRenderer.h" #include "Qt3DSRenderEffectSystem.h" #include "render/Qt3DSRenderFrameBuffer.h" #include "render/Qt3DSRenderRenderBuffer.h" #include "Qt3DSOffscreenRenderKey.h" #include "Qt3DSRenderPlugin.h" #include "Qt3DSRenderPluginGraphObject.h" #include "Qt3DSRenderResourceBufferObjects.h" #include "foundation/Qt3DSPerfTimer.h" #include "foundation/AutoDeallocatorAllocator.h" #include "Qt3DSRenderMaterialHelpers.h" #include "Qt3DSRenderBufferManager.h" #include "Qt3DSRenderCustomMaterialSystem.h" #include "Qt3DSRenderTextTextureCache.h" #include "Qt3DSRenderTextTextureAtlas.h" #include "Qt3DSRenderRenderList.h" #include "Qt3DSRenderPath.h" #include "Qt3DSRenderPathManager.h" #ifdef _WIN32 #pragma warning(disable : 4355) #endif namespace qt3ds { namespace render { using eastl::reverse; using eastl::stable_sort; using qt3ds::render::NVRenderContextScopedProperty; using qt3ds::QT3DSVec2; namespace { void MaybeQueueNodeForRender(SNode &inNode, nvvector &outRenderables, nvvector &outCamerasAndLights, QT3DSU32 &ioDFSIndex) { ++ioDFSIndex; inNode.m_DFSIndex = ioDFSIndex; if (GraphObjectTypes::IsRenderableType(inNode.m_Type)) outRenderables.push_back(inNode); else if (GraphObjectTypes::IsLightCameraType(inNode.m_Type)) outCamerasAndLights.push_back(&inNode); for (SNode *theChild = inNode.m_FirstChild; theChild != NULL; theChild = theChild->m_NextSibling) MaybeQueueNodeForRender(*theChild, outRenderables, outCamerasAndLights, ioDFSIndex); } bool HasValidLightProbe(SImage *inLightProbeImage) { return inLightProbeImage && inLightProbeImage->m_TextureData.m_Texture; } } SDefaultMaterialPreparationResult::SDefaultMaterialPreparationResult( SShaderDefaultMaterialKey inKey) : m_FirstImage(NULL) , m_Opacity(1.0f) , m_MaterialKey(inKey) , m_Dirty(false) { } #define MAX_AA_LEVELS 8 SLayerRenderPreparationData::SLayerRenderPreparationData(SLayer &inLayer, Qt3DSRendererImpl &inRenderer) : m_Layer(inLayer) , m_Renderer(inRenderer) , m_Allocator(inRenderer.GetContext().GetAllocator()) , m_RenderableNodeLightEntryPool( ForwardingAllocator(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_RenderableNodes")) , m_RenderableNodes(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_RenderableNodes") , m_LightToNodeMap(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_LightToNodeMap") , m_CamerasAndLights(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_CamerasAndLights") , m_Camera(NULL) , m_Lights(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_Lights") , m_OpaqueObjects(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_OpaqueObjects") , m_TransparentObjects(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_TransparentObjects") , m_RenderedOpaqueObjects(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_RenderedOpaqueObjects") , m_RenderedTransparentObjects(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_RenderedTransparentObjects") , m_IRenderWidgets(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_IRenderWidgets") , m_SourceLightDirections(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_SourceLightDirections") , m_LightDirections(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_LightDirections") , m_ModelContexts(inRenderer.GetContext().GetAllocator(), "SLayerRenderPreparationData::m_ModelContexts") , m_CGLightingFeatureName( inRenderer.GetContext().GetStringTable().RegisterStr("QT3DS_ENABLE_CG_LIGHTING")) , m_FeaturesDirty(true) , m_FeatureSetHash(0) , m_TooManyLightsError(false) { } SLayerRenderPreparationData::~SLayerRenderPreparationData() {} bool SLayerRenderPreparationData::NeedsWidgetTexture() const { return m_IRenderWidgets.size() > 0; } void SLayerRenderPreparationData::SetShaderFeature(CRegisteredString theStr, bool inValue) { SShaderPreprocessorFeature item(theStr, inValue); eastl::vector::iterator iter = m_Features.begin(), end = m_Features.end(); // empty loop intentional. for (; iter != end && ((iter->m_Name == theStr) == false); ++iter) ; if (iter != end) { if (iter->m_Enabled != inValue) { iter->m_Enabled = inValue; m_FeaturesDirty = true; m_FeatureSetHash = 0; } } else { m_Features.push_back(item); m_FeaturesDirty = true; m_FeatureSetHash = 0; } } void SLayerRenderPreparationData::SetShaderFeature(const char *inName, bool inValue) { CRegisteredString theStr(m_Renderer.GetQt3DSContext().GetStringTable().RegisterStr(inName)); SetShaderFeature(theStr, inValue); } NVConstDataRef SLayerRenderPreparationData::GetShaderFeatureSet() { if (m_FeaturesDirty) { eastl::sort(m_Features.begin(), m_Features.end()); m_FeaturesDirty = false; } return toConstDataRef(m_Features.data(), (QT3DSU32)m_Features.size()); } size_t SLayerRenderPreparationData::GetShaderFeatureSetHash() { if (!m_FeatureSetHash) m_FeatureSetHash = HashShaderFeatureSet(GetShaderFeatureSet()); return m_FeatureSetHash; } bool SLayerRenderPreparationData::GetShadowMapManager() { if (m_ShadowMapManager.mPtr) return true; m_ShadowMapManager.mPtr = Qt3DSShadowMap::Create(m_Renderer.GetQt3DSContext()); return m_ShadowMapManager.mPtr != NULL; } bool SLayerRenderPreparationData::GetOffscreenRenderer() { if (m_LastFrameOffscreenRenderer.mPtr) return true; if (m_Layer.m_RenderPlugin && m_Layer.m_RenderPlugin->m_Flags.IsActive()) { IRenderPluginInstance *theInstance = m_Renderer.GetQt3DSContext().GetRenderPluginManager().GetOrCreateRenderPluginInstance( m_Layer.m_RenderPlugin->m_PluginPath, m_Layer.m_RenderPlugin); if (theInstance) { m_Renderer.GetQt3DSContext() .GetOffscreenRenderManager() .MaybeRegisterOffscreenRenderer(&theInstance, *theInstance); m_LastFrameOffscreenRenderer = theInstance; } } if (m_LastFrameOffscreenRenderer.mPtr == NULL) m_LastFrameOffscreenRenderer = m_Renderer.GetQt3DSContext().GetOffscreenRenderManager().GetOffscreenRenderer( m_Layer.m_TexturePath); return m_LastFrameOffscreenRenderer.mPtr != NULL; } QT3DSVec3 SLayerRenderPreparationData::GetCameraDirection() { if (m_CameraDirection.hasValue() == false) { if (m_Camera) m_CameraDirection = m_Camera->GetScalingCorrectDirection(); else m_CameraDirection = QT3DSVec3(0, 0, -1); } return *m_CameraDirection; } // Per-frame cache of renderable objects post-sort. NVDataRef SLayerRenderPreparationData::GetOpaqueRenderableObjects() { if (m_RenderedOpaqueObjects.empty() == false || m_Camera == NULL) return m_RenderedOpaqueObjects; if (m_Layer.m_Flags.IsLayerEnableDepthTest() && m_OpaqueObjects.empty() == false) { QT3DSVec3 theCameraDirection(GetCameraDirection()); QT3DSVec3 theCameraPosition = m_Camera->GetGlobalPos(); m_RenderedOpaqueObjects.assign(m_OpaqueObjects.begin(), m_OpaqueObjects.end()); // Setup the object's sorting information for (QT3DSU32 idx = 0, end = m_RenderedOpaqueObjects.size(); idx < end; ++idx) { SRenderableObject &theInfo = *m_RenderedOpaqueObjects[idx]; QT3DSVec3 difference = theInfo.m_WorldCenterPoint - theCameraPosition; theInfo.m_CameraDistanceSq = difference.dot(theCameraDirection); } ForwardingAllocator alloc(m_Renderer.GetPerFrameAllocator(), "SortAllocations"); // Render nearest to furthest objects eastl::merge_sort(m_RenderedOpaqueObjects.begin(), m_RenderedOpaqueObjects.end(), alloc, ISRenderObjectPtrLessThan); } return m_RenderedOpaqueObjects; } // If layer depth test is false, this may also contain opaque objects. NVDataRef SLayerRenderPreparationData::GetTransparentRenderableObjects() { if (m_RenderedTransparentObjects.empty() == false || m_Camera == NULL) return m_RenderedTransparentObjects; m_RenderedTransparentObjects.assign(m_TransparentObjects.begin(), m_TransparentObjects.end()); if (m_Layer.m_Flags.IsLayerEnableDepthTest() == false) m_RenderedTransparentObjects.insert(m_RenderedTransparentObjects.end(), m_OpaqueObjects.begin(), m_OpaqueObjects.end()); if (m_RenderedTransparentObjects.empty() == false) { QT3DSVec3 theCameraDirection(GetCameraDirection()); QT3DSVec3 theCameraPosition = m_Camera->GetGlobalPos(); // Setup the object's sorting information for (QT3DSU32 idx = 0, end = m_RenderedTransparentObjects.size(); idx < end; ++idx) { SRenderableObject &theInfo = *m_RenderedTransparentObjects[idx]; QT3DSVec3 difference = theInfo.m_WorldCenterPoint - theCameraPosition; theInfo.m_CameraDistanceSq = difference.dot(theCameraDirection); } ForwardingAllocator alloc(m_Renderer.GetPerFrameAllocator(), "SortAllocations"); // render furthest to nearest. eastl::merge_sort(m_RenderedTransparentObjects.begin(), m_RenderedTransparentObjects.end(), alloc, ISRenderObjectPtrGreatThan); } return m_RenderedTransparentObjects; } #define MAX_LAYER_WIDGETS 200 void SLayerRenderPreparationData::AddRenderWidget(IRenderWidget &inWidget) { // The if the layer is not active then the widget can't be displayed. // Furthermore ResetForFrame won't be called below which leads to stale // widgets in the m_IRenderWidgets array. These stale widgets would get rendered // the next time the layer was active potentially causing a crash. if (!m_Layer.m_Flags.IsActive()) return; // Ensure we clear the widget layer always m_Renderer.LayerNeedsFrameClear(*static_cast(this)); if (m_IRenderWidgets.size() < MAX_LAYER_WIDGETS) m_IRenderWidgets.push_back(&inWidget); } #define RENDER_FRAME_NEW(type) QT3DS_NEW(m_Renderer.GetPerFrameAllocator(), type) #define QT3DS_RENDER_MINIMUM_RENDER_OPACITY .01f SShaderDefaultMaterialKey SLayerRenderPreparationData::GenerateLightingKey(DefaultMaterialLighting::Enum inLightingType) { SShaderDefaultMaterialKey theGeneratedKey(GetShaderFeatureSetHash()); const bool lighting = inLightingType != DefaultMaterialLighting::NoLighting; m_Renderer.DefaultMaterialShaderKeyProperties().m_HasLighting.SetValue(theGeneratedKey, lighting); if (lighting) { const bool lightProbe = m_Layer.m_LightProbe && m_Layer.m_LightProbe->m_TextureData.m_Texture; m_Renderer.DefaultMaterialShaderKeyProperties().m_HasIbl.SetValue(theGeneratedKey, lightProbe); QT3DSU32 numLights = (QT3DSU32)m_Lights.size(); if (numLights > SShaderDefaultMaterialKeyProperties::LightCount && m_TooManyLightsError == false) { m_TooManyLightsError = true; numLights = SShaderDefaultMaterialKeyProperties::LightCount; qCCritical(INVALID_OPERATION, "Too many lights on layer, max is 7"); QT3DS_ASSERT(false); } m_Renderer.DefaultMaterialShaderKeyProperties().m_LightCount.SetValue(theGeneratedKey, numLights); for (QT3DSU32 lightIdx = 0, lightEnd = m_Lights.size(); lightIdx < lightEnd; ++lightIdx) { SLight *theLight(m_Lights[lightIdx]); const bool isDirectional = theLight->m_LightType == RenderLightTypes::Directional; const bool isArea = theLight->m_LightType == RenderLightTypes::Area; const bool castShadowsArea = (theLight->m_LightType != RenderLightTypes::Area) && (theLight->m_CastShadow); m_Renderer.DefaultMaterialShaderKeyProperties().m_LightFlags[lightIdx] .SetValue(theGeneratedKey, !isDirectional); m_Renderer.DefaultMaterialShaderKeyProperties().m_LightAreaFlags[lightIdx] .SetValue(theGeneratedKey, isArea); m_Renderer.DefaultMaterialShaderKeyProperties().m_LightShadowFlags[lightIdx] .SetValue(theGeneratedKey, castShadowsArea); } } return theGeneratedKey; } bool SLayerRenderPreparationData::PrepareTextForRender( SText &inText, const QT3DSMat44 &inViewProjection, QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) { ITextTextureCache *theTextRenderer = m_Renderer.GetQt3DSContext().GetTextureCache(); if (theTextRenderer == NULL) return false; SRenderableObjectFlags theFlags; theFlags.SetHasTransparency(true); theFlags.SetCompletelyTransparent(inText.m_GlobalOpacity < .01f); theFlags.SetPickable(true); bool retval = false; if (theFlags.IsCompletelyTransparent() == false) { retval = inText.m_Flags.IsDirty() || inText.m_Flags.IsTextDirty(); inText.m_Flags.SetTextDirty(false); QT3DSMat44 theMVP; QT3DSMat33 theNormalMatrix; inText.CalculateMVPAndNormalMatrix(inViewProjection, theMVP, theNormalMatrix); SRenderableObject *theRenderable = nullptr; #if QT_VERSION >= QT_VERSION_CHECK(5,12,2) Q3DSDistanceFieldRenderer *distanceFieldText = static_cast( m_Renderer.GetQt3DSContext().getDistanceFieldRenderer()); theRenderable = RENDER_FRAME_NEW(SDistanceFieldRenderable)( theFlags, inText.GetGlobalPos(), inText, inText.m_Bounds, theMVP, *distanceFieldText); #else TTPathObjectAndTexture theResult = theTextRenderer->RenderText(inText, inTextScaleFactor); inText.m_TextTexture = theResult.second.second.mPtr; inText.m_TextTextureDetails = theResult.second.first; inText.m_PathFontItem = theResult.first.second; inText.m_PathFontDetails = theResult.first.first; STextScaleAndOffset theScaleAndOffset(*inText.m_TextTexture, inText.m_TextTextureDetails, inText); QT3DSVec2 theTextScale(theScaleAndOffset.m_TextScale); QT3DSVec2 theTextOffset(theScaleAndOffset.m_TextOffset); QT3DSVec3 minimum(theTextOffset[0] - theTextScale[0], theTextOffset[1] - theTextScale[1], 0); QT3DSVec3 maximum(theTextOffset[0] + theTextScale[0], theTextOffset[1] + theTextScale[1], 0); inText.m_Bounds = NVBounds3(minimum, maximum); if (inText.m_PathFontDetails) ioFlags.SetRequiresStencilBuffer(true); theRenderable = RENDER_FRAME_NEW(STextRenderable)( theFlags, inText.GetGlobalPos(), m_Renderer, inText, inText.m_Bounds, theMVP, inViewProjection, *inText.m_TextTexture, theTextOffset, theTextScale); #endif m_TransparentObjects.push_back(theRenderable); } return retval; } eastl::pair SLayerRenderPreparationData::ResolveReferenceMaterial(SGraphObject *inMaterial) { bool subsetDirty = false; bool badIdea = false; SGraphObject *theSourceMaterialObject(inMaterial); SGraphObject *theMaterialObject(inMaterial); while (theMaterialObject && theMaterialObject->m_Type == GraphObjectTypes::ReferencedMaterial && !badIdea) { SReferencedMaterial *theRefMaterial = static_cast(theMaterialObject); theMaterialObject = theRefMaterial->m_ReferencedMaterial; if (theMaterialObject == theSourceMaterialObject) { badIdea = true; } if (theRefMaterial == theSourceMaterialObject) { theRefMaterial->m_Dirty.UpdateDirtyForFrame(); } subsetDirty = subsetDirty | theRefMaterial->m_Dirty.IsDirty(); } if (badIdea) { theMaterialObject = NULL; } return eastl::make_pair(subsetDirty, theMaterialObject); } bool SLayerRenderPreparationData::PreparePathForRender( SPath &inPath, const QT3DSMat44 &inViewProjection, const Option &inClipFrustum, SLayerRenderPreparationResultFlags &ioFlags) { SRenderableObjectFlags theSharedFlags; theSharedFlags.SetPickable(true); QT3DSF32 subsetOpacity = inPath.m_GlobalOpacity; bool retval = inPath.m_Flags.IsDirty(); inPath.m_Flags.SetDirty(false); QT3DSMat44 theMVP; QT3DSMat33 theNormalMatrix; inPath.CalculateMVPAndNormalMatrix(inViewProjection, theMVP, theNormalMatrix); NVBounds3 theBounds(this->m_Renderer.GetQt3DSContext().GetPathManager().GetBounds(inPath)); if (inPath.m_GlobalOpacity >= QT3DS_RENDER_MINIMUM_RENDER_OPACITY && inClipFrustum.hasValue()) { // Check bounding box against the clipping planes NVBounds3 theGlobalBounds = theBounds; theGlobalBounds.transform(inPath.m_GlobalTransform); if (inClipFrustum->intersectsWith(theGlobalBounds) == false) subsetOpacity = 0.0f; } SGraphObject *theMaterials[2] = { inPath.m_Material, inPath.m_SecondMaterial }; if (inPath.m_PathType == PathTypes::Geometry || inPath.m_PaintStyle != PathPaintStyles::FilledAndStroked) theMaterials[1] = NULL; // We need to fill material to be the first one rendered so the stroke goes on top. // In the timeline, however, this is reversed. if (theMaterials[1]) eastl::swap(theMaterials[1], theMaterials[0]); for (QT3DSU32 idx = 0, end = 2; idx < end; ++idx) { if (theMaterials[idx] == NULL) continue; SRenderableObjectFlags theFlags = theSharedFlags; eastl::pair theMaterialAndDirty( ResolveReferenceMaterial(theMaterials[idx])); SGraphObject *theMaterial(theMaterialAndDirty.second); retval = retval || theMaterialAndDirty.first; if (theMaterial != NULL && theMaterial->m_Type == GraphObjectTypes::DefaultMaterial) { SDefaultMaterial *theDefaultMaterial = static_cast(theMaterial); // Don't clear dirty flags if the material was referenced. bool clearMaterialFlags = theMaterial == inPath.m_Material; SDefaultMaterialPreparationResult prepResult(PrepareDefaultMaterialForRender( *theDefaultMaterial, theFlags, subsetOpacity, clearMaterialFlags)); theFlags = prepResult.m_RenderableFlags; if (inPath.m_PathType == PathTypes::Geometry) { if ((inPath.m_BeginCapping != PathCapping::Noner && inPath.m_BeginCapOpacity < 1.0f) || (inPath.m_EndCapping != PathCapping::Noner && inPath.m_EndCapOpacity < 1.0f)) theFlags.SetHasTransparency(true); } else { ioFlags.SetRequiresStencilBuffer(true); } retval = retval || prepResult.m_Dirty; bool isStroke = true; if (idx == 0 && inPath.m_PathType == PathTypes::Painted) { if (inPath.m_PaintStyle == PathPaintStyles::Filled || inPath.m_PaintStyle == PathPaintStyles::FilledAndStroked) isStroke = false; } SPathRenderable *theRenderable = RENDER_FRAME_NEW(SPathRenderable)( theFlags, inPath.GetGlobalPos(), m_Renderer, inPath.m_GlobalTransform, theBounds, inPath, theMVP, theNormalMatrix, *theMaterial, prepResult.m_Opacity, prepResult.m_MaterialKey, isStroke); theRenderable->m_FirstImage = prepResult.m_FirstImage; IQt3DSRenderContext &qt3dsContext(m_Renderer.GetQt3DSContext()); IPathManager &thePathManager = qt3dsContext.GetPathManager(); retval = thePathManager.PrepareForRender(inPath) || retval; retval |= (inPath.m_WireframeMode != qt3dsContext.GetWireframeMode()); inPath.m_WireframeMode = qt3dsContext.GetWireframeMode(); if (theFlags.HasTransparency()) m_TransparentObjects.push_back(theRenderable); else m_OpaqueObjects.push_back(theRenderable); } else if (theMaterial != NULL && theMaterial->m_Type == GraphObjectTypes::CustomMaterial) { SCustomMaterial *theCustomMaterial = static_cast(theMaterial); // Don't clear dirty flags if the material was referenced. // bool clearMaterialFlags = theMaterial == inPath.m_Material; SDefaultMaterialPreparationResult prepResult( PrepareCustomMaterialForRender(*theCustomMaterial, theFlags, subsetOpacity)); theFlags = prepResult.m_RenderableFlags; if (inPath.m_PathType == PathTypes::Geometry) { if ((inPath.m_BeginCapping != PathCapping::Noner && inPath.m_BeginCapOpacity < 1.0f) || (inPath.m_EndCapping != PathCapping::Noner && inPath.m_EndCapOpacity < 1.0f)) theFlags.SetHasTransparency(true); } else { ioFlags.SetRequiresStencilBuffer(true); } retval = retval || prepResult.m_Dirty; bool isStroke = true; if (idx == 0 && inPath.m_PathType == PathTypes::Painted) { if (inPath.m_PaintStyle == PathPaintStyles::Filled || inPath.m_PaintStyle == PathPaintStyles::FilledAndStroked) isStroke = false; } SPathRenderable *theRenderable = RENDER_FRAME_NEW(SPathRenderable)( theFlags, inPath.GetGlobalPos(), m_Renderer, inPath.m_GlobalTransform, theBounds, inPath, theMVP, theNormalMatrix, *theMaterial, prepResult.m_Opacity, prepResult.m_MaterialKey, isStroke); theRenderable->m_FirstImage = prepResult.m_FirstImage; IQt3DSRenderContext &qt3dsContext(m_Renderer.GetQt3DSContext()); IPathManager &thePathManager = qt3dsContext.GetPathManager(); retval = thePathManager.PrepareForRender(inPath) || retval; retval |= (inPath.m_WireframeMode != qt3dsContext.GetWireframeMode()); inPath.m_WireframeMode = qt3dsContext.GetWireframeMode(); if (theFlags.HasTransparency()) m_TransparentObjects.push_back(theRenderable); else m_OpaqueObjects.push_back(theRenderable); } } return retval; } void SLayerRenderPreparationData::PrepareImageForRender( SImage &inImage, ImageMapTypes::Enum inMapType, SRenderableImage *&ioFirstImage, SRenderableImage *&ioNextImage, SRenderableObjectFlags &ioFlags, SShaderDefaultMaterialKey &inShaderKey, QT3DSU32 inImageIndex) { IQt3DSRenderContext &qt3dsContext(m_Renderer.GetQt3DSContext()); IBufferManager &bufferManager = qt3dsContext.GetBufferManager(); IOffscreenRenderManager &theOffscreenRenderManager( qt3dsContext.GetOffscreenRenderManager()); IRenderPluginManager &theRenderPluginManager(qt3dsContext.GetRenderPluginManager()); if (inImage.ClearDirty(bufferManager, theOffscreenRenderManager, theRenderPluginManager)) ioFlags |= RenderPreparationResultFlagValues::Dirty; // All objects with offscreen renderers are pickable so we can pass the pick through to the // offscreen renderer and let it deal with the pick. if (inImage.m_LastFrameOffscreenRenderer != NULL) { ioFlags.SetPickable(true); ioFlags |= RenderPreparationResultFlagValues::HasTransparency; } if (inImage.m_TextureData.m_Texture) { if (inImage.m_TextureData.m_TextureFlags.HasTransparency() && (inMapType == ImageMapTypes::Diffuse || inMapType == ImageMapTypes::Opacity || inMapType == ImageMapTypes::Translucency)) { ioFlags |= RenderPreparationResultFlagValues::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( NVRenderTextureMinifyingOp::Linear ); // inImage.m_TextureData.m_Texture->SetMagFilter( NVRenderTextureMagnifyingOp::Linear ); SRenderableImage *theImage = RENDER_FRAME_NEW(SRenderableImage)(inMapType, inImage); SShaderKeyImageMap &theKeyProp = m_Renderer.DefaultMaterialShaderKeyProperties().m_ImageMaps[inImageIndex]; theKeyProp.SetEnabled(inShaderKey, true); switch (inImage.m_MappingMode) { default: QT3DS_ASSERT(false); // fallthrough intentional case ImageMappingModes::Normal: break; case ImageMappingModes::Environment: theKeyProp.SetEnvMap(inShaderKey, true); break; case ImageMappingModes::LightProbe: theKeyProp.SetLightProbe(inShaderKey, true); break; } if (inImage.m_TextureData.m_TextureFlags.IsInvertUVCoords()) theKeyProp.SetInvertUVMap(inShaderKey, true); if (ioFirstImage == NULL) ioFirstImage = theImage; else ioNextImage->m_NextImage = theImage; // assume offscreen renderer produces non-premultiplied image if (inImage.m_LastFrameOffscreenRenderer == nullptr && inImage.m_TextureData.m_TextureFlags.IsPreMultiplied()) theKeyProp.SetPremultiplied(inShaderKey, true); SShaderKeyTextureSwizzle &theSwizzleKeyProp = m_Renderer.DefaultMaterialShaderKeyProperties().m_TextureSwizzle[inImageIndex]; theSwizzleKeyProp.SetSwizzleMode( inShaderKey, inImage.m_TextureData.m_Texture->GetTextureSwizzleMode(), true); ioNextImage = theImage; } } SDefaultMaterialPreparationResult SLayerRenderPreparationData::PrepareDefaultMaterialForRender( SDefaultMaterial &inMaterial, SRenderableObjectFlags &inExistingFlags, QT3DSF32 inOpacity, bool inClearDirtyFlags) { SDefaultMaterial *theMaterial = &inMaterial; SDefaultMaterialPreparationResult retval(GenerateLightingKey(theMaterial->m_Lighting)); retval.m_RenderableFlags = inExistingFlags; SRenderableObjectFlags &renderableFlags(retval.m_RenderableFlags); SShaderDefaultMaterialKey &theGeneratedKey(retval.m_MaterialKey); retval.m_Opacity = inOpacity; QT3DSF32 &subsetOpacity(retval.m_Opacity); if (theMaterial->m_Dirty.IsDirty()) { renderableFlags |= RenderPreparationResultFlagValues::Dirty; } subsetOpacity *= theMaterial->m_Opacity; if (inClearDirtyFlags) theMaterial->m_Dirty.UpdateDirtyForFrame(); SRenderableImage *firstImage = NULL; // set wireframe mode m_Renderer.DefaultMaterialShaderKeyProperties().m_WireframeMode.SetValue( theGeneratedKey, m_Renderer.GetQt3DSContext().GetWireframeMode()); if (theMaterial->m_IblProbe && CheckLightProbeDirty(*theMaterial->m_IblProbe)) { m_Renderer.PrepareImageForIbl(*theMaterial->m_IblProbe); } if (!m_Renderer.DefaultMaterialShaderKeyProperties().m_HasIbl.GetValue(theGeneratedKey)) { bool lightProbeValid = HasValidLightProbe(theMaterial->m_IblProbe); SetShaderFeature("QT3DS_ENABLE_LIGHT_PROBE", lightProbeValid); m_Renderer.DefaultMaterialShaderKeyProperties().m_HasIbl.SetValue(theGeneratedKey, lightProbeValid); // SetShaderFeature( "QT3DS_ENABLE_IBL_FOV", // m_Renderer.GetLayerRenderData()->m_Layer.m_ProbeFov < 180.0f ); } if (subsetOpacity >= QT3DS_RENDER_MINIMUM_RENDER_OPACITY) { if (theMaterial->m_BlendMode != DefaultMaterialBlendMode::Normal || theMaterial->m_OpacityMap) { renderableFlags |= RenderPreparationResultFlagValues::HasTransparency; } bool specularEnabled = theMaterial->IsSpecularEnabled(); m_Renderer.DefaultMaterialShaderKeyProperties().m_SpecularEnabled.SetValue( theGeneratedKey, specularEnabled); if (specularEnabled) { m_Renderer.DefaultMaterialShaderKeyProperties().m_SpecularModel.SetSpecularModel( theGeneratedKey, theMaterial->m_SpecularModel); } m_Renderer.DefaultMaterialShaderKeyProperties().m_FresnelEnabled.SetValue( theGeneratedKey, theMaterial->IsFresnelEnabled()); m_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. SRenderableImage *nextImage = NULL; #define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent) \ if ((img)) \ PrepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags, \ theGeneratedKey, shadercomponent); CHECK_IMAGE_AND_PREPARE(theMaterial->m_DiffuseMaps[0], ImageMapTypes::Diffuse, SShaderDefaultMaterialKeyProperties::DiffuseMap0); CHECK_IMAGE_AND_PREPARE(theMaterial->m_DiffuseMaps[1], ImageMapTypes::Diffuse, SShaderDefaultMaterialKeyProperties::DiffuseMap1); CHECK_IMAGE_AND_PREPARE(theMaterial->m_DiffuseMaps[2], ImageMapTypes::Diffuse, SShaderDefaultMaterialKeyProperties::DiffuseMap2); CHECK_IMAGE_AND_PREPARE(theMaterial->m_EmissiveMap, ImageMapTypes::Emissive, SShaderDefaultMaterialKeyProperties::EmissiveMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_EmissiveMap2, ImageMapTypes::Emissive, SShaderDefaultMaterialKeyProperties::EmissiveMap2); CHECK_IMAGE_AND_PREPARE(theMaterial->m_SpecularReflection, ImageMapTypes::Specular, SShaderDefaultMaterialKeyProperties::SpecularMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_RoughnessMap, ImageMapTypes::Roughness, SShaderDefaultMaterialKeyProperties::RoughnessMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_OpacityMap, ImageMapTypes::Opacity, SShaderDefaultMaterialKeyProperties::OpacityMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_BumpMap, ImageMapTypes::Bump, SShaderDefaultMaterialKeyProperties::BumpMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_SpecularMap, ImageMapTypes::SpecularAmountMap, SShaderDefaultMaterialKeyProperties::SpecularAmountMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_NormalMap, ImageMapTypes::Normal, SShaderDefaultMaterialKeyProperties::NormalMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_DisplacementMap, ImageMapTypes::Displacement, SShaderDefaultMaterialKeyProperties::DisplacementMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_TranslucencyMap, ImageMapTypes::Translucency, SShaderDefaultMaterialKeyProperties::TranslucencyMap); CHECK_IMAGE_AND_PREPARE(theMaterial->m_Lightmaps.m_LightmapIndirect, ImageMapTypes::LightmapIndirect, SShaderDefaultMaterialKeyProperties::LightmapIndirect); CHECK_IMAGE_AND_PREPARE(theMaterial->m_Lightmaps.m_LightmapRadiosity, ImageMapTypes::LightmapRadiosity, SShaderDefaultMaterialKeyProperties::LightmapRadiosity); CHECK_IMAGE_AND_PREPARE(theMaterial->m_Lightmaps.m_LightmapShadow, ImageMapTypes::LightmapShadow, SShaderDefaultMaterialKeyProperties::LightmapShadow); } #undef CHECK_IMAGE_AND_PREPARE if (subsetOpacity < QT3DS_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 |= RenderPreparationResultFlagValues::HasTransparency; renderableFlags |= RenderPreparationResultFlagValues::CompletelyTransparent; } if (IsNotOne(subsetOpacity)) renderableFlags |= RenderPreparationResultFlagValues::HasTransparency; retval.m_FirstImage = firstImage; if (retval.m_RenderableFlags.IsDirty()) retval.m_Dirty = true; return retval; } SDefaultMaterialPreparationResult SLayerRenderPreparationData::PrepareCustomMaterialForRender( SCustomMaterial &inMaterial, SRenderableObjectFlags &inExistingFlags, QT3DSF32 inOpacity) { SDefaultMaterialPreparationResult retval(GenerateLightingKey( DefaultMaterialLighting::FragmentLighting)); // always fragment lighting retval.m_RenderableFlags = inExistingFlags; SRenderableObjectFlags &renderableFlags(retval.m_RenderableFlags); SShaderDefaultMaterialKey &theGeneratedKey(retval.m_MaterialKey); retval.m_Opacity = inOpacity; QT3DSF32 &subsetOpacity(retval.m_Opacity); // If the custom material uses subpresentations, those have to be rendered before // the custom material itself m_Renderer.GetQt3DSContext().GetCustomMaterialSystem().renderSubpresentations(inMaterial); // set wireframe mode m_Renderer.DefaultMaterialShaderKeyProperties().m_WireframeMode.SetValue( theGeneratedKey, m_Renderer.GetQt3DSContext().GetWireframeMode()); if (subsetOpacity < QT3DS_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 |= RenderPreparationResultFlagValues::HasTransparency; renderableFlags |= RenderPreparationResultFlagValues::CompletelyTransparent; } if (IsNotOne(subsetOpacity)) renderableFlags |= RenderPreparationResultFlagValues::HasTransparency; SRenderableImage *firstImage = NULL; SRenderableImage *nextImage = NULL; #define CHECK_IMAGE_AND_PREPARE(img, imgtype, shadercomponent) \ if ((img)) \ PrepareImageForRender(*(img), imgtype, firstImage, nextImage, renderableFlags, \ theGeneratedKey, shadercomponent); CHECK_IMAGE_AND_PREPARE(inMaterial.m_DisplacementMap, ImageMapTypes::Displacement, SShaderDefaultMaterialKeyProperties::DisplacementMap); CHECK_IMAGE_AND_PREPARE(inMaterial.m_Lightmaps.m_LightmapIndirect, ImageMapTypes::LightmapIndirect, SShaderDefaultMaterialKeyProperties::LightmapIndirect); CHECK_IMAGE_AND_PREPARE(inMaterial.m_Lightmaps.m_LightmapRadiosity, ImageMapTypes::LightmapRadiosity, SShaderDefaultMaterialKeyProperties::LightmapRadiosity); CHECK_IMAGE_AND_PREPARE(inMaterial.m_Lightmaps.m_LightmapShadow, ImageMapTypes::LightmapShadow, SShaderDefaultMaterialKeyProperties::LightmapShadow); #undef CHECK_IMAGE_AND_PREPARE retval.m_FirstImage = firstImage; return retval; } bool SLayerRenderPreparationData::PrepareModelForRender( SModel &inModel, const QT3DSMat44 &inViewProjection, const Option &inClipFrustum, TNodeLightEntryList &inScopedLights) { IQt3DSRenderContext &qt3dsContext(m_Renderer.GetQt3DSContext()); IBufferManager &bufferManager = qt3dsContext.GetBufferManager(); SRenderMesh *theMesh = bufferManager.LoadMesh(inModel.m_MeshPath); if (theMesh == NULL) return false; SGraphObject *theSourceMaterialObject = inModel.m_FirstMaterial; SModelContext &theModelContext = *RENDER_FRAME_NEW(SModelContext)(inModel, inViewProjection); m_ModelContexts.push_back(&theModelContext); bool subsetDirty = false; SScopedLightsListScope lightsScope(m_Lights, m_LightDirections, m_SourceLightDirections, inScopedLights); SetShaderFeature(m_CGLightingFeatureName, m_Lights.empty() == false); for (QT3DSU32 idx = 0, end = theMesh->m_Subsets.size(); idx < end && theSourceMaterialObject; ++idx, theSourceMaterialObject = GetNextMaterialSibling(theSourceMaterialObject)) { SRenderSubset &theOuterSubset(theMesh->m_Subsets[idx]); { SRenderSubset &theSubset(theOuterSubset); SRenderableObjectFlags renderableFlags; renderableFlags.SetPickable(false); renderableFlags.SetShadowCaster(inModel.m_ShadowCaster); QT3DSF32 subsetOpacity = inModel.m_GlobalOpacity; QT3DSVec3 theModelCenter(theSubset.m_Bounds.getCenter()); theModelCenter = inModel.m_GlobalTransform.transform(theModelCenter); if (subsetOpacity >= QT3DS_RENDER_MINIMUM_RENDER_OPACITY && inClipFrustum.hasValue()) { // Check bounding box against the clipping planes NVBounds3 theGlobalBounds = theSubset.m_Bounds; theGlobalBounds.transform(theModelContext.m_Model.m_GlobalTransform); if (inClipFrustum->intersectsWith(theGlobalBounds) == false) subsetOpacity = 0.0f; } // For now everything is pickable. Eventually we want to have localPickable and // globalPickable set on the node during // updates and have the runtime tell us what is pickable and what is not pickable. // 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. bool canModelBePickable = inModel.m_GlobalOpacity > .01f; renderableFlags.SetPickable(canModelBePickable && (theModelContext.m_Model.m_Flags.IsGloballyPickable() || renderableFlags.GetPickable())); SRenderableObject *theRenderableObject(NULL); eastl::pair theMaterialObjectAndDirty = ResolveReferenceMaterial(theSourceMaterialObject); SGraphObject *theMaterialObject = theMaterialObjectAndDirty.second; subsetDirty = subsetDirty || theMaterialObjectAndDirty.first; if (theMaterialObject == NULL) continue; // set tessellation if (inModel.m_TessellationMode != TessModeValues::NoTess) { theSubset.m_PrimitiveType = NVRenderDrawMode::Patches; // set tessellation factor theSubset.m_EdgeTessFactor = inModel.m_EdgeTess; theSubset.m_InnerTessFactor = inModel.m_InnerTess; // update the vertex ver patch count in the input assembler // currently we only support triangle patches so count is always 3 theSubset.m_InputAssembler->SetPatchVertexCount(3); theSubset.m_InputAssemblerDepth->SetPatchVertexCount(3); // check wireframe mode theSubset.m_WireframeMode = qt3dsContext.GetWireframeMode(); subsetDirty = subsetDirty | (theSubset.m_WireframeMode != inModel.m_WireframeMode); inModel.m_WireframeMode = qt3dsContext.GetWireframeMode(); } else { theSubset.m_PrimitiveType = theSubset.m_InputAssembler->GetPrimitiveType(); theSubset.m_InputAssembler->SetPatchVertexCount(1); theSubset.m_InputAssemblerDepth->SetPatchVertexCount(1); // currently we allow wirframe mode only if tessellation is on theSubset.m_WireframeMode = false; subsetDirty = subsetDirty | (theSubset.m_WireframeMode != inModel.m_WireframeMode); inModel.m_WireframeMode = false; } // Only clear flags on the materials in this direct hierarchy. Do not clear them of // this // references materials in another hierarchy. bool clearMaterialDirtyFlags = theMaterialObject == theSourceMaterialObject; if (theMaterialObject == NULL) continue; if (theMaterialObject->m_Type == GraphObjectTypes::DefaultMaterial) { SDefaultMaterial &theMaterial( static_cast(*theMaterialObject)); SDefaultMaterialPreparationResult theMaterialPrepResult( PrepareDefaultMaterialForRender(theMaterial, renderableFlags, subsetOpacity, clearMaterialDirtyFlags)); SShaderDefaultMaterialKey theGeneratedKey = theMaterialPrepResult.m_MaterialKey; subsetOpacity = theMaterialPrepResult.m_Opacity; SRenderableImage *firstImage(theMaterialPrepResult.m_FirstImage); subsetDirty |= theMaterialPrepResult.m_Dirty; renderableFlags = theMaterialPrepResult.m_RenderableFlags; m_Renderer.DefaultMaterialShaderKeyProperties() .m_TessellationMode.SetTessellationMode(theGeneratedKey, inModel.m_TessellationMode, true); NVConstDataRef boneGlobals; if (theSubset.m_Joints.size()) { QT3DS_ASSERT(false); } theRenderableObject = RENDER_FRAME_NEW(SSubsetRenderable)( renderableFlags, theModelCenter, m_Renderer, theSubset, theMaterial, theModelContext, subsetOpacity, firstImage, theGeneratedKey, boneGlobals); subsetDirty = subsetDirty || renderableFlags.IsDirty(); } // if type == DefaultMaterial else if (theMaterialObject->m_Type == GraphObjectTypes::CustomMaterial) { SCustomMaterial &theMaterial( static_cast(*theMaterialObject)); ICustomMaterialSystem &theMaterialSystem( qt3dsContext.GetCustomMaterialSystem()); subsetDirty |= theMaterialSystem.PrepareForRender( theModelContext.m_Model, theSubset, theMaterial, clearMaterialDirtyFlags); SDefaultMaterialPreparationResult theMaterialPrepResult( PrepareCustomMaterialForRender(theMaterial, renderableFlags, subsetOpacity)); SShaderDefaultMaterialKey theGeneratedKey = theMaterialPrepResult.m_MaterialKey; subsetOpacity = theMaterialPrepResult.m_Opacity; SRenderableImage *firstImage(theMaterialPrepResult.m_FirstImage); renderableFlags = theMaterialPrepResult.m_RenderableFlags; // prepare for render tells us if the object is transparent if (theMaterial.m_hasTransparency) renderableFlags |= RenderPreparationResultFlagValues::HasTransparency; // prepare for render tells us if the object is transparent if (theMaterial.m_hasRefraction) renderableFlags |= RenderPreparationResultFlagValues::HasRefraction; m_Renderer.DefaultMaterialShaderKeyProperties() .m_TessellationMode.SetTessellationMode(theGeneratedKey, inModel.m_TessellationMode, true); if (theMaterial.m_IblProbe && CheckLightProbeDirty(*theMaterial.m_IblProbe)) { m_Renderer.PrepareImageForIbl(*theMaterial.m_IblProbe); } theRenderableObject = RENDER_FRAME_NEW(SCustomMaterialRenderable)( renderableFlags, theModelCenter, m_Renderer, theSubset, theMaterial, theModelContext, subsetOpacity, firstImage, theGeneratedKey); } if (theRenderableObject) { theRenderableObject->m_ScopedLights = inScopedLights; // set tessellation theRenderableObject->m_TessellationMode = inModel.m_TessellationMode; if (theRenderableObject->m_RenderableFlags.HasTransparency() || theRenderableObject->m_RenderableFlags.HasRefraction()) { m_TransparentObjects.push_back(theRenderableObject); } else { m_OpaqueObjects.push_back(theRenderableObject); } } } } return subsetDirty; } bool SLayerRenderPreparationData::PrepareRenderablesForRender( const QT3DSMat44 &inViewProjection, const Option &inClipFrustum, QT3DSF32 inTextScaleFactor, SLayerRenderPreparationResultFlags &ioFlags) { SStackPerfTimer __timer(m_Renderer.GetQt3DSContext().GetPerfTimer(), "SLayerRenderData::PrepareRenderablesForRender"); m_ViewProjection = inViewProjection; QT3DSF32 theTextScaleFactor = inTextScaleFactor; bool wasDataDirty = false; bool hasTextRenderer = m_Renderer.GetQt3DSContext().GetTextRenderer() != NULL; for (QT3DSU32 idx = 0, end = m_RenderableNodes.size(); idx < end; ++idx) { SRenderableNodeEntry &theNodeEntry(m_RenderableNodes[idx]); SNode *theNode = theNodeEntry.m_Node; wasDataDirty = wasDataDirty || theNode->m_Flags.IsDirty(); switch (theNode->m_Type) { case GraphObjectTypes::Model: { SModel *theModel = static_cast(theNode); theModel->CalculateGlobalVariables(); if (theModel->m_Flags.IsGloballyActive()) { bool wasModelDirty = PrepareModelForRender( *theModel, inViewProjection, inClipFrustum, theNodeEntry.m_Lights); wasDataDirty = wasDataDirty || wasModelDirty; } } break; case GraphObjectTypes::Text: { if (hasTextRenderer) { SText *theText = static_cast(theNode); theText->CalculateGlobalVariables(); if (theText->m_Flags.IsGloballyActive()) { bool wasTextDirty = PrepareTextForRender(*theText, inViewProjection, theTextScaleFactor, ioFlags); wasDataDirty = wasDataDirty || wasTextDirty; } } } break; case GraphObjectTypes::Path: { SPath *thePath = static_cast(theNode); thePath->CalculateGlobalVariables(); if (thePath->m_Flags.IsGloballyActive()) { bool wasPathDirty = PreparePathForRender(*thePath, inViewProjection, inClipFrustum, ioFlags); wasDataDirty = wasDataDirty || wasPathDirty; } } break; default: QT3DS_ASSERT(false); break; } } return wasDataDirty; } bool SLayerRenderPreparationData::CheckLightProbeDirty(SImage &inLightProbe) { IQt3DSRenderContext &theContext(m_Renderer.GetQt3DSContext()); return inLightProbe.ClearDirty(theContext.GetBufferManager(), theContext.GetOffscreenRenderManager(), theContext.GetRenderPluginManager(), true); } struct SLightNodeMarker { SLight *m_Light; QT3DSU32 m_LightIndex; QT3DSU32 m_FirstValidIndex; QT3DSU32 m_JustPastLastValidIndex; bool m_AddOrRemove; SLightNodeMarker() : m_Light(NULL) , m_FirstValidIndex(0) , m_JustPastLastValidIndex(0) , m_AddOrRemove(false) { } SLightNodeMarker(SLight &inLight, QT3DSU32 inLightIndex, SNode &inNode, bool aorm) : m_Light(&inLight) , m_LightIndex(inLightIndex) , m_AddOrRemove(aorm) { if (inNode.m_Type == GraphObjectTypes::Layer) { m_FirstValidIndex = 0; m_JustPastLastValidIndex = QT3DS_MAX_U32; } else { m_FirstValidIndex = inNode.m_DFSIndex; SNode *lastChild = NULL; SNode *firstChild = inNode.m_FirstChild; // find deepest last child while (firstChild) { for (SNode *childNode = firstChild; childNode; childNode = childNode->m_NextSibling) lastChild = childNode; if (lastChild) firstChild = lastChild->m_FirstChild; else firstChild = NULL; } if (lastChild) // last valid index would be the last child index + 1 m_JustPastLastValidIndex = lastChild->m_DFSIndex + 1; else // no children. m_JustPastLastValidIndex = m_FirstValidIndex + 1; } } }; // m_Layer.m_Camera->CalculateViewProjectionMatrix(m_ViewProjection); void SLayerRenderPreparationData::PrepareForRender(const QSize &inViewportDimensions) { SStackPerfTimer __timer(m_Renderer.GetQt3DSContext().GetPerfTimer(), "SLayerRenderData::PrepareForRender"); if (m_LayerPrepResult.hasValue()) return; m_Features.clear(); m_FeatureSetHash = 0; QT3DSVec2 thePresentationDimensions((QT3DSF32)inViewportDimensions.width(), (QT3DSF32)inViewportDimensions.height()); IRenderList &theGraph(m_Renderer.GetQt3DSContext().GetRenderList()); NVRenderRect theViewport(theGraph.GetViewport()); NVRenderRect theScissor(theGraph.GetViewport()); if (theGraph.IsScissorTestEnabled()) theScissor = m_Renderer.GetContext().GetScissorRect(); bool wasDirty = false; bool wasDataDirty = false; wasDirty = m_Layer.m_Flags.IsDirty(); // The first pass is just to render the data. QT3DSU32 maxNumAAPasses = m_Layer.m_ProgressiveAAMode == AAModeValues::NoAA ? (QT3DSU32)0 : (QT3DSU32)(m_Layer.m_ProgressiveAAMode) + 1; maxNumAAPasses = NVMin((QT3DSU32)(MAX_AA_LEVELS + 1), maxNumAAPasses); SEffect *theLastEffect = NULL; // Uncomment the line below to disable all progressive AA. // maxNumAAPasses = 0; SLayerRenderPreparationResult thePrepResult; bool hasOffscreenRenderer = GetOffscreenRenderer(); bool SSAOEnabled = (m_Layer.m_AoStrength > 0.0f && m_Layer.m_AoDistance > 0.0f); bool SSDOEnabled = (m_Layer.m_ShadowStrength > 0.0f && m_Layer.m_ShadowDist > 0.0f); SetShaderFeature("QT3DS_ENABLE_SSAO", SSAOEnabled); SetShaderFeature("QT3DS_ENABLE_SSDO", SSDOEnabled); bool requiresDepthPrepass = (hasOffscreenRenderer == false) && (SSAOEnabled || SSDOEnabled); SetShaderFeature("QT3DS_ENABLE_SSM", false); // by default no shadow map generation if (m_Layer.m_Flags.IsActive()) { // Get the layer's width and height. IEffectSystem &theEffectSystem(m_Renderer.GetQt3DSContext().GetEffectSystem()); for (SEffect *theEffect = m_Layer.m_FirstEffect; theEffect; theEffect = theEffect->m_NextEffect) { if (theEffect->m_Flags.IsDirty()) { wasDirty = true; theEffect->m_Flags.SetDirty(false); } if (theEffect->m_Flags.IsActive()) { // If the effect uses subpresentations, those have to be rendered before // the effect itself theEffectSystem.renderSubpresentations(*theEffect); theLastEffect = theEffect; if (hasOffscreenRenderer == false && theEffectSystem.DoesEffectRequireDepthTexture(theEffect->m_ClassName)) requiresDepthPrepass = true; } } if (m_Layer.m_Flags.IsDirty()) { wasDirty = true; m_Layer.CalculateGlobalVariables(); } bool shouldRenderToTexture = true; if (hasOffscreenRenderer) { // We don't render to texture with offscreen renderers, we just render them to the // viewport. shouldRenderToTexture = false; // Progaa disabled when using offscreen rendering. maxNumAAPasses = 0; } thePrepResult = SLayerRenderPreparationResult(SLayerRenderHelper( theViewport, theScissor, m_Layer.m_Scene->m_Presentation->m_PresentationDimensions, m_Layer, shouldRenderToTexture, m_Renderer.GetQt3DSContext().GetScaleMode(), m_Renderer.GetQt3DSContext().GetPresentationScaleFactor())); thePrepResult.m_LastEffect = theLastEffect; thePrepResult.m_MaxAAPassIndex = maxNumAAPasses; thePrepResult.m_Flags.SetRequiresDepthTexture(requiresDepthPrepass || NeedsWidgetTexture()); thePrepResult.m_Flags.SetShouldRenderToTexture(shouldRenderToTexture); if (m_Renderer.GetContext().GetRenderContextType() != NVRenderContextValues::GLES2) thePrepResult.m_Flags.SetRequiresSsaoPass(SSAOEnabled); if (thePrepResult.IsLayerVisible()) { if (shouldRenderToTexture) { m_Renderer.GetQt3DSContext().GetRenderList().AddRenderTask( CreateRenderToTextureRunnable()); } if (m_Layer.m_LightProbe && CheckLightProbeDirty(*m_Layer.m_LightProbe)) { m_Renderer.PrepareImageForIbl(*m_Layer.m_LightProbe); wasDataDirty = true; } bool lightProbeValid = HasValidLightProbe(m_Layer.m_LightProbe); SetShaderFeature("QT3DS_ENABLE_LIGHT_PROBE", lightProbeValid); SetShaderFeature("QT3DS_ENABLE_IBL_FOV", m_Layer.m_ProbeFov < 180.0f); if (lightProbeValid && m_Layer.m_LightProbe2 && CheckLightProbeDirty(*m_Layer.m_LightProbe2)) { m_Renderer.PrepareImageForIbl(*m_Layer.m_LightProbe2); wasDataDirty = true; } SetShaderFeature("QT3DS_ENABLE_LIGHT_PROBE_2", lightProbeValid && HasValidLightProbe(m_Layer.m_LightProbe2)); // Push nodes in reverse depth first order if (m_RenderableNodes.empty()) { m_CamerasAndLights.clear(); QT3DSU32 dfsIndex = 0; for (SNode *theChild = m_Layer.m_FirstChild; theChild; theChild = theChild->m_NextSibling) MaybeQueueNodeForRender(*theChild, m_RenderableNodes, m_CamerasAndLights, dfsIndex); reverse(m_CamerasAndLights.begin(), m_CamerasAndLights.end()); reverse(m_RenderableNodes.begin(), m_RenderableNodes.end()); m_LightToNodeMap.clear(); } m_Camera = NULL; m_Lights.clear(); m_OpaqueObjects.clear(); m_TransparentObjects.clear(); nvvector theLightNodeMarkers(m_Renderer.GetPerFrameAllocator(), "LightNodeMarkers"); m_SourceLightDirections.clear(); for (QT3DSU32 idx = 0, end = m_CamerasAndLights.size(); idx < end; ++idx) { SNode *theNode(m_CamerasAndLights[idx]); wasDataDirty = wasDataDirty || theNode->m_Flags.IsDirty(); switch (theNode->m_Type) { case GraphObjectTypes::Camera: { SCamera *theCamera = static_cast(theNode); if (theCamera->m_Flags.IsActive()) { // Only proceed with camera nodes which are currently active. // SetupCameraForRender() sets the camera used for picking and // updates global state e.g. IsGloballyActive() SCameraGlobalCalculationResult theResult = thePrepResult.SetupCameraForRender(*theCamera); wasDataDirty = wasDataDirty || theResult.m_WasDirty; if (theCamera->m_Flags.IsGloballyActive()) m_Camera = theCamera; if (theResult.m_ComputeFrustumSucceeded == false) { qCCritical(INTERNAL_ERROR, "Failed to calculate camera frustum"); } } } break; case GraphObjectTypes::Light: { SLight *theLight = static_cast(theNode); 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. QT3DSU32 lightIndex = (QT3DSU32)m_SourceLightDirections.size(); m_SourceLightDirections.push_back(QT3DSVec3(0.0f)); // 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->m_Flags.IsGloballyActive()) { if (theLight->m_Scope == NULL) { m_Lights.push_back(theLight); if (m_Renderer.GetContext().GetRenderContextType() != NVRenderContextValues::GLES2 && theLight->m_CastShadow && GetShadowMapManager()) { // 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. QT3DSU32 mapSize = 1 << theLight->m_ShadowMapRes; ShadowMapModes::Enum mapMode = (theLight->m_LightType != RenderLightTypes::Directional) ? ShadowMapModes::CUBE : ShadowMapModes::VSM; m_ShadowMapManager->AddShadowMapEntry( m_Lights.size() - 1, mapSize, mapSize, NVRenderTextureFormats::R16F, 1, mapMode, ShadowFilterValues::NONE); thePrepResult.m_Flags.SetRequiresShadowMapPass(true); SetShaderFeature("QT3DS_ENABLE_SSM", true); } } TLightToNodeMap::iterator iter = m_LightToNodeMap.insert(eastl::make_pair(theLight, (SNode *)NULL)) .first; SNode *oldLightScope = iter->second; SNode *newLightScope = theLight->m_Scope; if (oldLightScope != newLightScope) { iter->second = newLightScope; if (oldLightScope) theLightNodeMarkers.push_back(SLightNodeMarker( *theLight, lightIndex, *oldLightScope, false)); if (newLightScope) theLightNodeMarkers.push_back(SLightNodeMarker( *theLight, lightIndex, *newLightScope, true)); } if (newLightScope) { m_SourceLightDirections.back() = theLight->GetScalingCorrectDirection(); } } } break; default: QT3DS_ASSERT(false); break; } } if (theLightNodeMarkers.empty() == false) { for (QT3DSU32 idx = 0, end = m_RenderableNodes.size(); idx < end; ++idx) { SRenderableNodeEntry &theNodeEntry(m_RenderableNodes[idx]); QT3DSU32 nodeDFSIndex = theNodeEntry.m_Node->m_DFSIndex; for (QT3DSU32 markerIdx = 0, markerEnd = theLightNodeMarkers.size(); markerIdx < markerEnd; ++markerIdx) { SLightNodeMarker &theMarker = theLightNodeMarkers[markerIdx]; if (nodeDFSIndex >= theMarker.m_FirstValidIndex && nodeDFSIndex < theMarker.m_JustPastLastValidIndex) { if (theMarker.m_AddOrRemove) { SNodeLightEntry *theNewEntry = m_RenderableNodeLightEntryPool.construct( theMarker.m_Light, theMarker.m_LightIndex, __FILE__, __LINE__); theNodeEntry.m_Lights.push_back(*theNewEntry); } else { for (TNodeLightEntryList::iterator lightIter = theNodeEntry.m_Lights.begin(), lightEnd = theNodeEntry.m_Lights.end(); lightIter != lightEnd; ++lightIter) { if (lightIter->m_Light == theMarker.m_Light) { SNodeLightEntry &theEntry = *lightIter; theNodeEntry.m_Lights.remove(theEntry); m_RenderableNodeLightEntryPool.deallocate(&theEntry); break; } } } } } } } QT3DSF32 theTextScaleFactor = 1.0f; if (m_Camera) { m_Camera->CalculateViewProjectionMatrix(m_ViewProjection); theTextScaleFactor = m_Camera->GetTextScaleFactor( thePrepResult.GetLayerToPresentationViewport(), thePrepResult.GetPresentationDesignDimensions()); SClipPlane nearPlane; QT3DSMat33 theUpper33(m_Camera->m_GlobalTransform.getUpper3x3InverseTranspose()); QT3DSVec3 dir(theUpper33.transform(QT3DSVec3(0, 0, -1))); dir.normalize(); nearPlane.normal = dir; QT3DSVec3 theGlobalPos = m_Camera->GetGlobalPos() + m_Camera->m_ClipNear * dir; nearPlane.d = -(dir.dot(theGlobalPos)); // the near plane's bbox edges are calculated in the clipping frustum's // constructor. m_ClippingFrustum = SClippingFrustum(m_ViewProjection, nearPlane); } else { m_ViewProjection = QT3DSMat44::createIdentity(); } // Setup the light directions here. for (QT3DSU32 lightIdx = 0, lightEnd = m_Lights.size(); lightIdx < lightEnd; ++lightIdx) { m_LightDirections.push_back(m_Lights[lightIdx]->GetScalingCorrectDirection()); } m_ModelContexts.clear(); if (GetOffscreenRenderer() == false) { bool renderablesDirty = PrepareRenderablesForRender(m_ViewProjection, m_ClippingFrustum, theTextScaleFactor, thePrepResult.m_Flags); wasDataDirty = wasDataDirty || renderablesDirty; if (thePrepResult.m_Flags.RequiresStencilBuffer()) thePrepResult.m_Flags.SetShouldRenderToTexture(true); } else { NVRenderRect theViewport = thePrepResult.GetLayerToPresentationViewport().ToIntegerRect(); bool theScissor = true; NVRenderRect theScissorRect = thePrepResult.GetLayerToPresentationScissorRect().ToIntegerRect(); // This happens here because if there are any fancy render steps IRenderList &theRenderList(m_Renderer.GetQt3DSContext().GetRenderList()); NVRenderContext &theContext(m_Renderer.GetContext()); SRenderListScopedProperty _listScissorEnabled( theRenderList, &IRenderList::IsScissorTestEnabled, &IRenderList::SetScissorTestEnabled, theScissor); SRenderListScopedProperty _listViewport( theRenderList, &IRenderList::GetViewport, &IRenderList::SetViewport, theViewport); SRenderListScopedProperty _listScissor( theRenderList, &IRenderList::GetScissor, &IRenderList::SetScissorRect, theScissorRect); // Some plugins don't use the render list so they need the actual gl context // setup. qt3ds::render::NVRenderContextScopedProperty __scissorEnabled( theContext, &NVRenderContext::IsScissorTestEnabled, &NVRenderContext::SetScissorTestEnabled, true); qt3ds::render::NVRenderContextScopedProperty __scissorRect( theContext, &NVRenderContext::GetScissorRect, &NVRenderContext::SetScissorRect, theScissorRect); qt3ds::render::NVRenderContextScopedProperty __viewportRect( theContext, &NVRenderContext::GetViewport, &NVRenderContext::SetViewport, theViewport); SOffscreenRenderFlags theResult = m_LastFrameOffscreenRenderer->NeedsRender( CreateOffscreenRenderEnvironment(), m_Renderer.GetQt3DSContext().GetPresentationScaleFactor(), &m_Layer); wasDataDirty = wasDataDirty || theResult.m_HasChangedSinceLastFrame; } } } wasDirty = wasDirty || wasDataDirty; thePrepResult.m_Flags.SetWasDirty(wasDirty); thePrepResult.m_Flags.SetLayerDataDirty(wasDataDirty); m_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 SLayerRenderPreparationData::ResetForFrame() { m_TransparentObjects.clear_unsafe(); m_OpaqueObjects.clear_unsafe(); m_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. m_Camera = NULL; m_LastFrameOffscreenRenderer = NULL; m_IRenderWidgets.clear_unsafe(); m_CameraDirection.setEmpty(); m_LightDirections.clear_unsafe(); m_RenderedOpaqueObjects.clear_unsafe(); m_RenderedTransparentObjects.clear_unsafe(); } } }