| /**************************************************************************** |
| ** |
| ** Copyright (C) 2008-2012 NVIDIA Corporation. |
| ** Copyright (C) 2019 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of Qt Quick 3D. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qssgrenderbuffermanager_p.h" |
| |
| #include <QtQuick3DRuntimeRender/private/qssgrenderprefiltertexture_p.h> |
| #include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h> |
| #include <QtQuick3DAssetImport/private/qssgmeshbvhbuilder_p.h> |
| |
| #include <QtQuick/QSGTexture> |
| |
| #include <QtCore/QDir> |
| #include <QtCore/QMutex> |
| #include <QtCore/QMutexLocker> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace { |
| |
| struct PrimitiveEntry |
| { |
| // Name of the primitive as it will be in the UIP file |
| const char *primitive; |
| // Name of the primitive file on the filesystem |
| const char *file; |
| }; |
| |
| const int nPrimitives = 5; |
| const PrimitiveEntry primitives[nPrimitives] = { |
| {"#Rectangle", "/Rectangle.mesh"}, |
| {"#Sphere","/Sphere.mesh"}, |
| {"#Cube","/Cube.mesh"}, |
| {"#Cone","/Cone.mesh"}, |
| {"#Cylinder","/Cylinder.mesh"}, |
| }; |
| |
| const char *primitivesDirectory = "res//primitives"; |
| |
| } |
| |
| |
| QSSGBufferManager::QSSGBufferManager(const QSSGRef<QSSGRenderContext> &ctx, |
| const QSSGRef<QSSGInputStreamFactory> &inInputStreamFactory, |
| QSSGPerfTimer *inTimer) |
| { |
| context = ctx; |
| inputStreamFactory = inInputStreamFactory; |
| perfTimer = inTimer; |
| gpuSupportsDXT = ctx->supportsDXTImages(); |
| } |
| |
| QSSGBufferManager::~QSSGBufferManager() |
| { clear(); } |
| |
| void QSSGBufferManager::setImageHasTransparency(const QString &inImagePath, bool inHasTransparency) |
| { |
| ImageMap::iterator theImage = imageMap.insert(inImagePath, QSSGRenderImageTextureData()); |
| theImage.value().m_textureFlags.setHasTransparency(inHasTransparency); |
| } |
| |
| bool QSSGBufferManager::getImageHasTransparency(const QString &inSourcePath) const |
| { |
| ImageMap::const_iterator theIter = imageMap.find(inSourcePath); |
| if (theIter != imageMap.end()) |
| return theIter.value().m_textureFlags.hasTransparency(); |
| return false; |
| } |
| |
| void QSSGBufferManager::setImageTransparencyToFalseIfNotSet(const QString &inSourcePath) |
| { |
| ImageMap::iterator theImage = imageMap.find(inSourcePath); |
| |
| // If we did actually insert something |
| if (theImage != imageMap.end()) |
| theImage.value().m_textureFlags.setHasTransparency(false); |
| } |
| |
| void QSSGBufferManager::setInvertImageUVCoords(const QString &inImagePath, bool inShouldInvertCoords) |
| { |
| ImageMap::iterator theImage = imageMap.find(inImagePath); |
| if (theImage != imageMap.end()) |
| theImage.value().m_textureFlags.setInvertUVCoords(inShouldInvertCoords); |
| } |
| |
| bool QSSGBufferManager::isImageLoaded(const QString &inSourcePath) |
| { |
| QMutexLocker locker(&loadedImageSetMutex); |
| return loadedImageSet.find(inSourcePath) != loadedImageSet.end(); |
| } |
| |
| bool QSSGBufferManager::aliasImagePath(const QString &inSourcePath, |
| const QString &inAliasPath, |
| bool inIgnoreIfLoaded) |
| { |
| if (inSourcePath.isEmpty() || inAliasPath.isEmpty()) |
| return false; |
| // If the image is loaded then we ignore this call in some cases. |
| if (inIgnoreIfLoaded && isImageLoaded(inSourcePath)) |
| return false; |
| aliasImageMap.insert(inSourcePath, inAliasPath); |
| return true; |
| } |
| |
| void QSSGBufferManager::unaliasImagePath(const QString &inSourcePath) |
| { |
| aliasImageMap.remove(inSourcePath); |
| } |
| |
| QString QSSGBufferManager::getImagePath(const QString &inSourcePath) const |
| { |
| const auto foundIt = aliasImageMap.constFind(inSourcePath); |
| return (foundIt != aliasImageMap.cend()) ? foundIt.value() : inSourcePath; |
| } |
| |
| namespace { |
| QSize sizeForMipLevel(int mipLevel, const QSize &baseLevelSize) |
| { |
| const int w = qMax(1, baseLevelSize.width() >> mipLevel); |
| const int h = qMax(1, baseLevelSize.height() >> mipLevel); |
| return QSize(w, h); |
| } |
| } |
| |
| QSSGRenderImageTextureData QSSGBufferManager::loadRenderImage(const QString &inImagePath, const QSSGRef<QSSGLoadedTexture> &inLoadedImage, bool inForceScanForTransparency, bool inBsdfMipmaps) |
| { |
| // SStackPerfTimer __perfTimer(perfTimer, "Image Upload"); |
| { |
| QMutexLocker mapLocker(&loadedImageSetMutex); |
| loadedImageSet.insert(inImagePath); |
| } |
| ImageMap::iterator theImage = imageMap.find(inImagePath); |
| bool wasInserted = theImage == imageMap.end(); |
| if (wasInserted) |
| theImage = imageMap.insert(inImagePath, QSSGRenderImageTextureData()); |
| |
| // inLoadedImage.EnsureMultiplerOfFour( context->GetFoundation(), inImagePath.c_str() ); |
| |
| QSSGRef<QSSGRenderTexture2D> theTexture = new QSSGRenderTexture2D(context); |
| if (inLoadedImage->data) { |
| QSSGRenderTextureFormat destFormat = inLoadedImage->format; |
| if (inBsdfMipmaps) { |
| if (inLoadedImage->format != QSSGRenderTextureFormat::RGBE8) { |
| if (context->renderContextType() == QSSGRenderContextType::GLES2) |
| destFormat = QSSGRenderTextureFormat::RGBA8; |
| else |
| destFormat = QSSGRenderTextureFormat::RGBA16F; |
| } |
| } else { |
| theTexture->setTextureData(QSSGByteView((quint8 *)inLoadedImage->data, inLoadedImage->dataSizeInBytes), |
| 0, |
| inLoadedImage->width, |
| inLoadedImage->height, |
| inLoadedImage->format, |
| destFormat); |
| } |
| |
| if (inBsdfMipmaps && inLoadedImage->format.isUncompressedTextureFormat()) { |
| theTexture->setMinFilter(QSSGRenderTextureMinifyingOp::LinearMipmapLinear); |
| QSSGRef<QSSGRenderPrefilterTexture> theBSDFMipMap = theImage.value().m_bsdfMipMap; |
| if (theBSDFMipMap == nullptr) { |
| theBSDFMipMap = QSSGRenderPrefilterTexture::create(context, inLoadedImage->width, inLoadedImage->height, theTexture, destFormat); |
| theImage.value().m_bsdfMipMap = theBSDFMipMap; |
| } |
| |
| if (theBSDFMipMap) { |
| theBSDFMipMap->build(inLoadedImage->data, inLoadedImage->dataSizeInBytes, inLoadedImage->format); |
| } |
| } |
| } else if (inLoadedImage->compressedData.isValid()) { |
| // Compressed Texture Image handling using QTextureFileData |
| for (int i = 0; i < inLoadedImage->compressedData.numLevels(); i++) { |
| QSize imageSize = sizeForMipLevel(i, inLoadedImage->compressedData.size()); |
| auto format = GLConversion::fromGLtoTextureFormat(inLoadedImage->compressedData.glInternalFormat()); |
| theTexture->setTextureData(QSSGByteView(reinterpret_cast<quint8 *>(inLoadedImage->compressedData.data().data() + inLoadedImage->compressedData.dataOffset(i)), inLoadedImage->compressedData.dataLength(i)), |
| i, imageSize.width(), imageSize.height(), format); |
| } |
| } |
| |
| |
| |
| /*else if (inLoadedImage->dds) { |
| theImage.first->second.m_Texture = theTexture; |
| bool supportsDXT = GPUSupportsDXT; |
| bool isDXT = QSSGRenderTextureFormat::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], |
| (quint32)inLoadedImage.dds->size[idx]), |
| (quint8)idx, (quint32)inLoadedImage.dds->mipwidth[idx], |
| (quint32)inLoadedImage.dds->mipheight[idx], inLoadedImage.format); |
| } else { |
| theDecompressedImage = |
| inLoadedImage.DecompressDXTImage(idx, &theDecompressedImage); |
| |
| if (theDecompressedImage.data) { |
| theTexture->SetTextureData( |
| toU8DataRef((char *)theDecompressedImage.data, |
| (quint32)theDecompressedImage.dataSizeInBytes), |
| (quint8)idx, (quint32)inLoadedImage.dds->mipwidth[idx], |
| (quint32)inLoadedImage.dds->mipheight[idx], |
| theDecompressedImage.format); |
| } |
| } |
| } |
| } |
| if (theDecompressedImage.data) |
| inLoadedImage.ReleaseDecompressedTexture(theDecompressedImage); |
| }*/ |
| if (wasInserted == true || inForceScanForTransparency) |
| theImage.value().m_textureFlags.setHasTransparency(inLoadedImage->scanForTransparency()); |
| theImage.value().m_texture = theTexture; |
| return theImage.value(); |
| } |
| |
| QSSGRenderImageTextureData QSSGBufferManager::loadRenderImage(const QString &inImagePath, const QSSGRenderTextureFormat &inFormat, bool inForceScanForTransparency, bool inBsdfMipmaps) |
| { |
| const QString realImagePath = getImagePath(inImagePath); |
| |
| if (Q_UNLIKELY(realImagePath.isNull())) |
| return QSSGRenderImageTextureData(); |
| |
| const auto foundIt = imageMap.constFind(realImagePath); |
| if (foundIt != imageMap.cend()) |
| return foundIt.value(); |
| |
| if (Q_LIKELY(!realImagePath.isNull())) { |
| QSSGRef<QSSGLoadedTexture> theLoadedImage; |
| { |
| // SStackPerfTimer __perfTimer(perfTimer, "Image Decompression"); |
| theLoadedImage = QSSGLoadedTexture::load(realImagePath, inFormat, *inputStreamFactory, true, context->renderContextType()); |
| // Hackish solution to custom materials not finding their textures if they are used |
| // in sub-presentations. Note: Runtime 1 is going to be removed in Qt 3D Studio 2.x, |
| // so this should be ok. |
| if (!theLoadedImage) { |
| if (QDir(realImagePath).isRelative()) { |
| QString searchPath = realImagePath; |
| if (searchPath.startsWith(QLatin1String("./"))) |
| searchPath.prepend(QLatin1String(".")); |
| int loops = 0; |
| while (!theLoadedImage && ++loops <= 3) { |
| theLoadedImage = QSSGLoadedTexture::load(searchPath, |
| inFormat, |
| *inputStreamFactory, |
| true, |
| context->renderContextType()); |
| 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. |
| const QString &wholePath = realImagePath; |
| 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 = QSSGLoadedTexture::load(searchPath, |
| inFormat, |
| *inputStreamFactory, |
| true, |
| context->renderContextType()); |
| searchPath = splitPath.at(0); |
| for (int i = 0; i < loops; i++) |
| searchPath.append(QLatin1String("../")); |
| searchPath.append(splitPath.at(1)); |
| } |
| } |
| } |
| } |
| } |
| |
| if (Q_LIKELY(theLoadedImage)) |
| return loadRenderImage(realImagePath, theLoadedImage, inForceScanForTransparency, inBsdfMipmaps); |
| |
| // 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. |
| imageMap.insert(realImagePath, QSSGRenderImageTextureData()); |
| qCWarning(WARNING, "Failed to load image: %s", qPrintable(realImagePath)); |
| } |
| |
| return QSSGRenderImageTextureData(); |
| } |
| |
| QSSGRenderImageTextureData QSSGBufferManager::loadRenderImage(QSGTexture *qsgTexture) |
| { |
| if (Q_UNLIKELY(!qsgTexture)) |
| return QSSGRenderImageTextureData(); |
| |
| auto theImage = qsgImageMap.find(qsgTexture); |
| |
| if (theImage == qsgImageMap.end()) { |
| theImage = qsgImageMap.insert(qsgTexture, QSSGRenderImageTextureData()); |
| QSSGRef<QSSGRenderTexture2D> theTexture = new QSSGRenderTexture2D(context, qsgTexture); |
| theImage.value().m_texture = theTexture; |
| QObject::connect(qsgTexture, &QObject::destroyed, [this, qsgTexture]() { |
| qsgImageMap.remove(qsgTexture); |
| }); |
| } else { |
| //TODO: make QSSGRenderTexture2D support updating handles instead of this hack |
| auto textureId = reinterpret_cast<QSSGRenderBackend::QSSGRenderBackendTextureObject>(quintptr(qsgTexture->textureId())); |
| if (theImage.value().m_texture->handle() != textureId) { |
| QSSGRef<QSSGRenderTexture2D> theTexture = new QSSGRenderTexture2D(context, qsgTexture); |
| theImage.value().m_texture = theTexture; |
| } |
| } |
| |
| return theImage.value(); |
| } |
| |
| QSSGMeshUtilities::MultiLoadResult QSSGBufferManager::loadPrimitive(const QString &inRelativePath) const |
| { |
| QByteArray theName = inRelativePath.toUtf8(); |
| for (size_t idx = 0; idx < nPrimitives; ++idx) { |
| if (primitives[idx].primitive == theName) { |
| QString pathBuilder = QString::fromLatin1(primitivesDirectory); |
| pathBuilder += QLatin1String(primitives[idx].file); |
| quint32 id = 1; |
| QSharedPointer<QIODevice> theInStream(inputStreamFactory->getStreamForFile(pathBuilder)); |
| if (theInStream) |
| return QSSGMeshUtilities::Mesh::loadMulti(*theInStream, id); |
| |
| qCCritical(INTERNAL_ERROR, "Unable to find mesh primitive %s", qPrintable(pathBuilder)); |
| return QSSGMeshUtilities::MultiLoadResult(); |
| } |
| } |
| return QSSGMeshUtilities::MultiLoadResult(); |
| } |
| |
| QVector<QVector3D> QSSGBufferManager::createPackedPositionDataArray( |
| const QSSGMeshUtilities::MultiLoadResult &inResult) const |
| { |
| // we assume a position consists of 3 floats |
| const auto mesh = inResult.m_mesh; |
| qint32 vertexCount = mesh->m_vertexBuffer.m_data.size() / mesh->m_vertexBuffer.m_stride; |
| QVector<QVector3D> positions(vertexCount); |
| quint8 *baseOffset = reinterpret_cast<quint8 *>(mesh); |
| |
| // copy position data |
| float *srcData = reinterpret_cast<float *>(mesh->m_vertexBuffer.m_data.begin(baseOffset)); |
| quint32 srcStride = mesh->m_vertexBuffer.m_stride / sizeof(float); |
| QVector3D *p = positions.data(); |
| |
| for (qint32 i = 0; i < vertexCount; ++i) { |
| p[i] = QVector3D(srcData[0], srcData[1], srcData[2]); |
| srcData += srcStride; |
| } |
| |
| return positions; |
| } |
| |
| QSSGRenderMesh *QSSGBufferManager::getMesh(const QSSGRenderMeshPath &inSourcePath) const |
| { |
| if (inSourcePath.isNull()) |
| return nullptr; |
| |
| const auto foundIt = meshMap.constFind(inSourcePath); |
| return (foundIt != meshMap.constEnd()) ? *foundIt : nullptr; |
| } |
| |
| QSSGRenderMesh *QSSGBufferManager::createRenderMesh( |
| const QSSGMeshUtilities::MultiLoadResult &result, const QSSGRenderMeshPath &inSourcePath) |
| { |
| QSSGRenderMesh *newMesh = new QSSGRenderMesh(result.m_mesh->m_drawMode, |
| result.m_mesh->m_winding, |
| result.m_id); |
| quint8 *baseAddress = reinterpret_cast<quint8 *>(result.m_mesh); |
| meshMap.insert(QSSGRenderMeshPath::create(inSourcePath.path), newMesh); |
| QSSGByteView vertexBufferData(result.m_mesh->m_vertexBuffer.m_data.begin(baseAddress), |
| result.m_mesh->m_vertexBuffer.m_data.size()); |
| |
| QSSGRef<QSSGRenderVertexBuffer> |
| vertexBuffer = new QSSGRenderVertexBuffer(context, QSSGRenderBufferUsageType::Static, |
| result.m_mesh->m_vertexBuffer.m_stride, |
| vertexBufferData); |
| |
| // create a tight packed position data VBO |
| // this should improve our depth pre pass rendering |
| QSSGRef<QSSGRenderVertexBuffer> posVertexBuffer; |
| QVector<QVector3D> posData = createPackedPositionDataArray(result); |
| if (posData.size()) |
| posVertexBuffer = new QSSGRenderVertexBuffer(context, QSSGRenderBufferUsageType::Static, |
| 3 * sizeof(float), |
| toByteView(posData)); |
| |
| QSSGRef<QSSGRenderIndexBuffer> indexBuffer; |
| if (result.m_mesh->m_indexBuffer.m_data.size()) { |
| QSSGRenderComponentType bufComponentType = result.m_mesh->m_indexBuffer.m_componentType; |
| quint32 sizeofType = getSizeOfType(bufComponentType); |
| |
| if (sizeofType == 2 || sizeofType == 4) { |
| // Ensure type is unsigned; else things will fail in rendering pipeline. |
| if (bufComponentType == QSSGRenderComponentType::Integer16) |
| bufComponentType = QSSGRenderComponentType::UnsignedInteger16; |
| if (bufComponentType == QSSGRenderComponentType::Integer32) |
| bufComponentType = QSSGRenderComponentType::UnsignedInteger32; |
| |
| QSSGByteView indexBufferData(result.m_mesh->m_indexBuffer.m_data.begin(baseAddress), |
| result.m_mesh->m_indexBuffer.m_data.size()); |
| indexBuffer = new QSSGRenderIndexBuffer(context, QSSGRenderBufferUsageType::Static, |
| bufComponentType, |
| indexBufferData); |
| } else { |
| Q_ASSERT(false); |
| } |
| } |
| const auto &entries = result.m_mesh->m_vertexBuffer.m_entries; |
| entryBuffer.resize(entries.size()); |
| for (quint32 entryIdx = 0, entryEnd = entries.size(); entryIdx < entryEnd; ++entryIdx) { |
| entryBuffer[entryIdx] = entries.index(baseAddress, entryIdx).toVertexBufferEntry(baseAddress); |
| |
| newMesh->inputLayoutInputNames.append(QByteArray(entryBuffer[entryIdx].m_name)); |
| } |
| |
| // create our attribute layout |
| auto attribLayout = context->createAttributeLayout(toDataView(entryBuffer.constData(), entryBuffer.count())); |
| // create our attribute layout for depth pass |
| QSSGRenderVertexBufferEntry vertBufferEntries[] = { |
| QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3), |
| }; |
| auto attribLayoutDepth = context->createAttributeLayout(toDataView(vertBufferEntries, 1)); |
| |
| // create input assembler object |
| quint32 strides = result.m_mesh->m_vertexBuffer.m_stride; |
| quint32 offsets = 0; |
| auto inputAssembler = context->createInputAssembler(attribLayout, |
| toDataView(&vertexBuffer, 1), |
| indexBuffer, |
| toDataView(&strides, 1), |
| toDataView(&offsets, 1), |
| result.m_mesh->m_drawMode); |
| |
| // create depth input assembler object |
| quint32 posStrides = (posVertexBuffer) ? 3 * sizeof(float) : strides; |
| auto inputAssemblerDepth = context->createInputAssembler( |
| attribLayoutDepth, |
| toDataView((posVertexBuffer) ? &posVertexBuffer : &vertexBuffer, 1), |
| indexBuffer, toDataView(&posStrides, 1), toDataView(&offsets, 1), |
| result.m_mesh->m_drawMode); |
| |
| auto inputAssemblerPoints = context->createInputAssembler( |
| attribLayoutDepth, |
| toDataView((posVertexBuffer) ? &posVertexBuffer : &vertexBuffer, 1), |
| nullptr, toDataView(&posStrides, 1), toDataView(&offsets, 1), |
| QSSGRenderDrawMode::Points); |
| |
| if (!inputAssembler || !inputAssemblerDepth || !inputAssemblerPoints) { |
| Q_ASSERT(false); |
| return nullptr; |
| } |
| newMesh->joints.resize(result.m_mesh->m_joints.size()); |
| for (quint32 jointIdx = 0, jointEnd = result.m_mesh->m_joints.size(); jointIdx < jointEnd; ++jointIdx) { |
| const QSSGMeshUtilities::Joint &importJoint(result.m_mesh->m_joints.index(baseAddress, jointIdx)); |
| QSSGRenderJoint &newJoint(newMesh->joints[jointIdx]); |
| newJoint.jointID = importJoint.m_jointID; |
| newJoint.parentID = importJoint.m_parentID; |
| ::memcpy(newJoint.invBindPose, importJoint.m_invBindPose, 16 * sizeof(float)); |
| ::memcpy(newJoint.localToGlobalBoneSpace, importJoint.m_localToGlobalBoneSpace, 16 * sizeof(float)); |
| } |
| |
| for (quint32 subsetIdx = 0, subsetEnd = result.m_mesh->m_subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) { |
| QSSGRenderSubset subset; |
| const QSSGMeshUtilities::MeshSubset &source(result.m_mesh->m_subsets.index(baseAddress, subsetIdx)); |
| subset.bounds = source.m_bounds; |
| subset.bvhRoot = nullptr; |
| subset.count = source.m_count; |
| subset.offset = source.m_offset; |
| subset.joints = newMesh->joints; |
| subset.name = QString::fromUtf16(reinterpret_cast<const char16_t *>(source.m_name.begin(baseAddress))); |
| subset.vertexBuffer = vertexBuffer; |
| if (posVertexBuffer) |
| subset.posVertexBuffer = posVertexBuffer; |
| if (indexBuffer) |
| subset.indexBuffer = indexBuffer; |
| subset.inputAssembler = inputAssembler; |
| subset.inputAssemblerDepth = inputAssemblerDepth; |
| subset.inputAssemblerPoints = inputAssemblerPoints; |
| subset.primitiveType = result.m_mesh->m_drawMode; |
| newMesh->subsets.push_back(subset); |
| } |
| // If we want to, we can an in a quite stupid way break up modes into sub-subsets. |
| // These are assumed to use the same material as the outer subset but have fewer tris |
| // 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 it so that the sub-subsets 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 QSSG_RENDER_GENERATE_SUB_SUBSETS |
| QSSGOption<QSSGRenderVertexBufferEntry> thePosAttrOpt = theVertexBuffer->getEntryByName("attr_pos"); |
| bool hasPosAttr = thePosAttrOpt.hasValue() && thePosAttrOpt->m_componentType == QSSGRenderComponentTypes::Float32 |
| && thePosAttrOpt->m_numComponents == 3; |
| |
| for (size_t subsetIdx = 0, subsetEnd = theNewMesh->subsets.size(); subsetIdx < subsetEnd; ++subsetIdx) { |
| QSSGRenderSubset &theOuterSubset = theNewMesh->subsets[subsetIdx]; |
| if (theOuterSubset.count && theIndexBuffer |
| && theIndexBuffer->getComponentType() == QSSGRenderComponentTypes::UnsignedInteger16 |
| && theNewMesh->drawMode == QSSGRenderDrawMode::Triangles && hasPosAttr) { |
| // Num tris in a sub subset. |
| quint32 theSubsetSize = 3334 * 3; // divisible by three. |
| size_t theNumSubSubsets = ((theOuterSubset.count - 1) / theSubsetSize) + 1; |
| quint32 thePosAttrOffset = thePosAttrOpt->m_firstItemOffset; |
| const quint8 *theVertData = theResult.m_mesh->m_vertexBuffer.m_data.begin(); |
| const quint8 *theIdxData = theResult.m_mesh->m_indexBuffer.m_data.begin(); |
| quint32 theVertStride = theResult.m_mesh->m_vertexBuffer.m_stride; |
| quint32 theOffset = theOuterSubset.offset; |
| quint32 theCount = theOuterSubset.count; |
| for (size_t subSubsetIdx = 0, subSubsetEnd = theNumSubSubsets; subSubsetIdx < subSubsetEnd; ++subSubsetIdx) { |
| QSSGRenderSubsetBase theBase; |
| theBase.offset = theOffset; |
| theBase.count = NVMin(theSubsetSize, theCount); |
| theBase.bounds.setEmpty(); |
| theCount -= theBase.count; |
| theOffset += theBase.count; |
| // Create new bounds. |
| // Offset is in item size, not bytes. |
| const quint16 *theSubsetIdxData = reinterpret_cast<const quint16 *>(theIdxData + theBase.m_Offset * 2); |
| for (size_t theIdxIdx = 0, theIdxEnd = theBase.m_Count; theIdxIdx < theIdxEnd; ++theIdxIdx) { |
| quint32 theVertOffset = theSubsetIdxData[theIdxIdx] * theVertStride; |
| theVertOffset += thePosAttrOffset; |
| QVector3D thePos = *(reinterpret_cast<const QVector3D *>(theVertData + theVertOffset)); |
| theBase.bounds.include(thePos); |
| } |
| theOuterSubset.subSubsets.push_back(theBase); |
| } |
| } else { |
| QSSGRenderSubsetBase theBase; |
| theBase.bounds = theOuterSubset.bounds; |
| theBase.count = theOuterSubset.count; |
| theBase.offset = theOuterSubset.offset; |
| theOuterSubset.subSubsets.push_back(theBase); |
| } |
| } |
| #endif |
| return newMesh; |
| } |
| |
| QSSGRenderMesh *QSSGBufferManager::loadMesh(const QSSGRenderMeshPath &inMeshPath) |
| { |
| if (inMeshPath.isNull()) |
| return nullptr; |
| |
| // check if it is already loaded |
| MeshMap::iterator meshItr = meshMap.find(inMeshPath); |
| if (meshItr != meshMap.end()) |
| return meshItr.value(); |
| |
| // loading new mesh |
| QSSGMeshUtilities::MultiLoadResult result = loadMeshData(inMeshPath); |
| |
| if (result.m_mesh == nullptr) { |
| qCWarning(WARNING, "Failed to load mesh: %s", qPrintable(inMeshPath.path)); |
| return nullptr; |
| } |
| |
| auto ret = createRenderMesh(result, inMeshPath); |
| ::free(result.m_mesh); |
| return ret; |
| } |
| |
| QSSGRenderMesh *QSSGBufferManager::loadCustomMesh(const QSSGRenderMeshPath &inSourcePath, |
| QSSGMeshUtilities::Mesh *mesh, bool update) |
| { |
| if (!inSourcePath.isNull() && mesh) { |
| MeshMap::iterator meshItr = meshMap.find(inSourcePath); |
| // Only create the mesh if it doesn't yet exist or update is true |
| if (meshItr == meshMap.end() || update) { |
| if (meshItr != meshMap.end()) { |
| releaseMesh(*meshItr.value()); |
| meshMap.erase(meshItr); |
| } |
| QSSGMeshUtilities::MultiLoadResult result; |
| result.m_mesh = mesh; |
| auto ret = createRenderMesh(result, inSourcePath); |
| return ret; |
| } |
| } |
| return nullptr; |
| } |
| |
| QSSGMeshBVH *QSSGBufferManager::loadMeshBVH(const QSSGRenderMeshPath &inSourcePath) |
| { |
| // loading new mesh |
| QSSGMeshUtilities::MultiLoadResult result = loadMeshData(inSourcePath); |
| |
| if (result.m_mesh == nullptr) { |
| qCWarning(WARNING, "Failed to load mesh: %s", qPrintable(inSourcePath.path)); |
| return nullptr; |
| } |
| |
| // Build BVH for Mesh |
| QSSGMeshBVHBuilder meshBVHBuilder(result.m_mesh); |
| auto bvh = meshBVHBuilder.buildTree(); |
| |
| ::free(result.m_mesh); |
| return bvh; |
| } |
| |
| QSSGMeshUtilities::MultiLoadResult QSSGBufferManager::loadMeshData(const QSSGRenderMeshPath &inMeshPath) const |
| { |
| // loading new mesh |
| QSSGMeshUtilities::MultiLoadResult result; |
| |
| // check to see if this is a primitive mesh |
| if (inMeshPath.path.startsWith('#')) |
| result = loadPrimitive(inMeshPath.path); |
| |
| // Attempt a load from the filesystem if this mesh isn't a primitive. |
| if (result.m_mesh == nullptr) { |
| QString pathBuilder = inMeshPath.path; |
| int poundIndex = pathBuilder.lastIndexOf('#'); |
| int id = 0; |
| if (poundIndex != -1) { |
| id = pathBuilder.midRef(poundIndex + 1).toInt(); |
| pathBuilder = pathBuilder.left(poundIndex); |
| } |
| if (!pathBuilder.isEmpty()) { |
| QSharedPointer<QIODevice> ioStream(inputStreamFactory->getStreamForFile(pathBuilder)); |
| if (ioStream) |
| result = QSSGMeshUtilities::Mesh::loadMulti(*ioStream, id); |
| } |
| } |
| return result; |
| } |
| |
| QSSGRenderMesh *QSSGBufferManager::createMesh(const QString &inSourcePath, quint8 *inVertData, quint32 inNumVerts, quint32 inVertStride, quint32 *inIndexData, quint32 inIndexCount, QSSGBounds3 inBounds) |
| { |
| QString sourcePath = inSourcePath; |
| |
| // QPair<QString, SRenderMesh*> thePair(sourcePath, (SRenderMesh*)nullptr); |
| // Make sure there isn't already a buffer entry for this mesh. |
| const auto meshPath = QSSGRenderMeshPath::create(sourcePath); |
| auto it = meshMap.find(meshPath); |
| const auto end = meshMap.end(); |
| |
| QPair<MeshMap::iterator, bool> theMesh; |
| if (it != end) |
| theMesh = { it, true }; |
| else |
| theMesh = { meshMap.insert(meshPath, nullptr), false }; |
| |
| if (theMesh.second == true) { |
| QSSGRenderMesh *theNewMesh = new QSSGRenderMesh(QSSGRenderDrawMode::Triangles, QSSGRenderWinding::CounterClockwise, 0); |
| |
| // If we failed to create the RenderMesh, return a failure. |
| if (!theNewMesh) { |
| Q_ASSERT(false); |
| return nullptr; |
| } |
| |
| // 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.value() != nullptr) { |
| delete theMesh.first.value(); |
| } |
| |
| theMesh.first.value() = theNewMesh; |
| quint32 vertDataSize = inNumVerts * inVertStride; |
| Q_ASSERT(vertDataSize <= INT32_MAX); // TODO: |
| QSSGByteView theVBufData(inVertData, qint32(vertDataSize)); |
| // QSSGConstDataRef<quint8> theVBufData( theResult.Mesh->VertexBuffer.Data.begin( |
| // baseAddress ) |
| // , theResult.Mesh->VertexBuffer.Data.size() ); |
| |
| QSSGRef<QSSGRenderVertexBuffer> theVertexBuffer = new QSSGRenderVertexBuffer(context, QSSGRenderBufferUsageType::Static, |
| inVertStride, |
| theVBufData); |
| QSSGRef<QSSGRenderIndexBuffer> theIndexBuffer = nullptr; |
| if (inIndexData != nullptr && inIndexCount > 3) { |
| const quint32 inSize = inIndexCount * sizeof(quint32); |
| Q_ASSERT(inSize <= INT32_MAX); |
| Q_ASSERT(*inIndexData <= INT8_MAX); |
| QSSGByteView theIBufData(reinterpret_cast<quint8 *>(inIndexData), qint32(inSize)); |
| theIndexBuffer = new QSSGRenderIndexBuffer(context, QSSGRenderBufferUsageType::Static, |
| QSSGRenderComponentType::UnsignedInteger32, |
| theIBufData); |
| } |
| |
| // WARNING |
| // Making an assumption here about the contents of the stream |
| // PKC TODO : We may have to consider some other format. |
| QSSGRenderVertexBufferEntry theEntries[] = { |
| QSSGRenderVertexBufferEntry("attr_pos", QSSGRenderComponentType::Float32, 3), |
| QSSGRenderVertexBufferEntry("attr_uv", QSSGRenderComponentType::Float32, 2, 12), |
| QSSGRenderVertexBufferEntry("attr_norm", QSSGRenderComponentType::Float32, 3, 18), |
| }; |
| |
| // create our attribute layout |
| QSSGRef<QSSGRenderAttribLayout> theAttribLayout = context->createAttributeLayout(toDataView(theEntries, 3)); |
| /* |
| // create our attribute layout for depth pass |
| QSSGRenderVertexBufferEntry theEntriesDepth[] = { |
| QSSGRenderVertexBufferEntry( "attr_pos", |
| QSSGRenderComponentTypes::float, 3 ), |
| }; |
| QSSGRenderAttribLayout* theAttribLayoutDepth = context->CreateAttributeLayout( |
| toConstDataRef( theEntriesDepth, 1 ) ); |
| */ |
| // create input assembler object |
| quint32 strides = inVertStride; |
| quint32 offsets = 0; |
| QSSGRef<QSSGRenderInputAssembler> theInputAssembler = context->createInputAssembler(theAttribLayout, |
| toDataView(&theVertexBuffer, 1), |
| theIndexBuffer, |
| toDataView(&strides, 1), |
| toDataView(&offsets, 1), |
| QSSGRenderDrawMode::Triangles); |
| |
| if (!theInputAssembler) { |
| Q_ASSERT(false); |
| return nullptr; |
| } |
| |
| // Pull out just the mesh object name from the total path |
| const QString &fullName = inSourcePath; |
| QString subName(inSourcePath); |
| |
| int indexOfSub = fullName.lastIndexOf('#'); |
| if (indexOfSub != -1) { |
| subName = fullName.right(indexOfSub + 1); |
| } |
| |
| theNewMesh->joints.clear(); |
| QSSGRenderSubset theSubset; |
| theSubset.bounds = inBounds; |
| theSubset.count = inIndexCount; |
| theSubset.offset = 0; |
| theSubset.joints = theNewMesh->joints; |
| theSubset.name = subName; |
| theSubset.vertexBuffer = theVertexBuffer; |
| theSubset.posVertexBuffer = nullptr; |
| theSubset.indexBuffer = theIndexBuffer; |
| theSubset.inputAssembler = theInputAssembler; |
| theSubset.inputAssemblerDepth = theInputAssembler; |
| theSubset.inputAssemblerPoints = theInputAssembler; |
| theSubset.primitiveType = QSSGRenderDrawMode::Triangles; |
| theNewMesh->subsets.push_back(theSubset); |
| } |
| |
| return theMesh.first.value(); |
| } |
| |
| void QSSGBufferManager::releaseMesh(QSSGRenderMesh &inMesh) |
| { |
| delete &inMesh; |
| } |
| |
| void QSSGBufferManager::releaseTexture(QSSGRenderImageTextureData &inEntry) |
| { |
| // TODO: |
| Q_UNUSED(inEntry); |
| // if (inEntry.Texture) |
| // inEntry.Texture->release(); |
| } |
| |
| void QSSGBufferManager::clear() |
| { |
| for (auto iter = meshMap.begin(), end = meshMap.end(); iter != end; ++iter) { |
| QSSGRenderMesh *theMesh = iter.value(); |
| if (theMesh) |
| QSSGBufferManager::releaseMesh(*theMesh); |
| } |
| meshMap.clear(); |
| for (auto iter = imageMap.begin(), end = imageMap.end(); iter != end; ++iter) { |
| QSSGRenderImageTextureData &theEntry = iter.value(); |
| QSSGBufferManager::releaseTexture(theEntry); |
| } |
| imageMap.clear(); |
| aliasImageMap.clear(); |
| { |
| QMutexLocker locker(&loadedImageSetMutex); |
| loadedImageSet.clear(); |
| } |
| } |
| |
| void QSSGBufferManager::invalidateBuffer(const QString &inSourcePath) |
| { |
| { |
| // TODO: |
| const auto meshPath = QSSGRenderMeshPath::create(inSourcePath); |
| const auto iter = meshMap.constFind(meshPath); |
| if (iter != meshMap.cend()) { |
| if (iter.value()) |
| releaseMesh(*iter.value()); |
| meshMap.erase(iter); |
| return; |
| } |
| } |
| { |
| ImageMap::iterator iter = imageMap.find(inSourcePath); |
| if (iter != imageMap.end()) { |
| QSSGRenderImageTextureData &theEntry = iter.value(); |
| releaseTexture(theEntry); |
| imageMap.remove(inSourcePath); |
| { |
| QMutexLocker locker(&loadedImageSetMutex); |
| loadedImageSet.remove(inSourcePath); |
| } |
| } |
| } |
| } |
| |
| QT_END_NAMESPACE |