/**************************************************************************** ** ** 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 "Qt3DSRenderGraphObjectSerializer.h" #include "Qt3DSRenderPresentation.h" #include "Qt3DSRenderNode.h" #include "Qt3DSRenderScene.h" #include "Qt3DSRenderLayer.h" #include "Qt3DSRenderModel.h" #include "Qt3DSRenderText.h" #include "Qt3DSRenderDefaultMaterial.h" #include "Qt3DSRenderImage.h" #include "Qt3DSRenderEffect.h" #include "Qt3DSRenderCamera.h" #include "Qt3DSRenderLight.h" #include "Qt3DSRenderCustomMaterial.h" #include "foundation/Qt3DSFoundation.h" #include "foundation/Qt3DSBroadcastingAllocator.h" #include "foundation/Qt3DSContainers.h" #include "Qt3DSRenderEffectSystem.h" #include "foundation/SerializationTypes.h" #include "StringTools.h" #include "foundation/FileTools.h" #include "Qt3DSRenderPluginGraphObject.h" #include "Qt3DSRenderReferencedMaterial.h" #include "Qt3DSRenderPath.h" #include "Qt3DSRenderPathSubPath.h" #include "Qt3DSRenderPathManager.h" using namespace qt3ds::render; using namespace qt3ds::render::dynamic; namespace { typedef nvhash_set TPtrSet; void Align(MemoryBuffer<> &inBuffer) { inBuffer.align(sizeof(void *)); } typedef nvvector> TObjectFileStatList; typedef SPtrOffsetMap TPtrOffsetMap; struct SSerializerWriteContext { SPtrOffsetMap &m_OffsetMap; SWriteBuffer &m_MemoryBuffer; const SStrRemapMap &m_StrRemapMap; QT3DSU32 m_DataBlockStart; IDynamicObjectSystem &m_DynamicObjectSystem; IPathManager &m_PathManager; TObjectFileStatList &m_FileSizeStats; Qt3DSString m_PathMapper; Qt3DSString m_BasePath; Qt3DSString m_RelativePath; IStringTable &m_StringTable; SSerializerWriteContext(SPtrOffsetMap &inOffsetMap, SWriteBuffer &inWriteBuffer, const SStrRemapMap &inStrMap, QT3DSU32 inDataBlockStart, IDynamicObjectSystem &inDynamicObjectSystem, IPathManager &inPathManager, TObjectFileStatList &inStats, NVAllocatorCallback &inAllocator, const char8_t *inProjectDirectory, IStringTable &inStringTable) : m_OffsetMap(inOffsetMap) , m_MemoryBuffer(inWriteBuffer) , m_StrRemapMap(inStrMap) , m_DataBlockStart(inDataBlockStart) , m_DynamicObjectSystem(inDynamicObjectSystem) , m_PathManager(inPathManager) , m_FileSizeStats(inStats) , m_StringTable(inStringTable) { Q_UNUSED(inAllocator) m_BasePath.assign(inProjectDirectory); } bool HasWrittenObject(const void *inObject) { return m_OffsetMap.contains(inObject); } QT3DSU32 &GetStatEntry(GraphObjectTypes::Enum inType) const { for (QT3DSU32 idx = 0, end = m_FileSizeStats.size(); idx < end; ++idx) if (m_FileSizeStats[idx].first == inType) return m_FileSizeStats[idx].second; m_FileSizeStats.push_back(eastl::make_pair(inType, (QT3DSU32)0)); return m_FileSizeStats.back().second; } template void AddPtrOffset(const TObjType *inObject) { QT3DSU32 objOffset = m_MemoryBuffer.size() - m_DataBlockStart; m_OffsetMap.insert(eastl::make_pair(inObject, objOffset)); // In debug we keep stats on how much each type of object // contributes to the file size. #ifdef _DEBUG GetStatEntry(inObject->m_Type) += sizeof(TObjType); #endif } void Remap(CRegisteredString &inStr) { inStr.Remap(m_StrRemapMap); } template void Remap(TObjType *&inPtr) { if (inPtr) { TPtrOffsetMap::iterator theIter = m_OffsetMap.find(inPtr); if (theIter != m_OffsetMap.end()) inPtr = reinterpret_cast(theIter->second); else { QT3DS_ASSERT(false); } } } void RemapMaterial(SGraphObject *&inPtr) { Remap(inPtr); } template void NullPtr(TObjType *&inPtr) { inPtr = NULL; } }; /////////////////////////////////////////////////////////////////////// // --** Reading the scene graph is heavily threaded when we are loading // multiple presentations in one application --** /////////////////////////////////////////////////////////////////////// struct SSerializerReadContext : public SDataReader { IPathManagerCore &m_PathManager; IDynamicObjectSystemCore &m_DynamicObjectSystem; NVDataRef m_DataBlock; NVDataRef m_StrTableBlock; Qt3DSString m_PathMapper; const char8_t *m_ProjectDirectory; SSerializerReadContext(IPathManagerCore &inPathManager, IDynamicObjectSystemCore &inDynSystem, NVDataRef inDataBlock, NVDataRef inStrTable, NVAllocatorCallback &inAllocator, const char8_t *inProjectDirectory) : SDataReader(inDataBlock.begin(), inDataBlock.end()) , m_PathManager(inPathManager) , m_DynamicObjectSystem(inDynSystem) , m_DataBlock(inDataBlock) , m_StrTableBlock(inStrTable) , m_ProjectDirectory(inProjectDirectory) { Q_UNUSED(inAllocator) } void Remap(CRegisteredString &inStr) { inStr.Remap(m_StrTableBlock); } template void Remap(TObjType *&inPtr) { if (inPtr) { TObjType *purePtr = inPtr; size_t ptrValue = reinterpret_cast(purePtr); if (ptrValue < m_DataBlock.size()) inPtr = reinterpret_cast(m_DataBlock.begin() + ptrValue); else { QT3DS_ASSERT(false); inPtr = NULL; } } } void RemapMaterial(SGraphObject *&inPtr) { Remap(inPtr); } // Nulling out pointers was done on write, so we don't do it here. template void NullPtr(TObjType *&) { } }; template struct SGraphObjectSerializerImpl { static TObjType *Write(const TObjType &ioObject, SSerializerWriteContext &outSavedBuffer); static void Remap(TObjType &inObject, SSerializerWriteContext &inRemapContext); static TObjType *Read(SSerializerReadContext &inReadContext); }; struct SWriteRemapper { SSerializerWriteContext &m_WriteBuffer; SWriteRemapper(SSerializerWriteContext &buf) : m_WriteBuffer(buf) { } // This will happen later void Remap(const CRegisteredString &) {} void RemapPath(const CRegisteredString &) {} // We ignore objects that are saved out explicitly below. void Remap(const SScene *) {} void Remap(const SLayer *) {} // Nodes are ignored because we save them out *not* in depth first order, // with models, text, lights and camera saved out contiguously for in-memory // traversal. void Remap(const SNode *) {} #ifdef _INTEGRITYPLATFORM // explicit specialization of class "::SGraphObjectSerializerImpl" // must precede its first use struct SGraphObjectSerializerImpl template void Remap(const TObjType *inObj); #else template void Remap(const TObjType *inObj) { if (inObj) SGraphObjectSerializerImpl::Write(*inObj, m_WriteBuffer); } #endif void RemapMaterial(const SGraphObject *inObj) { if (inObj) { if (inObj->m_Type == GraphObjectTypes::DefaultMaterial) Remap(static_cast(inObj)); else if (inObj->m_Type == GraphObjectTypes::CustomMaterial) Remap(static_cast(inObj)); else if (inObj->m_Type == GraphObjectTypes::ReferencedMaterial) Remap(static_cast(inObj)); else { QT3DS_ASSERT(false); } } } template void NullPtr(const TObjType *) { } }; void PrepareFirstPass(const SNode &inNode, nvvector &ioLightCameras, nvvector &ioRenderable) { if (GraphObjectTypes::IsRenderableType(inNode.m_Type)) ioRenderable.push_back(&inNode); else if (GraphObjectTypes::IsLightCameraType(inNode.m_Type)) ioLightCameras.push_back(&inNode); for (const SNode *theChild = inNode.m_FirstChild; theChild; theChild = theChild->m_NextSibling) PrepareFirstPass(*theChild, ioLightCameras, ioRenderable); } template TObject *WriteGenericGraphObjectNoRemap(const TObject &ioObject, SSerializerWriteContext &outSavedBuffer) { if (outSavedBuffer.HasWrittenObject(&ioObject)) return NULL; outSavedBuffer.AddPtrOffset(&ioObject); QT3DSU32 theOffset = outSavedBuffer.m_MemoryBuffer.size(); outSavedBuffer.m_MemoryBuffer.write(ioObject); // Probably the buffer stays aligned but we want to work to keep it that way. Align(outSavedBuffer.m_MemoryBuffer); return reinterpret_cast(outSavedBuffer.m_MemoryBuffer.begin() + theOffset); } template TObject *WriteGenericGraphObject(const TObject &ioObject, SSerializerWriteContext &outSavedBuffer) { TObject *theObject = WriteGenericGraphObjectNoRemap(ioObject, outSavedBuffer); if (theObject) // The object may have already been written. { // Write mappers just follow pointers and ensure all the associated objects // are written out. SWriteRemapper theWriteRemapper(outSavedBuffer); const_cast(ioObject).Remap(theWriteRemapper); } return theObject; } template TObject *ReadGenericGraphObject(SSerializerReadContext &inReadContext) { TObject *retval = inReadContext.Load(); inReadContext.Align(); if (retval) { retval->Remap(inReadContext); } return retval; } template TObjType *SGraphObjectSerializerImpl::Write(const TObjType &ioObject, SSerializerWriteContext &outSavedBuffer) { return WriteGenericGraphObject(ioObject, outSavedBuffer); } template void SGraphObjectSerializerImpl::Remap(TObjType &ioObject, SSerializerWriteContext &inRemapContext) { return ioObject.Remap(inRemapContext); } template TObjType *SGraphObjectSerializerImpl::Read(SSerializerReadContext &inReadContext) { return ReadGenericGraphObject(inReadContext); } void RemapProperties(SDynamicObject &ioObject, SSerializerWriteContext &outSavedBuffer, CRegisteredString inClassName) { NVConstDataRef theObjectProps = outSavedBuffer.m_DynamicObjectSystem.GetProperties(inClassName); for (QT3DSU32 idx = 0, end = theObjectProps.size(); idx < end; ++idx) { const SPropertyDefinition &theDef(theObjectProps[idx]); if (theDef.m_DataType == qt3ds::render::NVRenderShaderDataTypes::NVRenderTexture2DPtr) { CRegisteredString *theStr = reinterpret_cast( ioObject.GetDataSectionBegin() + theDef.m_Offset); outSavedBuffer.Remap(*theStr); } } } void RemapProperties(SDynamicObject &ioObject, SSerializerReadContext &inReadContext) { // CN - !!Note this call is done on multiple threads simultaneously. I added a mutex just to be // sure even though // this is a read-only call; I am not certain how good the arm memory locking is when it is // completely unprotected. NVConstDataRef theProperties = inReadContext.m_DynamicObjectSystem.GetProperties(ioObject.m_ClassName); for (QT3DSU32 idx = 0, end = theProperties.size(); idx < end; ++idx) { const SPropertyDefinition &theDefinition(theProperties[idx]); if (theDefinition.m_DataType == NVRenderShaderDataTypes::NVRenderTexture2DPtr) { CRegisteredString *theString = reinterpret_cast( ioObject.GetDataSectionBegin() + theDefinition.m_Offset); inReadContext.Remap(*theString); } } } template <> struct SGraphObjectSerializerImpl { static SGraphObject *Write(const SEffect &ioObject, SSerializerWriteContext &outSavedBuffer) { size_t itemOffset = outSavedBuffer.m_MemoryBuffer.size(); SEffect *theNewEffect = static_cast(WriteGenericGraphObjectNoRemap(ioObject, outSavedBuffer)); if (theNewEffect) { theNewEffect->m_Context = NULL; // Writing it out is easy. Reading it back in means we have to have a correctly setup // IEffectManager so we // can remap strings. outSavedBuffer.m_MemoryBuffer.write(ioObject.GetDataSectionBegin(), ioObject.m_DataSectionByteSize); Align(outSavedBuffer.m_MemoryBuffer); SWriteRemapper theWriteRemapper(outSavedBuffer); // Write any connected objects. theNewEffect = reinterpret_cast(outSavedBuffer.m_MemoryBuffer.begin() + itemOffset); theNewEffect->Remap(theWriteRemapper); } return theNewEffect; } static void Remap(SEffect &ioObject, SSerializerWriteContext &outSavedBuffer) { CRegisteredString theClassName = ioObject.m_ClassName; ioObject.Remap(outSavedBuffer); RemapProperties(ioObject, outSavedBuffer, theClassName); } static SEffect *Read(SSerializerReadContext &inReadContext) { SEffect *theEffect = ReadGenericGraphObject(inReadContext); if (theEffect) { inReadContext.m_CurrentPtr += theEffect->m_DataSectionByteSize; inReadContext.Align(); RemapProperties(*theEffect, inReadContext); } return theEffect; } }; template <> struct SGraphObjectSerializerImpl { static SGraphObject *Write(const SCustomMaterial &ioObject, SSerializerWriteContext &outSavedBuffer) { size_t itemOffset = outSavedBuffer.m_MemoryBuffer.size(); SCustomMaterial *theNewObject = static_cast( WriteGenericGraphObjectNoRemap(ioObject, outSavedBuffer)); if (theNewObject) { // Writing it out is easy. Reading it back in means we have to have a correctly setup // IEffectManager so we // can remap strings. outSavedBuffer.m_MemoryBuffer.write(ioObject.GetDataSectionBegin(), ioObject.m_DataSectionByteSize); Align(outSavedBuffer.m_MemoryBuffer); theNewObject = reinterpret_cast(outSavedBuffer.m_MemoryBuffer.begin() + itemOffset); SWriteRemapper theWriteRemapper(outSavedBuffer); // Write any connected objects. theNewObject->Remap(theWriteRemapper); } return theNewObject; } static void Remap(SCustomMaterial &ioObject, SSerializerWriteContext &outSavedBuffer) { CRegisteredString theClassName(ioObject.m_ClassName); ioObject.Remap(outSavedBuffer); RemapProperties(ioObject, outSavedBuffer, theClassName); } static SCustomMaterial *Read(SSerializerReadContext &inReadContext) { SCustomMaterial *theMaterial = ReadGenericGraphObject(inReadContext); if (theMaterial) { inReadContext.m_CurrentPtr += theMaterial->m_DataSectionByteSize; inReadContext.Align(); RemapProperties(*theMaterial, inReadContext); } return theMaterial; } }; #ifdef _INTEGRITYPLATFORM template void SWriteRemapper::Remap(const TObjType *inObj) { if (inObj) SGraphObjectSerializerImpl::Write(*inObj, m_WriteBuffer); } #endif template <> struct SGraphObjectSerializerImpl { static SGraphObject *Write(const SPathSubPath &ioObject, SSerializerWriteContext &outSavedBuffer) { SPathSubPath *theObject = WriteGenericGraphObjectNoRemap(ioObject, outSavedBuffer); if (theObject) // The object may have already been written. { NVConstDataRef thePoints = outSavedBuffer.m_PathManager.GetPathSubPathBuffer(ioObject); outSavedBuffer.m_MemoryBuffer.write((QT3DSU32)thePoints.size()); outSavedBuffer.m_MemoryBuffer.write(thePoints.begin(), thePoints.size()); // Write mappers just follow pointers and ensure all the associated objects // are written out. SWriteRemapper theWriteRemapper(outSavedBuffer); const_cast(ioObject).Remap(theWriteRemapper); } return theObject; } static void Remap(SPathSubPath &ioObject, SSerializerWriteContext &outSavedBuffer) { ioObject.Remap(outSavedBuffer); } static SPathSubPath *Read(SSerializerReadContext &inReadContext) { SPathSubPath *theSubPath = ReadGenericGraphObject(inReadContext); if (theSubPath) { QT3DSU32 numPoints = *inReadContext.Load(); SPathAnchorPoint *theAnchorPointBuffer = reinterpret_cast(inReadContext.m_CurrentPtr); inReadContext.m_CurrentPtr += sizeof(SPathAnchorPoint) * numPoints; // CN - !!Note this call is done on multiple threads simultaneously. I added a mutex to // the path manager object // so this exact call is always protected. This absolutely caused crashing when it was // not protected approriately. inReadContext.m_PathManager.SetPathSubPathData( *theSubPath, toConstDataRef(theAnchorPointBuffer, numPoints)); } return theSubPath; } }; void WriteGraphObject(const SGraphObject &inObject, SSerializerWriteContext &outSavedBuffer) { SGraphObject *newObject = NULL; switch (inObject.m_Type) { #define QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE(type) \ case GraphObjectTypes::type: \ newObject = SGraphObjectSerializerImpl::Write( \ static_cast(inObject), outSavedBuffer); \ break; QT3DS_RENDER_ITERATE_GRAPH_OBJECT_TYPES #undef QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE default: QT3DS_ASSERT(false); break; } } void WriteNodeList(nvvector &inList, SSerializerWriteContext &outSavedBuffer) { for (QT3DSU32 idx = 0, end = inList.size(); idx < end; ++idx) WriteGraphObject(*inList[idx], outSavedBuffer); } // Now write everything you haven't written so far, skip writing renderables or cameras or lights void WriteNonRenderableNonCLNode(const SNode &inNode, SSerializerWriteContext &outSavedBuffer) { if (GraphObjectTypes::IsLightCameraType(inNode.m_Type) == false && GraphObjectTypes::IsRenderableType(inNode.m_Type) == false) { WriteGraphObject(inNode, outSavedBuffer); } for (const SNode *theChild = inNode.m_FirstChild; theChild; theChild = theChild->m_NextSibling) WriteNonRenderableNonCLNode(*theChild, outSavedBuffer); } SGraphObject *ReadGraphObject(SSerializerReadContext &inContext) { if (inContext.m_CurrentPtr + sizeof(SGraphObject) < inContext.m_EndPtr) { SGraphObject *theObject = reinterpret_cast(inContext.m_CurrentPtr); switch (theObject->m_Type) { #define QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE(type) \ case GraphObjectTypes::type: \ SGraphObjectSerializerImpl::Read(inContext); \ break; QT3DS_RENDER_ITERATE_GRAPH_OBJECT_TYPES #undef QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE default: QT3DS_ASSERT(false); theObject = NULL; break; } return theObject; } return NULL; } } void SGraphObjectSerializer::Save(NVFoundationBase &inFoundation, const SPresentation &inPresentation, qt3ds::render::SWriteBuffer &outSavedData, IDynamicObjectSystem &inDynamicObjectSystem, IPathManager &inPathManager, SPtrOffsetMap &outSceneGraphOffsets, IStringTable &inStringTable, NVDataRef inExtraGraphObjects) { using namespace qt3ds::foundation; nvvector theLightCameraList(inFoundation.getAllocator(), "SGraphObjectSerializer::theLightCameraList"); nvvector theRenderableList(inFoundation.getAllocator(), "SGraphObjectSerializer::theRenderableList"); TObjectFileStatList theStatList(inFoundation.getAllocator(), "SGraphObjectSerializer::FileSizeStats"); // We want to save out the scene graph in the order we are going to traverse it normally. // This is reverse depth first for the lights, cameras, and renderables and depth first for // everything else so we go // in two passes per layer. // We expect the incoming data buffer to be aligned already. QT3DS_ASSERT(outSavedData.size() % 4 == 0); if (inPresentation.m_Scene) { QT3DSU32 theDataSectionStart = outSavedData.size(); outSavedData.writeZeros(4); SSerializerWriteContext theWriteContext( outSceneGraphOffsets, outSavedData, inStringTable.GetRemapMap(), theDataSectionStart, inDynamicObjectSystem, inPathManager, theStatList, inFoundation.getAllocator(), inPresentation.m_PresentationDirectory, inStringTable); // First pass, just write out the data. WriteGraphObject(inPresentation, theWriteContext); WriteGraphObject(*inPresentation.m_Scene, theWriteContext); for (const SLayer *theLayer = inPresentation.m_Scene->m_FirstChild; theLayer; theLayer = static_cast(theLayer->m_NextSibling)) { theLightCameraList.clear(); theRenderableList.clear(); PrepareFirstPass(*theLayer, theLightCameraList, theRenderableList); eastl::reverse(theLightCameraList.begin(), theLightCameraList.end()); eastl::reverse(theRenderableList.begin(), theRenderableList.end()); WriteNodeList(theLightCameraList, theWriteContext); WriteNodeList(theRenderableList, theWriteContext); } // Now just write everything *but* renderable objects and cameras. for (const SLayer *theLayer = inPresentation.m_Scene->m_FirstChild; theLayer; theLayer = static_cast(theLayer->m_NextSibling)) { WriteNonRenderableNonCLNode(*theLayer, theWriteContext); } // Write out any extra objects we haven't covered yet. for (QT3DSU32 idx = 0, end = inExtraGraphObjects.size(); idx < end; ++idx) WriteGraphObject(*inExtraGraphObjects[idx], theWriteContext); QT3DSU32 theNumObjects = theWriteContext.m_OffsetMap.size(); QT3DSU32 *theCountPtr = reinterpret_cast(outSavedData.begin() + theDataSectionStart); *theCountPtr = theNumObjects; // Second pass, perform remapping on all the objects to change their pointers to offsets for (SPtrOffsetMap::iterator theIter = outSceneGraphOffsets.begin(), theEnd = outSceneGraphOffsets.end(); theIter != theEnd; ++theIter) { QT3DSU8 *theDataPtr = outSavedData.begin() + theDataSectionStart + theIter->second; SGraphObject *theGraphObj = reinterpret_cast(theDataPtr); switch (theGraphObj->m_Type) { #define QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE(type) \ case GraphObjectTypes::type: \ SGraphObjectSerializerImpl::Remap(static_cast(*theGraphObj), \ theWriteContext); \ break; QT3DS_RENDER_ITERATE_GRAPH_OBJECT_TYPES #undef QT3DS_RENDER_HANDL_GRAPH_OBJECT_TYPE default: QT3DS_ASSERT(false); break; } } } #ifdef _DEBUG qCDebug(TRACE_INFO, "--File size stats:"); // Tell the users how much space is used in the file based on object type: for (QT3DSU32 idx = 0, end = theStatList.size(); idx < end; ++idx) { const char *theObjType = GraphObjectTypes::GetObjectTypeName(theStatList[idx].first); qCDebug(TRACE_INFO, "%s - %d bytes:", theObjType, theStatList[idx].second); } qCDebug(TRACE_INFO, "--End file size stats:"); #endif }; SPresentation *SGraphObjectSerializer::Load(NVDataRef inData, NVDataRef inStrDataBlock, IDynamicObjectSystemCore &inDynamicObjectSystem, IPathManagerCore &inPathManager, NVAllocatorCallback &inAllocator, const char8_t *inProjectDirectory) { SSerializerReadContext theReadContext(inPathManager, inDynamicObjectSystem, inData, inStrDataBlock, inAllocator, inProjectDirectory); SPresentation *retval = NULL; if (inData.size() < 4) { QT3DS_ASSERT(false); return NULL; } QT3DSU32 theNumObjects = theReadContext.LoadRef(); for (QT3DSU32 idx = 0, end = theNumObjects; idx < end; ++idx) { SGraphObject *theObject = ReadGraphObject(theReadContext); if (theObject) { if (theObject->m_Type == GraphObjectTypes::Presentation) retval = static_cast(theObject); } else { QT3DS_ASSERT(false); } } return retval; }