diff options
Diffstat (limited to 'src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp')
-rw-r--r-- | src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp new file mode 100644 index 0000000..eb23f3d --- /dev/null +++ b/src/runtimerender/resourcemanager/Qt3DSRenderBufferManager.cpp @@ -0,0 +1,1092 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ +#ifdef _WIN32 +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +#endif +#include "Qt3DSRender.h" +#include "Qt3DSRenderBufferManager.h" +#include "EASTL/string.h" +#include "foundation/Qt3DSAllocator.h" +#include "render/Qt3DSRenderContext.h" +#include "foundation/Qt3DSAtomic.h" +#include "EASTL/hash_map.h" +#include "foundation/FileTools.h" +#include "Qt3DSImportMesh.h" +#include "Qt3DSRenderMesh.h" +#include "foundation/Qt3DSAllocatorCallback.h" +#include "Qt3DSRenderLoadedTexture.h" +#include "foundation/Qt3DSFoundation.h" +#include "Qt3DSRenderInputStreamFactory.h" +#include "Qt3DSRenderImageScaler.h" +#include "Qt3DSRenderImage.h" +#include "Qt3DSTextRenderer.h" +#include "foundation/Qt3DSPerfTimer.h" +#include "foundation/Qt3DSMutex.h" +#include "Qt3DSRenderPrefilterTexture.h" +#include <QtCore/qdir.h> + +using namespace qt3ds::render; + +namespace { + +using eastl::hash; +using eastl::pair; +using eastl::make_pair; +typedef eastl::basic_string<char8_t, ForwardingAllocator> TStr; +struct StrHasher +{ + size_t operator()(const TStr &str) const + { + return hash<const char8_t *>()((const char8_t *)str.c_str()); + } +}; + +struct StrEq +{ + bool operator()(const TStr &lhs, const TStr &rhs) const { return lhs == rhs; } +}; + +struct SImageEntry : public SImageTextureData +{ + bool m_Loaded; + SImageEntry() + : SImageTextureData(), m_Loaded(false) + { + } + SImageEntry(const SImageEntry &entry) + : SImageTextureData(entry), m_Loaded(entry.m_Loaded) + { + + } +}; + +struct SPrimitiveEntry +{ + // Name of the primitive as it will be in the UIP file + CRegisteredString m_PrimitiveName; + // Name of the primitive file on the filesystem + CRegisteredString m_FileName; +}; + +struct SBufferManager : public IBufferManager +{ + typedef eastl::hash_set<CRegisteredString, eastl::hash<CRegisteredString>, + eastl::equal_to<CRegisteredString>, ForwardingAllocator> + TStringSet; + typedef nvhash_map<CRegisteredString, SImageEntry> TImageMap; + typedef nvhash_map<CRegisteredString, SRenderMesh *> TMeshMap; + typedef nvhash_map<CRegisteredString, CRegisteredString> TAliasImageMap; + + NVScopedRefCounted<NVRenderContext> m_Context; + NVScopedRefCounted<IStringTable> m_StrTable; + NVScopedRefCounted<IInputStreamFactory> m_InputStreamFactory; + IPerfTimer &m_PerfTimer; + volatile QT3DSI32 mRefCount; + TStr m_PathBuilder; + TImageMap m_ImageMap; + Mutex m_LoadedImageSetMutex; + TStringSet m_LoadedImageSet; + TAliasImageMap m_AliasImageMap; + TMeshMap m_MeshMap; + SPrimitiveEntry m_PrimitiveNames[5]; + nvvector<qt3ds::render::NVRenderVertexBufferEntry> m_EntryBuffer; + bool m_GPUSupportsDXT; + bool m_reloadableResources; + + QHash<QString, ReloadableTexturePtr> m_reloadableTextures; + + static const char8_t *GetPrimitivesDirectory() { return "res//primitives"; } + + SBufferManager(NVRenderContext &ctx, IStringTable &strTable, + IInputStreamFactory &inInputStreamFactory, IPerfTimer &inTimer) + : m_Context(ctx) + , m_StrTable(strTable) + , m_InputStreamFactory(inInputStreamFactory) + , m_PerfTimer(inTimer) + , mRefCount(0) + , m_PathBuilder(ForwardingAllocator(ctx.GetAllocator(), "SBufferManager::m_PathBuilder")) + , m_ImageMap(ctx.GetAllocator(), "SBufferManager::m_ImageMap") + , m_LoadedImageSetMutex(ctx.GetAllocator()) + , m_LoadedImageSet( + ForwardingAllocator(ctx.GetAllocator(), "SBufferManager::m_LoadedImageSet")) + , m_AliasImageMap(ctx.GetAllocator(), "SBufferManager::m_AliasImageMap") + , m_MeshMap(ctx.GetAllocator(), "SBufferManager::m_MeshMap") + , m_EntryBuffer(ctx.GetAllocator(), "SBufferManager::m_EntryBuffer") + , m_GPUSupportsDXT(ctx.AreDXTImagesSupported()) + , m_reloadableResources(false) + { + } + virtual ~SBufferManager() { Clear(); } + + QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE(m_Context->GetAllocator()) + + CRegisteredString CombineBaseAndRelative(const char8_t *inBase, + const char8_t *inRelative) override + { + CFileTools::CombineBaseAndRelative(inBase, inRelative, m_PathBuilder); + return m_StrTable->RegisterStr(m_PathBuilder.c_str()); + } + + void SetImageHasTransparency(CRegisteredString inImagePath, bool inHasTransparency) override + { + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_TextureFlags.SetHasTransparency(inHasTransparency); + } + + bool GetImageHasTransparency(CRegisteredString inSourcePath) const override + { + TImageMap::const_iterator theIter = m_ImageMap.find(inSourcePath); + if (theIter != m_ImageMap.end()) + return theIter->second.m_TextureFlags.HasTransparency(); + return false; + } + + void SetImageTransparencyToFalseIfNotSet(CRegisteredString inSourcePath) override + { + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(inSourcePath, SImageEntry())); + // If we did actually insert something + if (theImage.second) + theImage.first->second.m_TextureFlags.SetHasTransparency(false); + } + + void SetInvertImageUVCoords(CRegisteredString inImagePath, bool inShouldInvertCoords) override + { + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_TextureFlags.SetInvertUVCoords(inShouldInvertCoords); + } + + bool IsImageLoaded(CRegisteredString inSourcePath) override + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + return m_LoadedImageSet.find(inSourcePath) != m_LoadedImageSet.end(); + } + + bool AliasImagePath(CRegisteredString inSourcePath, CRegisteredString inAliasPath, + bool inIgnoreIfLoaded) override + { + if (inSourcePath.IsValid() == false || inAliasPath.IsValid() == false) + return false; + // If the image is loaded then we ignore this call in some cases. + if (inIgnoreIfLoaded && IsImageLoaded(inSourcePath)) + return false; + m_AliasImageMap.insert(eastl::make_pair(inSourcePath, inAliasPath)); + return true; + } + + void UnaliasImagePath(CRegisteredString inSourcePath) override + { + m_AliasImageMap.erase(inSourcePath); + } + + CRegisteredString GetImagePath(CRegisteredString inSourcePath) override + { + TAliasImageMap::iterator theAliasIter = m_AliasImageMap.find(inSourcePath); + if (theAliasIter != m_AliasImageMap.end()) + return theAliasIter->second; + return inSourcePath; + } + + CRegisteredString getImagePath(const QString &path) + { + TAliasImageMap::iterator theAliasIter + = m_AliasImageMap.find(m_StrTable->RegisterStr(qPrintable(path))); + if (theAliasIter != m_AliasImageMap.end()) + return theAliasIter->second; + return m_StrTable->RegisterStr(qPrintable(path)); + } + + static inline int wrapMod(int a, int base) + { + int ret = a % base; + if (ret < 0) + ret += base; + return ret; + } + + static inline void getWrappedCoords(int &sX, int &sY, int width, int height) + { + if (sY < 0) { + sX -= width >> 1; + sY = -sY; + } + if (sY >= height) { + sX += width >> 1; + sY = height - sY; + } + sX = wrapMod(sX, width); + sY = wrapMod(sY, height); + } + + template <typename V, typename C> + void iterateAll(const V &vv, C c) + { + for (const auto x : vv) + c(x); + } + + void loadTextureImage(SReloadableImageTextureData &data) + { + CRegisteredString imagePath = getImagePath(data.m_path); + TImageMap::iterator theIter = m_ImageMap.find(imagePath); + if ((theIter == m_ImageMap.end() || theIter->second.m_Loaded == false) + && imagePath.IsValid()) { + NVScopedReleasable<SLoadedTexture> theLoadedImage; + SImageTextureData textureData; + + doImageLoad(imagePath, theLoadedImage); + + if (theLoadedImage) { + textureData = LoadRenderImage(imagePath, *theLoadedImage, data.m_scanTransparency, + data.m_bsdfMipmap); + data.m_Texture = textureData.m_Texture; + data.m_TextureFlags = textureData.m_TextureFlags; + data.m_BSDFMipMap = textureData.m_BSDFMipMap; + data.m_loaded = true; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + } else { + // We want to make sure that bad path fails once and doesn't fail over and over + // again which could slow down the system quite a bit. + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(imagePath, SImageEntry())); + theImage.first->second.m_Loaded = true; + qCWarning(WARNING, "Failed to load image: %s", imagePath.c_str()); + theIter = theImage.first; + } + } else { + SImageEntry textureData = theIter->second; + if (textureData.m_Loaded) { + data.m_Texture = textureData.m_Texture; + data.m_TextureFlags = textureData.m_TextureFlags; + data.m_BSDFMipMap = textureData.m_BSDFMipMap; + data.m_loaded = true; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + } + } + } + + void unloadTextureImage(SReloadableImageTextureData &data) + { + CRegisteredString r = m_StrTable->RegisterStr(qPrintable(data.m_path)); + data.m_loaded = false; + data.m_Texture = nullptr; + data.m_BSDFMipMap = nullptr; + data.m_TextureFlags = {}; + iterateAll(data.m_callbacks, [](SImage *img){ img->m_Flags.SetDirty(true); }); + InvalidateBuffer(r); + } + + void loadSet(const QSet<QString> &imageSet) override + { + for (const auto &x : imageSet) { + if (!m_reloadableTextures.contains(x)) { + auto img = CreateReloadableImage(m_StrTable->RegisterStr(qPrintable(x)), false, + false); + img->m_initialized = false; + loadTextureImage(*m_reloadableTextures[x]); + } else if (!m_reloadableTextures[x]->m_loaded) { + loadTextureImage(*m_reloadableTextures[x]); + } + } + } + + void unloadSet(const QSet<QString> &imageSet) override + { + for (const auto &x : imageSet) { + if (m_reloadableTextures.contains(x)) { + if (m_reloadableTextures[x]->m_loaded) + unloadTextureImage(*m_reloadableTextures[x]); + } + } + } + + virtual ReloadableTexturePtr CreateReloadableImage(CRegisteredString inSourcePath, + bool inForceScanForTransparency, + bool inBsdfMipmaps) override + { + QString path = QString::fromLatin1(inSourcePath.c_str()); + const bool inserted = m_reloadableTextures.contains(path); + if (!inserted || (inserted && m_reloadableTextures[path]->m_initialized == false)) { + if (!inserted) + m_reloadableTextures.insert(path, ReloadableTexturePtr::create()); + m_reloadableTextures[path]->m_path = path; + m_reloadableTextures[path]->m_scanTransparency = inForceScanForTransparency; + m_reloadableTextures[path]->m_bsdfMipmap = inBsdfMipmaps; + m_reloadableTextures[path]->m_initialized = true; + + if (!m_reloadableResources) + loadTextureImage(*m_reloadableTextures[path]); + + CRegisteredString imagePath = getImagePath(path); + TImageMap::iterator theIter = m_ImageMap.find(imagePath); + if (theIter != m_ImageMap.end()) { + SImageEntry textureData = theIter->second; + if (textureData.m_Loaded) { + m_reloadableTextures[path]->m_Texture = textureData.m_Texture; + m_reloadableTextures[path]->m_TextureFlags = textureData.m_TextureFlags; + m_reloadableTextures[path]->m_BSDFMipMap = textureData.m_BSDFMipMap; + m_reloadableTextures[path]->m_loaded = true; + } + } + } + return m_reloadableTextures[path]; + } + + void doImageLoad(CRegisteredString inImagePath, + NVScopedReleasable<SLoadedTexture> &theLoadedImage) + { + SStackPerfTimer __perfTimer(m_PerfTimer, "Image Decompression"); + theLoadedImage = SLoadedTexture::Load( + inImagePath.c_str(), m_Context->GetFoundation(), *m_InputStreamFactory, + true, m_Context->GetRenderContextType()); + // Hackish solution to custom materials not finding their textures if they are used + // in sub-presentations. + if (!theLoadedImage) { + if (QDir(inImagePath.c_str()).isRelative()) { + QString searchPath = inImagePath.c_str(); + if (searchPath.startsWith(QLatin1String("./"))) + searchPath.prepend(QLatin1Char('.')); + int loops = 0; + while (!theLoadedImage && ++loops <= 3) { + theLoadedImage = SLoadedTexture::Load( + searchPath.toUtf8(), m_Context->GetFoundation(), + *m_InputStreamFactory, true, + m_Context->GetRenderContextType()); + searchPath.prepend(QLatin1String("../")); + } + } else { + // Some textures, for example environment maps for custom materials, + // have absolute path at this point. It points to the wrong place with + // the new project structure, so we need to split it up and construct + // the new absolute path here. + QString wholePath = inImagePath.c_str(); + QStringList splitPath = wholePath.split(QLatin1String("../")); + if (splitPath.size() > 1) { + QString searchPath = splitPath.at(0) + splitPath.at(1); + int loops = 0; + while (!theLoadedImage && ++loops <= 3) { + theLoadedImage = SLoadedTexture::Load( + searchPath.toUtf8(), m_Context->GetFoundation(), + *m_InputStreamFactory, true, + m_Context->GetRenderContextType()); + searchPath = splitPath.at(0); + for (int i = 0; i < loops; i++) + searchPath.append(QLatin1String("../")); + searchPath.append(splitPath.at(1)); + } + } + } + } + } + + void enableReloadableResources(bool enable) override + { + m_reloadableResources = enable; + } + + bool isReloadableResourcesEnabled() const override + { + return m_reloadableResources; + } + + SImageTextureData LoadRenderImage(CRegisteredString inImagePath, + SLoadedTexture &inLoadedImage, + bool inForceScanForTransparency, bool inBsdfMipmaps) override + { + SStackPerfTimer __perfTimer(m_PerfTimer, "Image Upload"); + { + Mutex::ScopedLock __mapLocker(m_LoadedImageSetMutex); + m_LoadedImageSet.insert(inImagePath); + } + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + bool wasInserted = theImage.second; + theImage.first->second.m_Loaded = true; + // inLoadedImage.EnsureMultiplerOfFour( m_Context->GetFoundation(), inImagePath.c_str() ); + + NVRenderTexture2D *theTexture = m_Context->CreateTexture2D(); + if (inLoadedImage.data) { + qt3ds::render::NVRenderTextureFormats::Enum destFormat = inLoadedImage.format; + if (inBsdfMipmaps) { + if (m_Context->GetRenderContextType() == render::NVRenderContextValues::GLES2) + destFormat = qt3ds::render::NVRenderTextureFormats::RGBA8; + else + destFormat = qt3ds::render::NVRenderTextureFormats::RGBA16F; + } + else { + theTexture->SetTextureData( + NVDataRef<QT3DSU8>((QT3DSU8 *)inLoadedImage.data, inLoadedImage.dataSizeInBytes), 0, + inLoadedImage.width, inLoadedImage.height, inLoadedImage.format, destFormat); + { + static int enable = qEnvironmentVariableIntValue("QT3DS_GENERATE_MIPMAPS"); + if (enable) { + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::LinearMipmapLinear); + theTexture->SetMagFilter(NVRenderTextureMagnifyingOp::Linear); + theTexture->GenerateMipmaps(); + } + } + } + + if (inBsdfMipmaps + && NVRenderTextureFormats::isUncompressedTextureFormat(inLoadedImage.format)) { + theTexture->SetMinFilter(NVRenderTextureMinifyingOp::LinearMipmapLinear); + Qt3DSRenderPrefilterTexture *theBSDFMipMap = theImage.first->second.m_BSDFMipMap; + if (theBSDFMipMap == NULL) { + theBSDFMipMap = Qt3DSRenderPrefilterTexture::Create( + m_Context, inLoadedImage.width, inLoadedImage.height, *theTexture, + destFormat, m_Context->GetFoundation()); + theImage.first->second.m_BSDFMipMap = theBSDFMipMap; + } + + if (theBSDFMipMap) { + theBSDFMipMap->Build(inLoadedImage.data, inLoadedImage.dataSizeInBytes, + inLoadedImage.format); + } + } + } else if (inLoadedImage.dds) { + theImage.first->second.m_Texture = theTexture; + bool supportsDXT = m_GPUSupportsDXT; + bool isDXT = NVRenderTextureFormats::isCompressedTextureFormat(inLoadedImage.format); + bool requiresDecompression = (supportsDXT == false && isDXT) || false; + // test code for DXT decompression + // if ( isDXT ) requiresDecompression = true; + if (requiresDecompression) { + qCWarning(WARNING, PERF_INFO, + "Image %s is DXT format which is unsupported by " + "the graphics subsystem, decompressing in CPU", + inImagePath.c_str()); + } + STextureData theDecompressedImage; + for (int idx = 0; idx < inLoadedImage.dds->numMipmaps; ++idx) { + if (inLoadedImage.dds->mipwidth[idx] && inLoadedImage.dds->mipheight[idx]) { + if (requiresDecompression == false) { + theTexture->SetTextureData( + toU8DataRef((char *)inLoadedImage.dds->data[idx], + (QT3DSU32)inLoadedImage.dds->size[idx]), + (QT3DSU8)idx, (QT3DSU32)inLoadedImage.dds->mipwidth[idx], + (QT3DSU32)inLoadedImage.dds->mipheight[idx], inLoadedImage.format); + } else { + theDecompressedImage = + inLoadedImage.DecompressDXTImage(idx, &theDecompressedImage); + + if (theDecompressedImage.data) { + theTexture->SetTextureData( + toU8DataRef((char *)theDecompressedImage.data, + (QT3DSU32)theDecompressedImage.dataSizeInBytes), + (QT3DSU8)idx, (QT3DSU32)inLoadedImage.dds->mipwidth[idx], + (QT3DSU32)inLoadedImage.dds->mipheight[idx], + theDecompressedImage.format); + } + } + } + } + if (theDecompressedImage.data) + inLoadedImage.ReleaseDecompressedTexture(theDecompressedImage); + } + if (wasInserted == true || inForceScanForTransparency) + theImage.first->second.m_TextureFlags.SetHasTransparency( + inLoadedImage.ScanForTransparency()); + theImage.first->second.m_Texture = theTexture; + return theImage.first->second; + } + + SImageTextureData LoadRenderImage(CRegisteredString inImagePath, + bool inForceScanForTransparency, bool inBsdfMipmaps) override + { + inImagePath = GetImagePath(inImagePath); + + if (!inImagePath.IsValid()) + return SImageEntry(); + + TImageMap::iterator theIter = m_ImageMap.find(inImagePath); + if (theIter == m_ImageMap.end() && inImagePath.IsValid()) { + NVScopedReleasable<SLoadedTexture> theLoadedImage; + + doImageLoad(inImagePath, theLoadedImage); + + if (theLoadedImage) { + return LoadRenderImage(inImagePath, *theLoadedImage, inForceScanForTransparency, + inBsdfMipmaps); + } else { + // We want to make sure that bad path fails once and doesn't fail over and over + // again + // which could slow down the system quite a bit. + pair<TImageMap::iterator, bool> theImage = + m_ImageMap.insert(make_pair(inImagePath, SImageEntry())); + theImage.first->second.m_Loaded = true; + qCWarning(WARNING, "Failed to load image: %s", inImagePath.c_str()); + theIter = theImage.first; + } + } + return theIter->second; + } + + qt3dsimp::SMultiLoadResult LoadPrimitive(const char8_t *inRelativePath) + { + CRegisteredString theName(m_StrTable->RegisterStr(inRelativePath)); + if (m_PrimitiveNames[0].m_PrimitiveName.IsValid() == false) { + IStringTable &strTable(m_Context->GetStringTable()); + m_PrimitiveNames[0].m_PrimitiveName = strTable.RegisterStr("#Rectangle"); + m_PrimitiveNames[0].m_FileName = strTable.RegisterStr("Rectangle.mesh"); + m_PrimitiveNames[1].m_PrimitiveName = strTable.RegisterStr("#Sphere"); + m_PrimitiveNames[1].m_FileName = strTable.RegisterStr("Sphere.mesh"); + m_PrimitiveNames[2].m_PrimitiveName = strTable.RegisterStr("#Cube"); + m_PrimitiveNames[2].m_FileName = strTable.RegisterStr("Cube.mesh"); + m_PrimitiveNames[3].m_PrimitiveName = strTable.RegisterStr("#Cone"); + m_PrimitiveNames[3].m_FileName = strTable.RegisterStr("Cone.mesh"); + m_PrimitiveNames[4].m_PrimitiveName = strTable.RegisterStr("#Cylinder"); + m_PrimitiveNames[4].m_FileName = strTable.RegisterStr("Cylinder.mesh"); + } + for (size_t idx = 0; idx < 5; ++idx) { + if (m_PrimitiveNames[idx].m_PrimitiveName == theName) { + CFileTools::CombineBaseAndRelative(GetPrimitivesDirectory(), + m_PrimitiveNames[idx].m_FileName, m_PathBuilder); + QT3DSU32 id = 1; + NVScopedRefCounted<IRefCountedInputStream> theInStream( + m_InputStreamFactory->GetStreamForFile(m_PathBuilder.c_str())); + if (theInStream) + return qt3dsimp::Mesh::LoadMulti(m_Context->GetAllocator(), *theInStream, id); + else { + qCCritical(INTERNAL_ERROR, "Unable to find mesh primitive %s", + m_PathBuilder.c_str()); + return qt3dsimp::SMultiLoadResult(); + } + } + } + return qt3dsimp::SMultiLoadResult(); + } + + virtual NVConstDataRef<QT3DSU8> CreatePackedPositionDataArray( + const qt3dsimp::SMultiLoadResult &inResult) + { + // we assume a position consists of 3 floats + QT3DSU32 vertexCount = inResult.m_Mesh->m_VertexBuffer.m_Data.size() + / inResult.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 dataSize = vertexCount * 3 * sizeof(QT3DSF32); + QT3DSF32 *posData = static_cast<QT3DSF32 *>( + QT3DS_ALLOC(m_Context->GetAllocator(), dataSize, + "SRenderMesh::CreatePackedPositionDataArray")); + QT3DSU8 *baseOffset = reinterpret_cast<QT3DSU8 *>(inResult.m_Mesh); + // copy position data + if (posData) { + QT3DSF32 *srcData = reinterpret_cast<QT3DSF32 *>( + inResult.m_Mesh->m_VertexBuffer.m_Data.begin(baseOffset)); + QT3DSU32 srcStride = inResult.m_Mesh->m_VertexBuffer.m_Stride / sizeof(QT3DSF32); + QT3DSF32 *dstData = posData; + QT3DSU32 dstStride = 3; + + for (QT3DSU32 i = 0; i < vertexCount; ++i) { + dstData[0] = srcData[0]; + dstData[1] = srcData[1]; + dstData[2] = srcData[2]; + + dstData += dstStride; + srcData += srcStride; + } + + return toConstDataRef(reinterpret_cast<const qt3ds::QT3DSU8 *>(posData), dataSize); + } + + return NVConstDataRef<QT3DSU8>(); + } + + SRenderMesh *createRenderMesh(const qt3dsimp::SMultiLoadResult &result) + { + SRenderMesh *theNewMesh = QT3DS_NEW(m_Context->GetAllocator(), SRenderMesh)( + qt3ds::render::NVRenderDrawMode::Triangles, + qt3ds::render::NVRenderWinding::CounterClockwise, result.m_Id, + m_Context->GetAllocator()); + QT3DSU8 *baseAddress = reinterpret_cast<QT3DSU8 *>(result.m_Mesh); + NVConstDataRef<QT3DSU8> theVBufData( + result.m_Mesh->m_VertexBuffer.m_Data.begin(baseAddress), + result.m_Mesh->m_VertexBuffer.m_Data.size()); + + NVRenderVertexBuffer *theVertexBuffer = m_Context->CreateVertexBuffer( + qt3ds::render::NVRenderBufferUsageType::Static, + result.m_Mesh->m_VertexBuffer.m_Data.m_Size, + result.m_Mesh->m_VertexBuffer.m_Stride, theVBufData); + + // create a tight packed position data VBO + // this should improve our depth pre pass rendering + NVRenderVertexBuffer *thePosVertexBuffer = nullptr; + NVConstDataRef<QT3DSU8> posData = CreatePackedPositionDataArray(result); + if (posData.size()) { + thePosVertexBuffer + = m_Context->CreateVertexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + posData.size(), 3 * sizeof(QT3DSF32), posData); + } + + NVRenderIndexBuffer *theIndexBuffer = nullptr; + if (result.m_Mesh->m_IndexBuffer.m_Data.size()) { + using qt3ds::render::NVRenderComponentTypes; + QT3DSU32 theIndexBufferSize = result.m_Mesh->m_IndexBuffer.m_Data.size(); + NVRenderComponentTypes::Enum bufComponentType = + result.m_Mesh->m_IndexBuffer.m_ComponentType; + QT3DSU32 sizeofType + = qt3ds::render::NVRenderComponentTypes::getSizeofType(bufComponentType); + + if (sizeofType == 2 || sizeofType == 4) { + // Ensure type is unsigned; else things will fail in rendering pipeline. + if (bufComponentType == NVRenderComponentTypes::QT3DSI16) + bufComponentType = NVRenderComponentTypes::QT3DSU16; + if (bufComponentType == NVRenderComponentTypes::QT3DSI32) + bufComponentType = NVRenderComponentTypes::QT3DSU32; + + NVConstDataRef<QT3DSU8> theIBufData( + result.m_Mesh->m_IndexBuffer.m_Data.begin(baseAddress), + result.m_Mesh->m_IndexBuffer.m_Data.size()); + theIndexBuffer = m_Context->CreateIndexBuffer( + qt3ds::render::NVRenderBufferUsageType::Static, bufComponentType, + theIndexBufferSize, theIBufData); + } else { + QT3DS_ASSERT(false); + } + } + nvvector<qt3ds::render::NVRenderVertexBufferEntry> &theEntryBuffer(m_EntryBuffer); + theEntryBuffer.resize(result.m_Mesh->m_VertexBuffer.m_Entries.size()); + for (QT3DSU32 entryIdx = 0, + entryEnd = result.m_Mesh->m_VertexBuffer.m_Entries.size(); + entryIdx < entryEnd; ++entryIdx) { + theEntryBuffer[entryIdx] + = result.m_Mesh->m_VertexBuffer.m_Entries.index(baseAddress, entryIdx) + .ToVertexBufferEntry(baseAddress); + } + // create our attribute layout + NVRenderAttribLayout *theAttribLayout + = m_Context->CreateAttributeLayout(theEntryBuffer); + // create our attribute layout for depth pass + qt3ds::render::NVRenderVertexBufferEntry theEntries[] = { + qt3ds::render::NVRenderVertexBufferEntry( + "attr_pos", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3), + }; + NVRenderAttribLayout *theAttribLayoutDepth + = m_Context->CreateAttributeLayout(toConstDataRef(theEntries, 1)); + + // create input assembler object + QT3DSU32 strides = result.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 offsets = 0; + NVRenderInputAssembler *theInputAssembler = m_Context->CreateInputAssembler( + theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer, + toConstDataRef(&strides, 1), toConstDataRef(&offsets, 1), + result.m_Mesh->m_DrawMode); + + // create depth input assembler object + QT3DSU32 posStrides = thePosVertexBuffer ? 3 * sizeof(QT3DSF32) : strides; + NVRenderInputAssembler *theInputAssemblerDepth = m_Context->CreateInputAssembler( + theAttribLayoutDepth, + toConstDataRef(thePosVertexBuffer ? &thePosVertexBuffer : &theVertexBuffer, 1), + theIndexBuffer, toConstDataRef(&posStrides, 1), toConstDataRef(&offsets, 1), + result.m_Mesh->m_DrawMode); + + NVRenderInputAssembler *theInputAssemblerPoints = m_Context->CreateInputAssembler( + theAttribLayoutDepth, + toConstDataRef(thePosVertexBuffer ? &thePosVertexBuffer : &theVertexBuffer, 1), + nullptr, toConstDataRef(&posStrides, 1), toConstDataRef(&offsets, 1), + NVRenderDrawMode::Points); + + if (!theInputAssembler || !theInputAssemblerDepth || !theInputAssemblerPoints) { + QT3DS_ASSERT(false); + return nullptr; + } + theNewMesh->m_Joints.resize(result.m_Mesh->m_Joints.size()); + for (QT3DSU32 jointIdx = 0, jointEnd = result.m_Mesh->m_Joints.size(); + jointIdx < jointEnd; ++jointIdx) { + const qt3dsimp::Joint &theImportJoint( + result.m_Mesh->m_Joints.index(baseAddress, jointIdx)); + SRenderJoint &theNewJoint(theNewMesh->m_Joints[jointIdx]); + theNewJoint.m_JointID = theImportJoint.m_JointID; + theNewJoint.m_ParentID = theImportJoint.m_ParentID; + memCopy(theNewJoint.m_invBindPose, theImportJoint.m_invBindPose, + 16 * sizeof(QT3DSF32)); + memCopy(theNewJoint.m_localToGlobalBoneSpace, + theImportJoint.m_localToGlobalBoneSpace, 16 * sizeof(QT3DSF32)); + } + + for (QT3DSU32 subsetIdx = 0, subsetEnd = result.m_Mesh->m_Subsets.size(); + subsetIdx < subsetEnd; ++subsetIdx) { + SRenderSubset theSubset(m_Context->GetAllocator()); + const qt3dsimp::MeshSubset &source( + result.m_Mesh->m_Subsets.index(baseAddress, subsetIdx)); + theSubset.m_Bounds = source.m_Bounds; + theSubset.m_Count = source.m_Count; + theSubset.m_Offset = source.m_Offset; + theSubset.m_Joints = theNewMesh->m_Joints; + theSubset.m_Name = m_StrTable->RegisterStr(source.m_Name.begin(baseAddress)); + theVertexBuffer->addRef(); + theSubset.m_VertexBuffer = theVertexBuffer; + if (thePosVertexBuffer) { + thePosVertexBuffer->addRef(); + theSubset.m_PosVertexBuffer = thePosVertexBuffer; + } + if (theIndexBuffer) { + theIndexBuffer->addRef(); + theSubset.m_IndexBuffer = theIndexBuffer; + } + theSubset.m_InputAssembler = theInputAssembler; + theSubset.m_InputAssemblerDepth = theInputAssemblerDepth; + theSubset.m_InputAssemblerPoints = theInputAssemblerPoints; + theSubset.m_PrimitiveType = result.m_Mesh->m_DrawMode; + theInputAssembler->addRef(); + theInputAssemblerDepth->addRef(); + theSubset.m_InputAssemblerPoints->addRef(); + theNewMesh->m_Subsets.push_back(theSubset); + } + // If we want to, we can break up models into sub-subsets. + // These are assumed to use the same material as the outer subset but have fewer triangles + // and should have a more exact bounding box. This sort of thing helps with using the + // frustum culling system but it is really done incorrectly. + // It should be done via some sort of oct-tree mechanism and so that the sub-subsets + // are spatially sorted and it should only be done upon save-to-binary with the + // results saved out to disk. As you can see, doing it properly requires some real + // engineering effort so it is somewhat unlikely it will ever happen. + // Or it could be done on import if someone really wants to change the mesh buffer + // format. Either way it isn't going to happen here and it isn't going to happen this way + // but this is a working example of using the technique. +#ifdef QT3DS_RENDER_GENERATE_SUB_SUBSETS + Option<qt3ds::render::NVRenderVertexBufferEntry> thePosAttrOpt + = theVertexBuffer->GetEntryByName("attr_pos"); + bool hasPosAttr = thePosAttrOpt.hasValue() + && thePosAttrOpt->m_ComponentType == qt3ds::render::NVRenderComponentTypes::QT3DSF32 + && thePosAttrOpt->m_NumComponents == 3; + + for (size_t subsetIdx = 0, subsetEnd = theNewMesh->m_Subsets.size(); + subsetIdx < subsetEnd; ++subsetIdx) { + SRenderSubset &theOuterSubset = theNewMesh->m_Subsets[subsetIdx]; + if (theOuterSubset.m_Count && theIndexBuffer + && theIndexBuffer->GetComponentType() + == qt3ds::render::NVRenderComponentTypes::QT3DSU16 + && theNewMesh->m_DrawMode == NVRenderDrawMode::Triangles && hasPosAttr) { + // Num tris in a sub subset. + QT3DSU32 theSubsetSize = 3334 * 3; // divisible by three. + size_t theNumSubSubsets = ((theOuterSubset.m_Count - 1) / theSubsetSize) + 1; + QT3DSU32 thePosAttrOffset = thePosAttrOpt->m_FirstItemOffset; + const QT3DSU8 *theVertData = result.m_Mesh->m_VertexBuffer.m_Data.begin(); + const QT3DSU8 *theIdxData = result.m_Mesh->m_IndexBuffer.m_Data.begin(); + QT3DSU32 theVertStride = result.m_Mesh->m_VertexBuffer.m_Stride; + QT3DSU32 theOffset = theOuterSubset.m_Offset; + QT3DSU32 theCount = theOuterSubset.m_Count; + for (size_t subSubsetIdx = 0, subSubsetEnd = theNumSubSubsets; + subSubsetIdx < subSubsetEnd; ++subSubsetIdx) { + SRenderSubsetBase theBase; + theBase.m_Offset = theOffset; + theBase.m_Count = NVMin(theSubsetSize, theCount); + theBase.m_Bounds.setEmpty(); + theCount -= theBase.m_Count; + theOffset += theBase.m_Count; + // Create new bounds. + // Offset is in item size, not bytes. + const QT3DSU16 *theSubsetIdxData + = reinterpret_cast<const QT3DSU16 *>(theIdxData + theBase.m_Offset * 2); + for (size_t theIdxIdx = 0, theIdxEnd = theBase.m_Count; + theIdxIdx < theIdxEnd; ++theIdxIdx) { + QT3DSU32 theVertOffset = theSubsetIdxData[theIdxIdx] * theVertStride; + theVertOffset += thePosAttrOffset; + QT3DSVec3 thePos = *( + reinterpret_cast<const QT3DSVec3 *>(theVertData + theVertOffset)); + theBase.m_Bounds.include(thePos); + } + theOuterSubset.m_SubSubsets.push_back(theBase); + } + } else { + SRenderSubsetBase theBase; + theBase.m_Bounds = theOuterSubset.m_Bounds; + theBase.m_Count = theOuterSubset.m_Count; + theBase.m_Offset = theOuterSubset.m_Offset; + theOuterSubset.m_SubSubsets.push_back(theBase); + } + } +#endif + if (posData.size()) { + m_Context->GetAllocator().deallocate( + static_cast<void *>(const_cast<qt3ds::QT3DSU8 *>(posData.begin()))); + } + + return theNewMesh; + } + + void loadCustomMesh(const QString &name, qt3dsimp::Mesh *mesh) override + { + if (!name.isEmpty() && mesh) { + CRegisteredString meshName = m_StrTable->RegisterStr(name); + pair<TMeshMap::iterator, bool> theMesh + = m_MeshMap.insert({ meshName, static_cast<SRenderMesh *>(nullptr) }); + // Only create the mesh if it doesn't yet exist + if (theMesh.second) { + qt3dsimp::SMultiLoadResult result; + result.m_Mesh = mesh; + theMesh.first->second = createRenderMesh(result); + } + } + } + + SRenderMesh *LoadMesh(CRegisteredString inMeshPath) override + { + if (inMeshPath.IsValid() == false) + return nullptr; + pair<TMeshMap::iterator, bool> theMesh = + m_MeshMap.insert(make_pair(inMeshPath, static_cast<SRenderMesh *>(nullptr))); + if (theMesh.second) { + // Check to see if this is primitive + qt3dsimp::SMultiLoadResult theResult = LoadPrimitive(inMeshPath); + + // Attempt a load from the filesystem if this mesh isn't a primitive. + if (!theResult.m_Mesh) { + m_PathBuilder = inMeshPath; + TStr::size_type pound = m_PathBuilder.rfind('#'); + QT3DSU32 id = 0; + if (pound != TStr::npos) { + id = QT3DSU32(atoi(m_PathBuilder.c_str() + pound + 1)); + m_PathBuilder.erase(m_PathBuilder.begin() + pound, m_PathBuilder.end()); + } + NVScopedRefCounted<IRefCountedInputStream> theStream( + m_InputStreamFactory->GetStreamForFile(m_PathBuilder.c_str())); + if (theStream) { + theResult = qt3dsimp::Mesh::LoadMulti( + m_Context->GetAllocator(), *theStream, id); + } + if (!theResult.m_Mesh) + qCWarning(WARNING, "Failed to load mesh: %s", m_PathBuilder.c_str()); + } + + if (theResult.m_Mesh) { + theMesh.first->second = createRenderMesh(theResult); + m_Context->GetAllocator().deallocate(theResult.m_Mesh); + } + } + return theMesh.first->second; + } + + SRenderMesh *CreateMesh(Qt3DSBCharPtr inSourcePath, QT3DSU8 *inVertData, QT3DSU32 inNumVerts, + QT3DSU32 inVertStride, QT3DSU32 *inIndexData, QT3DSU32 inIndexCount, + qt3ds::NVBounds3 inBounds) override + { + CRegisteredString sourcePath = m_StrTable->RegisterStr(inSourcePath); + + // eastl::pair<CRegisteredString, SRenderMesh*> thePair(sourcePath, (SRenderMesh*)NULL); + pair<TMeshMap::iterator, bool> theMesh; + // Make sure there isn't already a buffer entry for this mesh. + if (m_MeshMap.contains(sourcePath)) { + theMesh = make_pair<TMeshMap::iterator, bool>(m_MeshMap.find(sourcePath), true); + } else { + theMesh = m_MeshMap.insert(make_pair(sourcePath, (SRenderMesh *)NULL)); + } + + if (theMesh.second == true) { + SRenderMesh *theNewMesh = QT3DS_NEW(m_Context->GetAllocator(), SRenderMesh)( + qt3ds::render::NVRenderDrawMode::Triangles, + qt3ds::render::NVRenderWinding::CounterClockwise, 0, m_Context->GetAllocator()); + + // If we failed to create the RenderMesh, return a failure. + if (!theNewMesh) { + QT3DS_ASSERT(false); + return NULL; + } + + // Get rid of any old mesh that was sitting here and fill it with a new one. + // NOTE : This is assuming that the source of our mesh data doesn't do its own memory + // management and always returns new buffer pointers every time. + // Don't know for sure if that's what we'll get from our intended sources, but that's + // easily + // adjustable by looking for matching pointers in the Subsets. + if (theNewMesh && theMesh.first->second != NULL) { + delete theMesh.first->second; + theMesh.first->second = NULL; + } + + theMesh.first->second = theNewMesh; + QT3DSU32 vertDataSize = inNumVerts * inVertStride; + NVConstDataRef<QT3DSU8> theVBufData(inVertData, vertDataSize); + // NVConstDataRef<QT3DSU8> theVBufData( theResult.m_Mesh->m_VertexBuffer.m_Data.begin( + // baseAddress ) + // , theResult.m_Mesh->m_VertexBuffer.m_Data.size() ); + + NVRenderVertexBuffer *theVertexBuffer = + m_Context->CreateVertexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + vertDataSize, inVertStride, theVBufData); + NVRenderIndexBuffer *theIndexBuffer = NULL; + if (inIndexData != NULL && inIndexCount > 3) { + NVConstDataRef<QT3DSU8> theIBufData((QT3DSU8 *)inIndexData, inIndexCount * sizeof(QT3DSU32)); + theIndexBuffer = + m_Context->CreateIndexBuffer(qt3ds::render::NVRenderBufferUsageType::Static, + qt3ds::render::NVRenderComponentTypes::QT3DSU32, + inIndexCount * sizeof(QT3DSU32), theIBufData); + } + + // WARNING + // Making an assumption here about the contents of the stream + // PKC TODO : We may have to consider some other format. + qt3ds::render::NVRenderVertexBufferEntry theEntries[] = { + qt3ds::render::NVRenderVertexBufferEntry("attr_pos", + qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3), + qt3ds::render::NVRenderVertexBufferEntry( + "attr_uv", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 2, 12), + qt3ds::render::NVRenderVertexBufferEntry( + "attr_norm", qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3, 18), + }; + + // create our attribute layout + NVRenderAttribLayout *theAttribLayout = + m_Context->CreateAttributeLayout(toConstDataRef(theEntries, 3)); + /* + // create our attribute layout for depth pass + qt3ds::render::NVRenderVertexBufferEntry theEntriesDepth[] = { + qt3ds::render::NVRenderVertexBufferEntry( "attr_pos", + qt3ds::render::NVRenderComponentTypes::QT3DSF32, 3 ), + }; + NVRenderAttribLayout* theAttribLayoutDepth = m_Context->CreateAttributeLayout( + toConstDataRef( theEntriesDepth, 1 ) ); + */ + // create input assembler object + QT3DSU32 strides = inVertStride; + QT3DSU32 offsets = 0; + NVRenderInputAssembler *theInputAssembler = m_Context->CreateInputAssembler( + theAttribLayout, toConstDataRef(&theVertexBuffer, 1), theIndexBuffer, + toConstDataRef(&strides, 1), toConstDataRef(&offsets, 1), + qt3ds::render::NVRenderDrawMode::Triangles); + + if (!theInputAssembler) { + QT3DS_ASSERT(false); + return NULL; + } + + // Pull out just the mesh object name from the total path + eastl::string fullName(inSourcePath); + eastl::string subName(inSourcePath); + if (fullName.rfind("#") != eastl::string::npos) { + subName = fullName.substr(fullName.rfind("#"), eastl::string::npos); + } + + theNewMesh->m_Joints.clear(); + SRenderSubset theSubset(m_Context->GetAllocator()); + theSubset.m_Bounds = inBounds; + theSubset.m_Count = inIndexCount; + theSubset.m_Offset = 0; + theSubset.m_Joints = theNewMesh->m_Joints; + theSubset.m_Name = m_StrTable->RegisterStr(subName.c_str()); + theVertexBuffer->addRef(); + theSubset.m_VertexBuffer = theVertexBuffer; + theSubset.m_PosVertexBuffer = NULL; + if (theIndexBuffer) + theIndexBuffer->addRef(); + theSubset.m_IndexBuffer = theIndexBuffer; + theSubset.m_InputAssembler = theInputAssembler; + theSubset.m_InputAssemblerDepth = theInputAssembler; + theSubset.m_InputAssemblerPoints = theInputAssembler; + theSubset.m_PrimitiveType = qt3ds::render::NVRenderDrawMode::Triangles; + theSubset.m_InputAssembler->addRef(); + theSubset.m_InputAssemblerDepth->addRef(); + theSubset.m_InputAssemblerPoints->addRef(); + theNewMesh->m_Subsets.push_back(theSubset); + } + + return theMesh.first->second; + } + + void ReleaseMesh(SRenderMesh &inMesh) + { + for (QT3DSU32 subsetIdx = 0, subsetEnd = inMesh.m_Subsets.size(); subsetIdx < subsetEnd; + ++subsetIdx) { + inMesh.m_Subsets[subsetIdx].m_VertexBuffer->release(); + if (inMesh.m_Subsets[subsetIdx].m_PosVertexBuffer) // can be NULL + inMesh.m_Subsets[subsetIdx].m_PosVertexBuffer->release(); + if (inMesh.m_Subsets[subsetIdx].m_IndexBuffer) // can be NULL + inMesh.m_Subsets[subsetIdx].m_IndexBuffer->release(); + inMesh.m_Subsets[subsetIdx].m_InputAssembler->release(); + inMesh.m_Subsets[subsetIdx].m_InputAssemblerDepth->release(); + if (inMesh.m_Subsets[subsetIdx].m_InputAssemblerPoints) + inMesh.m_Subsets[subsetIdx].m_InputAssemblerPoints->release(); + } + NVDelete(m_Context->GetAllocator(), &inMesh); + } + void ReleaseTexture(SImageEntry &inEntry) + { + if (inEntry.m_Texture) + inEntry.m_Texture->release(); + if (inEntry.m_BSDFMipMap) + inEntry.m_BSDFMipMap->release(); + } + void Clear() override + { + m_reloadableTextures.clear(); + for (TMeshMap::iterator iter = m_MeshMap.begin(), end = m_MeshMap.end(); iter != end; + ++iter) { + SRenderMesh *theMesh = iter->second; + if (theMesh) + ReleaseMesh(*theMesh); + } + m_MeshMap.clear(); + for (TImageMap::iterator iter = m_ImageMap.begin(), end = m_ImageMap.end(); iter != end; + ++iter) { + SImageEntry &theEntry = iter->second; + ReleaseTexture(theEntry); + } + m_ImageMap.clear(); + m_AliasImageMap.clear(); + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + m_LoadedImageSet.clear(); + } + } + void InvalidateBuffer(CRegisteredString inSourcePath) override + { + { + TMeshMap::iterator iter = m_MeshMap.find(inSourcePath); + if (iter != m_MeshMap.end()) { + if (iter->second) + ReleaseMesh(*iter->second); + m_MeshMap.erase(iter); + return; + } + } + { + TImageMap::iterator iter = m_ImageMap.find(inSourcePath); + if (iter != m_ImageMap.end()) { + SImageEntry &theEntry = iter->second; + ReleaseTexture(theEntry); + m_ImageMap.erase(inSourcePath); + { + Mutex::ScopedLock __locker(m_LoadedImageSetMutex); + m_LoadedImageSet.erase(inSourcePath); + } + } + } + } + IStringTable &GetStringTable() override { return *m_StrTable; } +}; +} + +IBufferManager &IBufferManager::Create(NVRenderContext &inRenderContext, IStringTable &inStrTable, + IInputStreamFactory &inFactory, IPerfTimer &inPerfTimer) +{ + return *QT3DS_NEW(inRenderContext.GetAllocator(), SBufferManager)(inRenderContext, inStrTable, + inFactory, inPerfTimer); +} |