diff --git a/src/qmlSfmData/CameraLocatorEntity.cpp b/src/qmlSfmData/CameraLocatorEntity.cpp index 9666231b..149dc555 100644 --- a/src/qmlSfmData/CameraLocatorEntity.cpp +++ b/src/qmlSfmData/CameraLocatorEntity.cpp @@ -58,23 +58,23 @@ CameraLocatorEntity::CameraLocatorEntity(const aliceVision::IndexT& viewId, tr.y() = sin(vfov / 2.0f); tr.z() = cos(vfov / 2.0f); - const float vslice = vfov / static_cast(subdiv); - const float hslice = hfov / static_cast(subdiv); + const float vslice = vfov / static_cast(subdiv); + const float hslice = hfov / static_cast(subdiv); Eigen::Vector3d vZ = - Eigen::Vector3d::UnitZ() * radius; for (int vid = 0; vid < subdiv; vid++) { - float vangle1 = - vfov / 2.0f + (vid) * vslice; - float vangle2 = - vfov / 2.0f + (vid + 1) * vslice; + float vangle1 = - vfov / 2.0f + static_cast(vid) * vslice; + float vangle2 = - vfov / 2.0f + static_cast(vid + 1) * vslice; Eigen::AngleAxis Rv1(vangle1, Eigen::Vector3d::UnitX()); Eigen::AngleAxis Rv2(vangle2, Eigen::Vector3d::UnitX()); for (int hid = 0; hid < subdiv; hid++) { - float hangle1 = - hfov / 2.0f + (hid) * hslice; - float hangle2 = - hfov / 2.0f + (hid + 1) * hslice; + float hangle1 = - hfov / 2.0f + static_cast(hid) * hslice; + float hangle2 = - hfov / 2.0f + static_cast(hid + 1) * hslice; Eigen::AngleAxis Rh1(hangle1, Eigen::Vector3d::UnitY()); Eigen::AngleAxis Rh2(hangle2, Eigen::Vector3d::UnitY()); diff --git a/src/qtAliceVision/AsyncFetcher.cpp b/src/qtAliceVision/AsyncFetcher.cpp index 86712232..269354ab 100644 --- a/src/qtAliceVision/AsyncFetcher.cpp +++ b/src/qtAliceVision/AsyncFetcher.cpp @@ -3,11 +3,10 @@ #include #include +#include #include #include -using namespace aliceVision; - namespace qtAliceVision { namespace imgserve { @@ -32,14 +31,18 @@ void AsyncFetcher::setSequence(const std::vector& paths) _sequence = paths; _currentIndex = 0; - for (unsigned idx = 0; idx < _sequence.size(); idx++) + for (std::size_t idx = 0; idx < _sequence.size(); ++idx) { - _pathToSeqId[_sequence[idx]] = idx; + _pathToSeqId[_sequence[idx]] = static_cast(idx); } } void AsyncFetcher::setResizeRatio(double ratio) { + if (ratio <= 0.0) + { + return; + } QMutexLocker locker(&_mutexResizeRatio); _resizeRatio = ratio; } @@ -55,7 +58,7 @@ void AsyncFetcher::setPrefetching(bool prefetch) } } -bool AsyncFetcher::getPrefetching() +bool AsyncFetcher::getPrefetching() const { return _isPrefetching; } @@ -93,7 +96,7 @@ void AsyncFetcher::run() continue; } - if (_sequence.size() == 0) + if (_sequence.empty()) { continue; } @@ -109,7 +112,7 @@ void AsyncFetcher::run() ratio = _resizeRatio; } - _cache->get(lpath, static_cast(_currentIndex), ratio, false); + _cache->get(lpath, static_cast(_currentIndex), ratio, false); } if (_isPrefetching) @@ -135,7 +138,6 @@ void AsyncFetcher::run() } } - _requestSynchronous = false; _isAsynchronous = false; } @@ -149,9 +151,9 @@ void AsyncFetcher::updateCacheMemory(std::size_t maxMemory) } } -std::size_t AsyncFetcher::getCacheMemory() +std::size_t AsyncFetcher::getCacheMemory() const { - return (_cache)?_cache->getMaxMemory():0; + return (_cache) ? _cache->getMaxMemory() : 0; } std::size_t AsyncFetcher::getCacheSize() const @@ -178,35 +180,39 @@ QVariantList AsyncFetcher::getCachedFrames() const size_t size = _sequence.size(); + double resizeRatio; { - // Build cached frames intervals - for (std::size_t i = 0; i < size; ++i) - { - const int frame = static_cast(i); + QMutexLocker locker(&_mutexResizeRatio); + resizeRatio = _resizeRatio; + } - // Check if current frame is in cache - if (_cache->contains(_sequence[i], _resizeRatio)) + // Build cached frames intervals + for (std::size_t i = 0; i < size; ++i) + { + const int frame = static_cast(i); + + // Check if current frame is in cache + if (_cache->contains(_sequence[i], resizeRatio)) + { + // Either grow currently open region or create a new region + if (regionOpen) { - // Either grow currently open region or create a new region - if (regionOpen) - { - region.second = frame; - } - else - { - region.first = frame; - region.second = frame; - regionOpen = true; - } + region.second = frame; } else { - // Close currently open region - if (regionOpen) - { - intervals.append(QPoint(region.first, region.second)); - regionOpen = false; - } + region.first = frame; + region.second = frame; + regionOpen = true; + } + } + else + { + // Close currently open region + if (regionOpen) + { + intervals.append(QPoint(region.first, region.second)); + regionOpen = false; } } } @@ -221,7 +227,7 @@ QVariantList AsyncFetcher::getCachedFrames() const } bool AsyncFetcher::getFrame(const std::string& path, - std::shared_ptr>& image, + std::shared_ptr>& image, oiio::ParamValueList& metadatas, size_t& originalWidth, size_t& originalHeight, @@ -238,25 +244,26 @@ bool AsyncFetcher::getFrame(const std::string& path, bool onlyCache = _isAsynchronous; // Upgrade the thread with the current Index - for (std::size_t idx = 0; idx < _sequence.size(); ++idx) + auto it = _pathToSeqId.find(path); + if (it != _pathToSeqId.end()) { - if (_sequence[idx] == path) - { - _currentIndex = static_cast(idx); - break; - } + _currentIndex = static_cast(it->second); } // Try to find in the cache - std::optional ovalue = _cache->get(path, _currentIndex, _resizeRatio, onlyCache); + double ratio; + { + QMutexLocker locker(&_mutexResizeRatio); + ratio = _resizeRatio; + } + std::optional ovalue = _cache->get(path, static_cast(_currentIndex), ratio, onlyCache); if (ovalue.has_value()) { auto& value = ovalue.value(); image = value.get(); - oiio::ParamValueList copy_metadatas = value.getMetadatas(); - metadatas = copy_metadatas; + metadatas = value.getMetadatas(); originalWidth = value.getOriginalWidth(); originalHeight = value.getOriginalHeight(); missingFile = value.isFileMissing(); @@ -271,7 +278,7 @@ bool AsyncFetcher::getFrame(const std::string& path, } else { - // If there is no cache, then poke the fetch thread + // Image not yet in cache; poke the fetch thread to load it _semLoop.release(1); } @@ -280,5 +287,3 @@ bool AsyncFetcher::getFrame(const std::string& path, } // namespace imgserve } // namespace qtAliceVision - -#include "AsyncFetcher.moc" diff --git a/src/qtAliceVision/AsyncFetcher.hpp b/src/qtAliceVision/AsyncFetcher.hpp index 5dffabb6..be29e34a 100644 --- a/src/qtAliceVision/AsyncFetcher.hpp +++ b/src/qtAliceVision/AsyncFetcher.hpp @@ -52,7 +52,7 @@ class AsyncFetcher : public QObject, public QRunnable * in the sequence before asked. * @return true if prefetching is activated */ - bool getPrefetching(); + bool getPrefetching() const; /** * @brief retrieve a frame from the cache in both sync and async mode @@ -105,10 +105,10 @@ class AsyncFetcher : public QObject, public QRunnable void updateCacheMemory(std::size_t maxMemory); /** - * @brief update maxMemory for the cache + * @brief get the maximum memory allowed for the cache * @return the number of bytes allowed in the cache */ - std::size_t getCacheMemory(); + std::size_t getCacheMemory() const; /** * @brief get a list of regions containing the image frames @@ -131,7 +131,7 @@ class AsyncFetcher : public QObject, public QRunnable QAtomicInt _requestSynchronous; double _resizeRatio; - QMutex _mutexResizeRatio; + mutable QMutex _mutexResizeRatio; QSemaphore _semLoop; }; diff --git a/src/qtAliceVision/CMakeLists.txt b/src/qtAliceVision/CMakeLists.txt index 2a09216a..e933d4c6 100644 --- a/src/qtAliceVision/CMakeLists.txt +++ b/src/qtAliceVision/CMakeLists.txt @@ -6,6 +6,9 @@ set(PLUGIN_SOURCES MViewStats.cpp MTracks.cpp FloatImageViewer.cpp + FloatImageViewerMaterial.cpp + FloatImageViewerMaterialShader.cpp + FloatImageViewerNode.cpp FloatTexture.cpp Surface.cpp MSfMDataStats.cpp @@ -26,6 +29,9 @@ set(PLUGIN_HEADERS MTracks.hpp MViewStats.hpp FloatImageViewer.hpp + FloatImageViewerMaterial.hpp + FloatImageViewerMaterialShader.hpp + FloatImageViewerNode.hpp FloatTexture.hpp MSfMDataStats.hpp PanoramaViewer.hpp @@ -76,12 +82,15 @@ qt6_add_shaders(qtAliceVisionPlugin "qtAliceVisionPlugin_shaders" BATCHABLE PRECOMPILE OPTIMIZED - PREFIX "/shaders" - FILES FloatImageViewer.vert FloatImageViewer.frag - FeaturesViewer.vert FeaturesViewer.frag - PhongImageViewer.vert PhongImageViewer.frag - ImageOverlay.frag - AttributeItemDelegate.frag + #PREFIX "/shaders" + FILES shaders/FloatImageViewer.vert + shaders/FloatImageViewer.frag + shaders/FeaturesViewer.vert + shaders/FeaturesViewer.frag + shaders/PhongImageViewer.vert + shaders/PhongImageViewer.frag + shaders/ImageOverlay.frag + shaders/AttributeItemDelegate.frag ) target_link_libraries(qtAliceVisionPlugin diff --git a/src/qtAliceVision/FloatImageViewer.cpp b/src/qtAliceVision/FloatImageViewer.cpp index 2d3c459a..796876da 100644 --- a/src/qtAliceVision/FloatImageViewer.cpp +++ b/src/qtAliceVision/FloatImageViewer.cpp @@ -1,12 +1,14 @@ #include "FloatImageViewer.hpp" #include "FloatTexture.hpp" -#include +#include "FloatImageViewerMaterial.hpp" +#include "FloatImageViewerMaterialShader.hpp" +#include "FloatImageViewerNode.hpp" + #include -#include -#include #include #include +#include #include @@ -17,272 +19,6 @@ namespace qtAliceVision { -namespace { -class FloatImageViewerMaterialShader; - -class FloatImageViewerMaterial : public QSGMaterial -{ - public: - FloatImageViewerMaterial() - { - std::shared_ptr image = std::make_shared(1, 1, true); - texture = std::make_unique(); - texture->setImage(image); - texture->setFiltering(QSGTexture::Nearest); - texture->setHorizontalWrapMode(QSGTexture::Repeat); - texture->setVerticalWrapMode(QSGTexture::Repeat); - } - - QSGMaterialType* type() const override - { - static QSGMaterialType type; - return &type; - } - - int compare(const QSGMaterial* other) const override - { - Q_ASSERT(other && type() == other->type()); - return other == this ? 0 : (other > this ? 1 : -1); - } - - QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; - - struct - { - // warning: matches layout and padding of FloatImageViewer.vert/frag shaders - QVector4D channelOrder = QVector4D(0, 1, 2, 3); - QVector2D fisheyeCircleCoord = QVector2D(0, 0); - float gamma = 1.f; - float gain = 0.f; - float fisheyeCircleRadius = 0.f; - float aspectRatio = 0.f; - } uniforms; - - bool dirtyUniforms; - bool appliedHoveringGamma; - std::unique_ptr texture = std::make_unique(); // should be initialize; -}; - -class FloatImageViewerMaterialShader : public QSGMaterialShader -{ - public: - FloatImageViewerMaterialShader() - { - setShaderFileName(VertexStage, QLatin1String(":/shaders/FloatImageViewer.vert.qsb")); - setShaderFileName(FragmentStage, QLatin1String(":/shaders/FloatImageViewer.frag.qsb")); - } - - bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override - { - bool changed = false; - QByteArray* buf = state.uniformData(); - Q_ASSERT(buf->size() >= 84); - if (state.isMatrixDirty()) - { - const QMatrix4x4 m = state.combinedMatrix(); - memcpy(buf->data() + 0, m.constData(), 64); - changed = true; - } - if (state.isOpacityDirty()) - { - const float opacity = state.opacity(); - memcpy(buf->data() + 64, &opacity, 4); - changed = true; - } - auto* customMaterial = static_cast(newMaterial); - if (oldMaterial != newMaterial || customMaterial->dirtyUniforms) - { - memcpy(buf->data() + 80, &customMaterial->uniforms, 40); - customMaterial->dirtyUniforms = false; - changed = true; - } - return changed; - } - - void updateSampledImage(RenderState& state, int binding, QSGTexture** texture, QSGMaterial* newMaterial, QSGMaterial*) override - { - FloatImageViewerMaterial* mat = static_cast(newMaterial); - if (binding == 1) - { - if (mat->texture) - { - mat->texture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch()); - } - *texture = mat->texture.get(); - } - } -}; - -QSGMaterialShader* FloatImageViewerMaterial::createShader(QSGRendererInterface::RenderMode) const { return new FloatImageViewerMaterialShader; } - -class FloatImageViewerNode : public QSGGeometryNode -{ - public: - FloatImageViewerNode(int vertexCount, int indexCount) - { - auto* m = new FloatImageViewerMaterial; - setMaterial(m); - setFlag(OwnsMaterial, true); - - QSGGeometry* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), vertexCount, indexCount); - QSGGeometry::updateTexturedRectGeometry(geometry, QRect(), QRect()); - geometry->setDrawingMode(GL_TRIANGLES); - geometry->setIndexDataPattern(QSGGeometry::StaticPattern); - geometry->setVertexDataPattern(QSGGeometry::StaticPattern); - setGeometry(geometry); - setFlag(OwnsGeometry, true); - - { - /* Geometry and Material for the Grid */ - _gridNode = new QSGGeometryNode; - auto gridMaterial = new QSGFlatColorMaterial; - { - // Vertexcount of the grid is equal to indexCount of the image - QSGGeometry* geometryLine = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), indexCount); - geometryLine->setDrawingMode(GL_LINES); - geometryLine->setLineWidth(2); - - _gridNode->setGeometry(geometryLine); - _gridNode->setFlags(QSGNode::OwnsGeometry); - _gridNode->setMaterial(gridMaterial); - _gridNode->setFlags(QSGNode::OwnsMaterial); - } - appendChildNode(_gridNode); - } - } - - void setSubdivisions(int vertexCount, int indexCount) - { - geometry()->allocate(vertexCount, indexCount); - markDirty(QSGNode::DirtyGeometry); - - // Vertexcount of the grid is equal to indexCount of the image - _gridNode->geometry()->allocate(indexCount); - _gridNode->markDirty(QSGNode::DirtyGeometry); - } - - void updatePaintSurface(Surface& surface, QSize textureSize, int downscaleLevel, bool canBeHovered, bool wasHovered) - { - // Highlight - if (canBeHovered) - { - if (surface.getMouseOver() && !wasHovered) - { - auto* m = static_cast(material()); - - if (!m->appliedHoveringGamma) - { - setGamma(m->uniforms.gamma + 1.f); - m->appliedHoveringGamma = true; - } - } - - else if (!surface.getMouseOver() && wasHovered) - { - auto* m = static_cast(material()); - if (m->appliedHoveringGamma) - { - setGamma(m->uniforms.gamma - 1.f); - m->appliedHoveringGamma = false; - } - } - markDirty(QSGNode::DirtyMaterial); - } - - // If vertices has changed, Re-Compute the grid - if (surface.hasVerticesChanged()) - { - // Retrieve Vertices and Index Data - QSGGeometry::TexturedPoint2D* vertices = geometry()->vertexDataAsTexturedPoint2D(); - quint16* indices = geometry()->indexDataAsUShort(); - - // Update surface - surface.update(vertices, indices, textureSize, downscaleLevel); - - geometry()->markIndexDataDirty(); - geometry()->markVertexDataDirty(); - markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); - - // Fill the Surface vertices array - surface.fillVertices(vertices); - } - - // Draw the grid if Distortion Viewer is enabled and Grid Mode is enabled - surface.getDisplayGrid() ? surface.computeGrid(_gridNode->geometry()) : surface.removeGrid(_gridNode->geometry()); - _gridNode->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); - } - - void setRect(const QRectF& bounds) - { - QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1)); - markDirty(QSGNode::DirtyGeometry); - } - - void setChannelOrder(QVector4D channelOrder) - { - auto* m = static_cast(material()); - m->uniforms.channelOrder = channelOrder; - m->dirtyUniforms = true; - markDirty(DirtyMaterial); - } - - void setBlending(bool value) - { - auto* m = static_cast(material()); - m->setFlag(QSGMaterial::Blending, value); - } - - void setGamma(float gamma) - { - auto* m = static_cast(material()); - m->uniforms.gamma = gamma; - m->dirtyUniforms = true; - markDirty(DirtyMaterial); - } - - void setGain(float gain) - { - auto* m = static_cast(material()); - m->uniforms.gain = gain; - m->dirtyUniforms = true; - markDirty(DirtyMaterial); - } - - void setTexture(std::unique_ptr texture) - { - auto* m = static_cast(material()); - m->texture = std::move(texture); - markDirty(DirtyMaterial); - } - - void setGridColor(const QColor& gridColor) - { - auto* m = static_cast(_gridNode->material()); - m->setColor(gridColor); - } - - void setFisheye(float aspectRatio, float fisheyeCircleRadius, QVector2D fisheyeCircleCoord) - { - auto* m = static_cast(material()); - m->uniforms.aspectRatio = aspectRatio; - m->uniforms.fisheyeCircleRadius = fisheyeCircleRadius; - m->uniforms.fisheyeCircleCoord = fisheyeCircleCoord; - m->dirtyUniforms = true; - markDirty(DirtyMaterial); - } - - void resetFisheye() - { - auto* m = static_cast(material()); - m->uniforms.fisheyeCircleRadius = 0.f; - m->dirtyUniforms = true; - markDirty(DirtyMaterial); - } - - private: - QSGGeometryNode* _gridNode; -}; -} // namespace FloatImageViewer::FloatImageViewer(QQuickItem* parent) : QQuickItem(parent) @@ -395,13 +131,14 @@ double FloatImageViewer::getResizeRatio() void FloatImageViewer::setMemoryLimit(int memoryLimit) { - _sequenceCache.setMemoryLimit(memoryLimit); + const int clampedMemoryLimit = std::max(0, memoryLimit); + _sequenceCache.setMemoryLimit(static_cast(clampedMemoryLimit)); Q_EMIT memoryLimitChanged(); } int FloatImageViewer::getMemoryLimit() { - return _sequenceCache.getMemoryLimit(); + return static_cast(_sequenceCache.getMemoryLimit()); } QVariantList FloatImageViewer::getCachedFrames() const @@ -487,7 +224,7 @@ void FloatImageViewer::reload() Q_EMIT cachedFramesChanged(); } -void FloatImageViewer::playback(bool active) {} +void FloatImageViewer::playback(bool /*active*/) {} void FloatImageViewer::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { @@ -546,13 +283,37 @@ QSGNode* FloatImageViewer::updatePaintNode(QSGNode* oldNode, [[maybe_unused]] QQ texture->setHorizontalWrapMode(QSGTexture::Repeat); texture->setVerticalWrapMode(QSGTexture::Repeat); newTextureSize = texture->textureSize(); + + // commitTextureOperations() runs on the render thread after this function returns + // and may downscale the image to fit GPU limits, changing the texture size. + // Post the updated size back to the GUI thread so the signal fires correctly. + QPointer weakThis(this); + texture->setOnCommit([weakThis](QSize committedSize) { + if (!weakThis) + { + return; + } + + QMetaObject::invokeMethod(weakThis, [weakThis, committedSize]() { + if (!weakThis) + { + return; + } + + if (weakThis->_textureSize != committedSize) + { + weakThis->_textureSize = committedSize; + weakThis->_geometryChanged = true; + Q_EMIT weakThis->textureSizeChanged(); + } + }, Qt::QueuedConnection); + }); // Crop the image to only display what is inside the fisheye circle const aliceVision::camera::Equidistant* intrinsicEquidistant = _surface.getIntrinsicEquidistant(); if (_cropFisheye && intrinsicEquidistant) { - const aliceVision::Vec3 fisheyeCircleParams( - intrinsicEquidistant->getCircleCenterX(), intrinsicEquidistant->getCircleCenterY(), intrinsicEquidistant->getCircleRadius()); + const aliceVision::Vec3 fisheyeCircleParams(intrinsicEquidistant->getCircleCenterX(), intrinsicEquidistant->getCircleCenterY(), intrinsicEquidistant->getCircleRadius()); const double width = _image->width() * pow(2.0, _downscaleLevel); const double height = _image->height() * pow(2.0, _downscaleLevel); @@ -605,8 +366,7 @@ QSGNode* FloatImageViewer::updatePaintNode(QSGNode* oldNode, [[maybe_unused]] QQ static const int MARGIN = 0; geometryRect = geometryRect.adjusted(MARGIN, MARGIN, -MARGIN, -MARGIN); - QSGGeometry::updateTexturedRectGeometry(node->geometry(), geometryRect, QRectF(0, 0, 1, 1)); - node->markDirty(QSGNode::DirtyGeometry); + node->setRect(geometryRect); } _geometryChanged = false; diff --git a/src/qtAliceVision/FloatImageViewer.hpp b/src/qtAliceVision/FloatImageViewer.hpp index 4b97a3a8..27040a8a 100644 --- a/src/qtAliceVision/FloatImageViewer.hpp +++ b/src/qtAliceVision/FloatImageViewer.hpp @@ -1,218 +1,444 @@ #pragma once #include "FloatTexture.hpp" -#include "Surface.hpp" #include "SequenceCache.hpp" #include "SingleImageLoader.hpp" +#include "Surface.hpp" -#include #include -#include -#include +#include #include #include -#include #include -#include #include namespace qtAliceVision { /** - * @brief Load and display image. + * @brief QQuickItem that loads and displays a floating-point image. + * + * Supports single-image loading and sequence playback, adjustable gamma/gain, + * per-channel display modes, optional fisheye cropping, and panorama downscaling. + * + * The item integrates with Qt Quick's scene graph via a custom QSGNode and + * exposes its state through QML-bindable properties and signals. */ class FloatImageViewer : public QQuickItem { Q_OBJECT - // Q_PROPERTIES + + // ------------------------------------------------------------------------- + // Image source & loading + // ------------------------------------------------------------------------- + + /** @brief URL of the image file to display. */ Q_PROPERTY(QUrl source MEMBER _source NOTIFY sourceChanged) + /** @brief Current loading status. @see EStatus */ + Q_PROPERTY(EStatus status READ status NOTIFY statusChanged) + + /** @brief When true, clears the current image before starting a new load. */ + Q_PROPERTY(bool clearBeforeLoad MEMBER _clearBeforeLoad NOTIFY clearBeforeLoadChanged) + + // ------------------------------------------------------------------------- + // Display parameters + // ------------------------------------------------------------------------- + + /** @brief Gamma correction factor applied to the displayed image (default: 1.0). */ Q_PROPERTY(float gamma MEMBER _gamma NOTIFY gammaChanged) + /** @brief Gain (exposure multiplier) applied to the displayed image (default: 1.0). */ Q_PROPERTY(float gain MEMBER _gain NOTIFY gainChanged) - Q_PROPERTY(bool canBeHovered MEMBER _canBeHovered NOTIFY canBeHoveredChanged) + /** @brief Selects which channel(s) to display. @see EChannelMode */ + Q_PROPERTY(EChannelMode channelMode MEMBER _channelMode NOTIFY channelModeChanged) + + // ------------------------------------------------------------------------- + // Size & geometry + // ------------------------------------------------------------------------- + /** @brief Size of the GPU texture used for rendering. */ Q_PROPERTY(QSize textureSize MEMBER _textureSize NOTIFY textureSizeChanged) + /** @brief Original pixel dimensions of the loaded image (read-only). */ Q_PROPERTY(QSize sourceSize READ sourceSize NOTIFY sourceSizeChanged) - Q_PROPERTY(EStatus status READ status NOTIFY statusChanged) + /** @brief Downscale level applied when loading panorama images (0 = full resolution). */ + Q_PROPERTY(int downscaleLevel READ getDownscaleLevel WRITE setDownscaleLevel NOTIFY downscaleLevelChanged) - Q_PROPERTY(bool clearBeforeLoad MEMBER _clearBeforeLoad NOTIFY clearBeforeLoadChanged) + /** @brief Scale ratio of the viewer relative to the image size (clamped internally). */ + Q_PROPERTY(double resizeRatio READ getResizeRatio WRITE setResizeRatio NOTIFY resizeRatioChanged) - Q_PROPERTY(EChannelMode channelMode MEMBER _channelMode NOTIFY channelModeChanged) + // ------------------------------------------------------------------------- + // Surface & interaction + // ------------------------------------------------------------------------- - Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) + /** @brief Pointer to the internal Surface object (read-only from QML). */ + Q_PROPERTY(Surface* surface READ getSurfacePtr NOTIFY surfaceChanged) - Q_PROPERTY(int downscaleLevel READ getDownscaleLevel WRITE setDownscaleLevel NOTIFY downscaleLevelChanged) + /** @brief When true, pixel hover information is enabled for this viewer. */ + Q_PROPERTY(bool canBeHovered MEMBER _canBeHovered NOTIFY canBeHoveredChanged) - Q_PROPERTY(Surface* surface READ getSurfacePtr NOTIFY surfaceChanged) + // ------------------------------------------------------------------------- + // Fisheye + // ------------------------------------------------------------------------- - Q_PROPERTY(bool cropFisheye READ getCropFisheye WRITE setCropFisheye NOTIFY isCropFisheyeChanged) + /** @brief When true, the fisheye circle region is cropped from the display. */ + Q_PROPERTY(bool cropFisheye READ getCropFisheye WRITE setCropFisheye NOTIFY cropFisheyeChanged) - Q_PROPERTY(QVariantList sequence MEMBER _sequence WRITE setSequence NOTIFY sequenceChanged) + // ------------------------------------------------------------------------- + // Metadata + // ------------------------------------------------------------------------- - Q_PROPERTY(double resizeRatio READ getResizeRatio WRITE setResizeRatio NOTIFY resizeRatioChanged) + /** @brief Key/value map of image metadata extracted during loading. */ + Q_PROPERTY(QVariantMap metadata READ metadata NOTIFY metadataChanged) - Q_PROPERTY(QVariantList cachedFrames READ getCachedFrames NOTIFY cachedFramesChanged) + // ------------------------------------------------------------------------- + // Sequence & cache + // ------------------------------------------------------------------------- + + /** @brief Ordered list of file paths forming the image sequence. */ + Q_PROPERTY(QVariantList sequence READ getSequence WRITE setSequence NOTIFY sequenceChanged) + /** @brief When true, sequence mode is active (as opposed to single-image mode). */ Q_PROPERTY(bool useSequence MEMBER _useSequence NOTIFY useSequenceChanged) + /** @brief When true, the sequence cache is actively prefetching frames. */ Q_PROPERTY(bool fetchingSequence READ getFetchingSequence WRITE setFetchingSequence NOTIFY fetchingSequenceChanged) + /** @brief List of frame indices currently held in the sequence cache. */ + Q_PROPERTY(QVariantList cachedFrames READ getCachedFrames NOTIFY cachedFramesChanged) + + /** @brief Maximum RAM (in GB) the sequence cache is allowed to consume. */ Q_PROPERTY(int memoryLimit READ getMemoryLimit WRITE setMemoryLimit NOTIFY memoryLimitChanged) + /** + * @brief Current RAM usage of the sequence cache. + * @note Re-evaluated whenever the cache changes; @c cachedFramesChanged is reused intentionally. + * @return QPointF where x = used GB, y = total allocated GB. + */ Q_PROPERTY(QPointF ramInfo READ getRamInfo NOTIFY cachedFramesChanged) public: explicit FloatImageViewer(QQuickItem* parent = nullptr); ~FloatImageViewer() override; + // ------------------------------------------------------------------------- + // Loading status + // ------------------------------------------------------------------------- + + /** + * @brief Describes the current state of the image loading pipeline. + */ enum class EStatus : quint8 { - NONE, // nothing is happening, no error has been detected - LOADING, // an image is being loaded - OUTDATED_LOADING, // an image was already loading during the previous reload() - MISSING_FILE, // the file to load is missing - LOADING_ERROR // generic error + NONE, /**< Idle — no error detected. */ + LOADING, /**< An image is currently being loaded. */ + OUTDATED_LOADING, /**< A new load was requested while the previous one was still running. */ + MISSING_FILE, /**< The requested file does not exist on disk. */ + LOADING_ERROR /**< A generic loading error occurred. */ }; Q_ENUM(EStatus) + /** @brief Returns the current loading status. */ EStatus status() const { return _status; } + /** + * @brief Sets the loading status and emits statusChanged(). + * @param status New status value. + */ void setStatus(EStatus status); + /** @brief Returns true while an asynchronous image load is in progress. */ bool loading() const { return _loading; } + /** + * @brief Updates the loading flag and emits the relevant signals. + * @param loading True if a load is in progress. + */ void setLoading(bool loading); + // ------------------------------------------------------------------------- + // Size accessors + // ------------------------------------------------------------------------- + + /** + * @brief Returns the original pixel dimensions of the loaded image. + * @return Image size in pixels, or QSize(0,0) when no image is loaded. + */ QSize sourceSize() const { return _sourceSize; } - const QVariantMap& metadata() const { return _metadata; } + // ------------------------------------------------------------------------- + // Downscale + // ------------------------------------------------------------------------- + /** @brief Returns the current downscale level. */ int getDownscaleLevel() const { return _downscaleLevel; } + + /** + * @brief Sets the downscale level, clamped to >= 0. + * @param level Desired downscale level (0 = full resolution). Negative values are clamped to 0. + * @note Emits downscaleLevelChanged() when the value changes. + */ void setDownscaleLevel(int level) { if (level == _downscaleLevel) return; - _downscaleLevel = std::max(0, level); Q_EMIT downscaleLevelChanged(); } + // ------------------------------------------------------------------------- + // Channel mode + // ------------------------------------------------------------------------- + + /** + * @brief Controls which image channel(s) are rendered. + */ enum class EChannelMode : quint8 { - RGBA, - RGB, - R, - G, - B, - A + RGBA, /**< All four channels composited normally. */ + RGB, /**< Colour channels only; alpha channel is ignored. */ + R, /**< Red channel displayed as greyscale. */ + G, /**< Green channel displayed as greyscale. */ + B, /**< Blue channel displayed as greyscale. */ + A /**< Alpha channel displayed as greyscale. */ }; Q_ENUM(EChannelMode) + // ------------------------------------------------------------------------- + // Fisheye + // ------------------------------------------------------------------------- + + /** @brief Returns true if the fisheye crop is active. */ bool getCropFisheye() const { return _cropFisheye; } - void setCropFisheye(bool cropFisheye) { _cropFisheye = cropFisheye; } - Q_SIGNAL void isCropFisheyeChanged(); + /** + * @brief Enables or disables the fisheye circle crop. + * @param cropFisheye True to crop the fisheye region from the display. + * @note Emits cropFisheyeChanged() when the value changes. + */ + void setCropFisheye(bool cropFisheye) + { + if (_cropFisheye == cropFisheye) + { + return; + } - // Q_SIGNALS - Q_SIGNAL void sourceChanged(); - Q_SIGNAL void statusChanged(); - Q_SIGNAL void clearBeforeLoadChanged(); - Q_SIGNAL void gammaChanged(); - Q_SIGNAL void gainChanged(); - Q_SIGNAL void textureSizeChanged(); - Q_SIGNAL void sourceSizeChanged(); - Q_SIGNAL void channelModeChanged(); - Q_SIGNAL void imageChanged(); - Q_SIGNAL void metadataChanged(); - Q_SIGNAL void downscaleLevelChanged(); - Q_SIGNAL void surfaceChanged(); - Q_SIGNAL void canBeHoveredChanged(); - Q_SIGNAL void fisheyeCircleParametersChanged(); - Q_SIGNAL void sequenceChanged(); - Q_SIGNAL void targetSizeChanged(); - Q_SIGNAL void resizeRatioChanged(); - Q_SIGNAL void cachedFramesChanged(); - Q_SIGNAL void useSequenceChanged(); - Q_SIGNAL void fetchingSequenceChanged(); - Q_SIGNAL void memoryLimitChanged(); + _cropFisheye = cropFisheye; + Q_EMIT cropFisheyeChanged(); + } - // Q_INVOKABLE - Q_INVOKABLE QVector4D pixelValueAt(int x, int y); - Q_INVOKABLE void playback(bool active); + // ------------------------------------------------------------------------- + // Metadata + // ------------------------------------------------------------------------- + /** + * @brief Returns the image metadata map extracted during loading. + * @return Read-only reference to the key/value metadata map. + */ + const QVariantMap& metadata() const { return _metadata; } + + // ------------------------------------------------------------------------- + // Surface + // ------------------------------------------------------------------------- + + /** + * @brief Returns a pointer to the internal Surface object. + * @return Non-owning pointer to the Surface; lifetime is tied to this viewer. + */ Surface* getSurfacePtr() { return &_surface; } + // ------------------------------------------------------------------------- + // Sequence & cache controls + // ------------------------------------------------------------------------- + + /** + * @brief Returns the current image sequence as a list of file path strings. + * @return Copy of the sequence path list. + */ + QVariantList getSequence() const { return _sequence; } + + /** + * @brief Sets the image sequence from an ordered list of file paths. + * @param paths List of QUrl or QString entries representing the sequence frames. + */ void setSequence(const QVariantList& paths); + /** + * @brief Sets the resize ratio applied to the viewer (clamped internally). + * @param ratio Desired scale ratio. + */ void setResizeRatio(double ratio); + /** + * @brief Returns the current (clamped) resize ratio. + * @return Scale ratio value. + */ double getResizeRatio(); + /** + * @brief Enables or disables active sequence prefetching. + * @param fetching True to start prefetching; false to stop. + */ void setFetchingSequence(bool fetching); + /** + * @brief Returns true if the sequence cache is actively prefetching frames. + * @return True while prefetching is in progress. + */ bool getFetchingSequence(); + /** + * @brief Sets the maximum amount of RAM (in GB) the sequence cache may use. + * @param memoryLimit Memory limit in gigabytes. + */ void setMemoryLimit(int memoryLimit); + /** + * @brief Returns the configured memory limit for the sequence cache. + * @return Memory limit in gigabytes. + */ int getMemoryLimit(); + /** + * @brief Returns the list of frame indices currently held in the sequence cache. + * @return List of cached frame indices. + */ QVariantList getCachedFrames() const; + /** + * @brief Returns the current RAM usage of the sequence cache. + * @return QPointF where x = used GB and y = total allocated GB. + */ QPointF getRamInfo() const; + // ------------------------------------------------------------------------- + // Signals + // ------------------------------------------------------------------------- + + // Source & loading + Q_SIGNAL void sourceChanged(); + Q_SIGNAL void statusChanged(); + Q_SIGNAL void clearBeforeLoadChanged(); + + // Display parameters + Q_SIGNAL void gammaChanged(); + Q_SIGNAL void gainChanged(); + Q_SIGNAL void channelModeChanged(); + + // Size & geometry + Q_SIGNAL void textureSizeChanged(); + Q_SIGNAL void sourceSizeChanged(); + Q_SIGNAL void downscaleLevelChanged(); + Q_SIGNAL void resizeRatioChanged(); + Q_SIGNAL void targetSizeChanged(); + + // Surface & interaction + Q_SIGNAL void surfaceChanged(); + Q_SIGNAL void canBeHoveredChanged(); + + // Fisheye + Q_SIGNAL void cropFisheyeChanged(); + Q_SIGNAL void fisheyeCircleParametersChanged(); + + // Metadata & image + Q_SIGNAL void imageChanged(); + Q_SIGNAL void metadataChanged(); + + // Sequence & cache + Q_SIGNAL void sequenceChanged(); + Q_SIGNAL void useSequenceChanged(); + Q_SIGNAL void fetchingSequenceChanged(); + Q_SIGNAL void cachedFramesChanged(); + Q_SIGNAL void memoryLimitChanged(); + + // ------------------------------------------------------------------------- + // Invokables + // ------------------------------------------------------------------------- + + /** + * @brief Returns the RGBA pixel value at the given image-space coordinates. + * @param x Horizontal pixel coordinate (image space). + * @param y Vertical pixel coordinate (image space). + * @return Floating-point RGBA value as a QVector4D(r, g, b, a). + */ + Q_INVOKABLE QVector4D pixelValueAt(int x, int y); + + /** + * @brief Starts or stops sequence playback. + * @param active True to begin playback; false to pause. + */ + Q_INVOKABLE void playback(bool active); + protected: - virtual void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; + void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; private: - /// Reload image from source + /** + * @brief Triggers a reload of the image from the current source URL. + * + * Called whenever the source, downscale level, or other load-affecting + * properties change. + */ void reload(); - /// Custom QSGNode update + /** + * @brief Provides the custom QSG scene-graph node used for GPU rendering. + * @param oldNode Previously returned node, or nullptr on first call. + * @param data Additional paint node data provided by Qt Quick. + * @return Updated or newly created QSGNode. + */ QSGNode* updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) override; + // --- Source & loading state --- QUrl _source; - float _gamma = 1.f; - bool _gammaChanged = false; - float _gain = 1.f; - bool _gainChanged = false; - bool _mouseOverChanged = false; - EStatus _status = EStatus::NONE; bool _loading = false; bool _outdated = false; bool _clearBeforeLoad = true; - bool _geometryChanged = false; - bool _imageChanged = false; - EChannelMode _channelMode; + // --- Display parameters --- + float _gamma = 1.f; + bool _gammaChanged = false; + float _gain = 1.f; + bool _gainChanged = false; + EChannelMode _channelMode = EChannelMode::RGBA; bool _channelModeChanged = false; - std::shared_ptr _image; + + // --- Geometry & size --- QRectF _boundingRect; QSize _textureSize; QSize _sourceSize = QSize(0, 0); + bool _geometryChanged = false; + double _clampedResizeRatio = 1.0; /**< @brief Clamped resize ratio; exposed to QML as the @c resizeRatio property. */ + // --- Image data --- + std::shared_ptr _image; + bool _imageChanged = false; QVariantMap _metadata; + // --- Surface --- Surface _surface; - // Prevent to update surface without the root created - bool _createRoot = true; - // Level of downscale for images of a Panorama - int _downscaleLevel = 0; + bool _createRoot = true; /**< @brief Prevents updating the surface before the root item is created. */ + + // --- Downscale --- + int _downscaleLevel = 0; /**< @brief Downscale level for panorama images (0 = full resolution). */ + // --- Interaction --- bool _canBeHovered = false; + bool _mouseOverChanged = false; + // --- Fisheye --- bool _cropFisheye = false; + // --- Sequence & cache --- imgserve::SequenceCache _sequenceCache; imgserve::SingleImageLoader _singleImageLoader; - bool _useSequence = true; - double _clampedResizeRatio = 1.0; QVariantList _sequence; + bool _useSequence = true; + bool _fetchingSequence = false; }; } // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerMaterial.cpp b/src/qtAliceVision/FloatImageViewerMaterial.cpp new file mode 100644 index 00000000..77862f8b --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerMaterial.cpp @@ -0,0 +1,40 @@ +#include "FloatImageViewerMaterial.hpp" +#include "FloatImageViewerMaterialShader.hpp" + +#include + +namespace qtAliceVision { + +FloatImageViewerMaterial::FloatImageViewerMaterial() +{ + // Initialise the texture with a 1x1 placeholder so the sampler is never unbound. + auto image = std::make_shared(1, 1, true); + texture = std::make_unique(); + texture->setImage(image); + texture->setFiltering(QSGTexture::Nearest); + texture->setHorizontalWrapMode(QSGTexture::Repeat); + texture->setVerticalWrapMode(QSGTexture::Repeat); +} + +QSGMaterialType* FloatImageViewerMaterial::type() const +{ + static QSGMaterialType type; + return &type; +} + +int FloatImageViewerMaterial::compare(const QSGMaterial* other) const +{ + Q_ASSERT(other && type() == other->type()); + if (other == this) + { + return 0; + } + return std::less{}(this, other) ? -1 : 1; +} + +QSGMaterialShader* FloatImageViewerMaterial::createShader(QSGRendererInterface::RenderMode) const +{ + return new FloatImageViewerMaterialShader; +} + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerMaterial.hpp b/src/qtAliceVision/FloatImageViewerMaterial.hpp new file mode 100644 index 00000000..27ad02bd --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerMaterial.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "FloatTexture.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace qtAliceVision { + + +class FloatImageViewerMaterialShader; + +/** + * @brief Custom QSGMaterial carrying the per-frame uniform data and the floating-point image texture + * used by FloatImageViewerMaterialShader. + * + * @warning The Uniforms struct layout must stay in sync with the uniform block declared in + * FloatImageViewer.vert and FloatImageViewer.frag. + */ +class FloatImageViewerMaterial : public QSGMaterial +{ + public: + FloatImageViewerMaterial(); + + QSGMaterialType* type() const override; + int compare(const QSGMaterial* other) const override; + QSGMaterialShader* createShader(QSGRendererInterface::RenderMode renderMode) const override; + + /** + * @brief Uniform block mirrored on the CPU side. + * @warning Layout and padding must exactly match FloatImageViewer.vert / .frag. + */ + struct Uniforms + { + QVector4D channelOrder = QVector4D(0, 1, 2, 3); ///< Swizzle indices for channel display mode. + QVector2D fisheyeCircleCoord = QVector2D(0, 0); ///< Normalised centre of the fisheye circle (UV space). + float gamma = 1.f; ///< Gamma correction factor. + float gain = 0.f; ///< Exposure gain. + float fisheyeCircleRadius = 0.f; ///< Fisheye circle radius in UV space (0 = disabled). + float aspectRatio = 0.f; ///< Aspect ratio used for fisheye circle correction. + } uniforms; + + static_assert(sizeof(Uniforms) == 40, "Uniforms size mismatch — check alignment against FloatImageViewer shader UBO"); + + bool dirtyUniforms = false; ///< True when uniforms have been modified since the last GPU upload. + bool appliedHoveringGamma = false; ///< True while the hover gamma boost is currently applied. + + std::unique_ptr texture; ///< The floating-point texture uploaded to the GPU. +}; + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerMaterialShader.cpp b/src/qtAliceVision/FloatImageViewerMaterialShader.cpp new file mode 100644 index 00000000..7048ec45 --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerMaterialShader.cpp @@ -0,0 +1,91 @@ +#include "FloatImageViewerMaterialShader.hpp" +#include "FloatImageViewerMaterial.hpp" + +#include + +#include + +namespace qtAliceVision { + +namespace { + +/** + * @brief CPU-side mirror of the uniform buffer object shared by FloatImageViewer.vert/.frag. + * + * The layout follows std140 rules: + * - mat4 mvp → 64 bytes at offset 0 + * - float opacity → 4 bytes at offset 64 + * - float _pad[3] → 12 bytes of std140 padding to align the next vec4 + * - Uniforms block → 40 bytes at offset 80 + * + * @warning This struct must remain in sync with the GLSL uniform block in + * FloatImageViewer.vert and FloatImageViewer.frag. + */ +struct UniformBufferLayout +{ + float mvp[16]; ///< Combined model-view-projection matrix. + float opacity; ///< Item opacity in [0, 1]. + float _pad[3]; ///< std140 padding — do not use. + FloatImageViewerMaterial::Uniforms custom; ///< Per-material shader parameters. +}; + +static_assert(offsetof(UniformBufferLayout, mvp) == 0, "UBO layout mismatch: mvp"); +static_assert(offsetof(UniformBufferLayout, opacity) == 64, "UBO layout mismatch: opacity"); +static_assert(offsetof(UniformBufferLayout, custom) == 80, "UBO layout mismatch: custom uniforms"); +static_assert(sizeof(UniformBufferLayout) == 120, "UBO size mismatch"); + +} // namespace + +FloatImageViewerMaterialShader::FloatImageViewerMaterialShader() +{ + setShaderFileName(VertexStage, QLatin1String(":/shaders/FloatImageViewer.vert.qsb")); + setShaderFileName(FragmentStage, QLatin1String(":/shaders/FloatImageViewer.frag.qsb")); +} + +bool FloatImageViewerMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) +{ + bool changed = false; + QByteArray* buf = state.uniformData(); + Q_ASSERT(buf->size() >= static_cast(sizeof(UniformBufferLayout))); + + if (state.isMatrixDirty()) + { + const QMatrix4x4 m = state.combinedMatrix(); + memcpy(buf->data() + offsetof(UniformBufferLayout, mvp), m.constData(), sizeof(UniformBufferLayout::mvp)); + changed = true; + } + + if (state.isOpacityDirty()) + { + const float opacity = state.opacity(); + memcpy(buf->data() + offsetof(UniformBufferLayout, opacity), &opacity, sizeof(UniformBufferLayout::opacity)); + changed = true; + } + + auto* customMaterial = static_cast(newMaterial); + if (oldMaterial != newMaterial || customMaterial->dirtyUniforms) + { + memcpy(buf->data() + offsetof(UniformBufferLayout, custom), &customMaterial->uniforms, sizeof(UniformBufferLayout::custom)); + customMaterial->dirtyUniforms = false; + changed = true; + } + + return changed; +} + +void FloatImageViewerMaterialShader::updateSampledImage(RenderState& state, int binding, QSGTexture** texture, + QSGMaterial* newMaterial, QSGMaterial* /*oldMaterial*/) +{ + auto* mat = static_cast(newMaterial); + if (binding == 1) + { + if (mat->texture) + { + mat->texture->commitTextureOperations(state.rhi(), state.resourceUpdateBatch()); + } + + *texture = mat->texture.get(); + } +} + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerMaterialShader.hpp b/src/qtAliceVision/FloatImageViewerMaterialShader.hpp new file mode 100644 index 00000000..4436aaff --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerMaterialShader.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +namespace qtAliceVision { + +/** + * @brief QSGMaterialShader that drives the FloatImageViewer render pipeline. + * + * Loads the compiled FloatImageViewer vertex and fragment shaders, uploads the + * per-frame uniform block (matrix, opacity, gamma/gain/channel/fisheye parameters) + * and binds the FloatTexture to sampler binding 1. + */ +class FloatImageViewerMaterialShader : public QSGMaterialShader +{ + public: + FloatImageViewerMaterialShader(); + + /** + * @brief Uploads the combined MVP matrix, opacity, and custom uniforms to the GPU buffer. + * @param state Current render state providing the matrix and opacity. + * @param newMaterial Incoming material carrying the dirty uniform data. + * @param oldMaterial Previous material (used to detect material changes). + * @return True if any data was written to the uniform buffer. + */ + bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; + + /** + * @brief Commits the FloatTexture for sampler binding 1 and sets the output texture pointer. + * @param state Current render state providing the RHI and resource update batch. + * @param binding Sampler binding index being updated. + * @param texture Output — set to the material's FloatTexture when binding == 1. + * @param newMaterial Incoming material carrying the texture. + * @param oldMaterial Previous material (unused). + */ + void updateSampledImage(RenderState& state, int binding, QSGTexture** texture, + QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; +}; + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerNode.cpp b/src/qtAliceVision/FloatImageViewerNode.cpp new file mode 100644 index 00000000..43727d04 --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerNode.cpp @@ -0,0 +1,177 @@ +#include "FloatImageViewerNode.hpp" +#include "FloatImageViewerMaterial.hpp" +#include "Surface.hpp" + +#include +#include + +namespace qtAliceVision { + +FloatImageViewerNode::FloatImageViewerNode(int vertexCount, int indexCount) +{ + // --- Image mesh --- + auto* m = new FloatImageViewerMaterial; + setMaterial(m); + setFlag(OwnsMaterial, true); + + auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), vertexCount, indexCount); + // Empty rect is intentional: setRect() will be called by updatePaintNode() before first render. + QSGGeometry::updateTexturedRectGeometry(geometry, QRect(), QRect()); + geometry->setDrawingMode(QSGGeometry::DrawTriangles); + geometry->setIndexDataPattern(QSGGeometry::StaticPattern); + geometry->setVertexDataPattern(QSGGeometry::StaticPattern); + setGeometry(geometry); + setFlag(OwnsGeometry, true); + + // --- Grid overlay --- + _gridNode = new QSGGeometryNode; + + // The grid vertex count equals the image index count. + auto* gridGeometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), indexCount); + gridGeometry->setDrawingMode(QSGGeometry::DrawLines); + gridGeometry->setLineWidth(2); + _gridNode->setGeometry(gridGeometry); + _gridNode->setFlag(QSGNode::OwnsGeometry); // use setFlag() — setFlags() replaces all flags + + auto* gridMaterial = new QSGFlatColorMaterial; + _gridNode->setMaterial(gridMaterial); + _gridNode->setFlag(QSGNode::OwnsMaterial); + + appendChildNode(_gridNode); +} + +void FloatImageViewerNode::setSubdivisions(int vertexCount, int indexCount) +{ + geometry()->allocate(vertexCount, indexCount); + markDirty(QSGNode::DirtyGeometry); + + // Grid vertex count equals the image index count. + _gridNode->geometry()->allocate(indexCount); + _gridNode->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); +} + +void FloatImageViewerNode::updatePaintSurface(Surface& surface, QSize textureSize, int downscaleLevel, bool canBeHovered, bool mouseJustLeft) +{ + // Apply / remove the hover gamma boost. + if (canBeHovered) + { + auto* m = mat(); + if (surface.getMouseOver() && !m->appliedHoveringGamma) + { + setGamma(m->uniforms.gamma + 1.f); + m->appliedHoveringGamma = true; + markDirty(QSGNode::DirtyMaterial); + } + else if (mouseJustLeft && m->appliedHoveringGamma) + { + setGamma(m->uniforms.gamma - 1.f); + m->appliedHoveringGamma = false; + markDirty(QSGNode::DirtyMaterial); + } + } + + // Recompute mesh vertices when the surface topology has changed. + if (surface.hasVerticesChanged()) + { + auto* vertices = geometry()->vertexDataAsTexturedPoint2D(); + auto* indices = geometry()->indexDataAsUShort(); + + surface.update(vertices, indices, textureSize, downscaleLevel); + + geometry()->markIndexDataDirty(); + geometry()->markVertexDataDirty(); + markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); + + surface.fillVertices(vertices); + } + + // Redraw or clear the grid overlay only when it is visible. + if (surface.getDisplayGrid()) + { + surface.computeGrid(_gridNode->geometry()); + _gridNode->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); + } + else if (surface.hasVerticesChanged()) + { + // Vertices changed while grid is hidden — clear residual data. + surface.removeGrid(_gridNode->geometry()); + _gridNode->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); + } +} + +void FloatImageViewerNode::setRect(const QRectF& bounds) +{ + QSGGeometry::updateTexturedRectGeometry(geometry(), bounds, QRectF(0, 0, 1, 1)); + markDirty(QSGNode::DirtyGeometry); +} + +FloatImageViewerMaterial* FloatImageViewerNode::mat() const +{ + auto* m = static_cast(material()); + Q_ASSERT(m); + return m; +} + +void FloatImageViewerNode::setChannelOrder(QVector4D channelOrder) +{ + auto* m = mat(); + m->uniforms.channelOrder = channelOrder; + m->dirtyUniforms = true; + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::setBlending(bool value) +{ + mat()->setFlag(QSGMaterial::Blending, value); + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::setGamma(float gamma) +{ + auto* m = mat(); + m->uniforms.gamma = gamma; + m->dirtyUniforms = true; + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::setGain(float gain) +{ + auto* m = mat(); + m->uniforms.gain = gain; + m->dirtyUniforms = true; + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::setTexture(std::unique_ptr texture) +{ + auto* m = mat(); + m->texture = std::move(texture); + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::setGridColor(const QColor& gridColor) +{ + auto* m = static_cast(_gridNode->material()); + m->setColor(gridColor); + _gridNode->markDirty(QSGNode::DirtyMaterial); +} + +void FloatImageViewerNode::setFisheye(float aspectRatio, float fisheyeCircleRadius, QVector2D fisheyeCircleCoord) +{ + auto* m = mat(); + m->uniforms.aspectRatio = aspectRatio; + m->uniforms.fisheyeCircleRadius = fisheyeCircleRadius; + m->uniforms.fisheyeCircleCoord = fisheyeCircleCoord; + m->dirtyUniforms = true; + markDirty(DirtyMaterial); +} + +void FloatImageViewerNode::resetFisheye() +{ + auto* m = mat(); + m->uniforms.fisheyeCircleRadius = 0.f; + m->dirtyUniforms = true; + markDirty(DirtyMaterial); +} + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatImageViewerNode.hpp b/src/qtAliceVision/FloatImageViewerNode.hpp new file mode 100644 index 00000000..a50a4cb4 --- /dev/null +++ b/src/qtAliceVision/FloatImageViewerNode.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "FloatImageViewerMaterial.hpp" +#include "FloatTexture.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace qtAliceVision { + +class Surface; + +/** + * @brief Scene graph node responsible for rendering a FloatImageViewer frame. + * + * Owns both the image geometry node (textured quad / distortion mesh) and a child + * grid node used when the distortion grid overlay is enabled. + * All setters mark the node dirty so Qt Quick re-renders on the next frame. + */ +class FloatImageViewerNode : public QSGGeometryNode +{ + public: + /** + * @brief Constructs the node with pre-allocated geometry buffers. + * @param vertexCount Initial number of vertices for the image mesh. + * @param indexCount Initial number of indices for the image mesh (also used as the grid vertex count). + */ + FloatImageViewerNode(int vertexCount, int indexCount); + + /** + * @brief Reallocates geometry buffers when the mesh subdivision changes. + * @param vertexCount New vertex count for the image mesh. + * @param indexCount New index count for the image mesh / vertex count for the grid. + */ + void setSubdivisions(int vertexCount, int indexCount); + + /** + * @brief Updates the mesh geometry, applies hover gamma, and redraws the grid overlay. + * @param surface Surface object providing vertex data and display settings. + * @param textureSize Size of the texture currently bound to this node. + * @param downscaleLevel Downscale level applied to the loaded image. + * @param canBeHovered When true, a gamma boost is applied on mouse-over. + * @param mouseJustLeft True on the frame the cursor leaves the item (used to revert the gamma boost). + */ + void updatePaintSurface(Surface& surface, QSize textureSize, int downscaleLevel, bool canBeHovered, bool mouseJustLeft); + + /** + * @brief Updates the textured-rect geometry to fill the given bounding rectangle. + * @param bounds Target rectangle in scene coordinates. + */ + void setRect(const QRectF& bounds); + + /** + * @brief Sets the channel swizzle order uploaded to the shader. + * @param channelOrder Four-component index vector selecting r/g/b/a source channels. + */ + void setChannelOrder(QVector4D channelOrder); + + /** + * @brief Enables or disables alpha blending on the material. + * @param value True to enable blending (required for RGBA mode). + */ + void setBlending(bool value); + + /** + * @brief Sets the gamma correction factor uploaded to the shader. + * @param gamma Gamma value (1.0 = linear). + */ + void setGamma(float gamma); + + /** + * @brief Sets the exposure gain factor uploaded to the shader. + * @param gain Gain multiplier (0.0 = no change in the shader's convention). + */ + void setGain(float gain); + + /** + * @brief Replaces the floating-point texture displayed by this node. + * @param texture New texture to bind; ownership is transferred to the material. + */ + void setTexture(std::unique_ptr texture); + + /** + * @brief Sets the colour of the distortion grid overlay. + * @param gridColor Desired grid line colour. + */ + void setGridColor(const QColor& gridColor); + + /** + * @brief Configures the fisheye circle crop parameters in the shader. + * @param aspectRatio Width-to-height (or height-to-width) ratio of the full-res image. + * @param fisheyeCircleRadius Radius of the fisheye circle in UV space [0, 0.5]. + * @param fisheyeCircleCoord Normalised centre of the fisheye circle in UV space. + */ + void setFisheye(float aspectRatio, float fisheyeCircleRadius, QVector2D fisheyeCircleCoord); + + /** + * @brief Disables fisheye circle cropping (sets radius to 0 in the shader). + */ + void resetFisheye(); + + private: + /** + * @brief Returns the typed image material, asserting it is non-null. + * @return Pointer to the FloatImageViewerMaterial owned by this node. + */ + FloatImageViewerMaterial* mat() const; + + QSGGeometryNode* _gridNode = nullptr; ///< Child node rendering the distortion grid overlay. +}; + +} // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatTexture.cpp b/src/qtAliceVision/FloatTexture.cpp index 337a5e71..79663822 100644 --- a/src/qtAliceVision/FloatTexture.cpp +++ b/src/qtAliceVision/FloatTexture.cpp @@ -1,39 +1,56 @@ #include "FloatTexture.hpp" #include -#include -#include - #include #include namespace qtAliceVision { -int FloatTexture::_maxTextureSize = -1; -FloatTexture::FloatTexture() {} +static constexpr const char* kLogPrefix = "[QtAliceVision] "; + +int FloatTexture::_maxTextureSize = -1; -FloatTexture::~FloatTexture() +void FloatTexture::RhiTextureDeleter::operator()(QRhiTexture* t) const { - if (_rhiTexture) + if (t) { - _rhiTexture->destroy(); + t->destroy(); + delete t; } } -void FloatTexture::setImage(std::shared_ptr& image) +FloatTexture::FloatTexture() {} + +FloatTexture::~FloatTexture() = default; + +void FloatTexture::setImage(const std::shared_ptr& image) { + if (!image || image->width() == 0 || image->height() == 0) + { + qWarning() << kLogPrefix << "setImage() called with a null or empty image; ignoring."; + return; + } _srcImage = image; - _textureSize = {_srcImage->width(), _srcImage->height()}; + _textureSize = {image->width(), image->height()}; _dirty = true; _mipmapsGenerated = false; } -bool FloatTexture::isValid() const { return _srcImage->width() != 0 && _srcImage->height() != 0; } +bool FloatTexture::isValid() const +{ + return _srcImage && _srcImage->width() != 0 && _srcImage->height() != 0; +} -qint64 FloatTexture::comparisonKey() const { return _rhiTexture ? _rhiTexture->nativeTexture().object : 0; } +qint64 FloatTexture::comparisonKey() const +{ + return _rhiTexture ? static_cast(_rhiTexture->nativeTexture().object) : 0; +} -QRhiTexture* FloatTexture::rhiTexture() const { return _rhiTexture; } +QRhiTexture* FloatTexture::rhiTexture() const +{ + return _rhiTexture.get(); +} void FloatTexture::commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* resourceUpdates) { @@ -42,55 +59,82 @@ void FloatTexture::commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* r return; } + if (!isValid()) { - if (_rhiTexture) - { - _rhiTexture->destroy(); - } - _rhiTexture = nullptr; + _rhiTexture.reset(); + _dirty = false; return; } - QRhiTexture::Format texFormat = QRhiTexture::RGBA32F; + const QRhiTexture::Format texFormat = QRhiTexture::RGBA32F; if (!rhi->isTextureFormatSupported(texFormat)) { - qWarning() << "[QtAliceVision] Unsupported float images."; + qWarning() << kLogPrefix << "Unsupported float texture format; cannot upload image."; + _dirty = false; return; } - // Init max texture size + // Query the GPU's maximum texture dimension on first use. if (_maxTextureSize == -1) { _maxTextureSize = rhi->resourceLimit(QRhi::TextureSizeMax); } - // Downscale the texture to fit inside the max texture limit if it is too big. - while (_maxTextureSize != -1 && (_srcImage->width() > _maxTextureSize || _srcImage->height() > _maxTextureSize)) + const FloatImage* uploadImage = _srcImage.get(); + FloatImage scaledImage; + if (_maxTextureSize != -1 && (_srcImage->width() > _maxTextureSize || _srcImage->height() > _maxTextureSize)) { - FloatImage tmp; - aliceVision::image::imageHalfSample(*_srcImage, tmp); - *_srcImage = std::move(tmp); + // Only copy/downscale when the source exceeds GPU limits. + scaledImage = *_srcImage; + while (scaledImage.width() > _maxTextureSize || scaledImage.height() > _maxTextureSize) + { + FloatImage tmp; + aliceVision::image::imageHalfSample(scaledImage, tmp); + scaledImage = std::move(tmp); + } + uploadImage = &scaledImage; } - _textureSize = {_srcImage->width(), _srcImage->height()}; + const QSize newTextureSize(uploadImage->width(), uploadImage->height()); const QRhiTexture::Flags texFlags(hasMipmaps() ? QRhiTexture::MipMapped : 0); - _rhiTexture = rhi->newTexture(texFormat, _textureSize, 1, texFlags); - if (!_rhiTexture || !_rhiTexture->create()) + + const bool needsReallocation = !_rhiTexture || _rhiTexture->format() != texFormat || _rhiTexture->pixelSize() != newTextureSize || _rhiTexture->flags() != texFlags; + if (needsReallocation) { - qWarning() << "[QtAliceVision] Unable to create float texture."; - return; + _rhiTexture.reset(); + _rhiTexture.reset(rhi->newTexture(texFormat, newTextureSize, 1, texFlags)); + if (!_rhiTexture || !_rhiTexture->create()) + { + qWarning() << kLogPrefix << "Unable to create float texture."; + _rhiTexture.reset(); + _dirty = false; + return; + } } - const QByteArray textureData(reinterpret_cast(_srcImage->data()), _srcImage->size() * sizeof(*_srcImage->data())); - resourceUpdates->uploadTexture(_rhiTexture, QRhiTextureUploadEntry(0, 0, QRhiTextureSubresourceUploadDescription(textureData))); + _textureSize = newTextureSize; + + // Declare texture data from image properties + const QByteArray textureData( + reinterpret_cast(uploadImage->data()), + static_cast(uploadImage->size()) * static_cast(sizeof(*uploadImage->data()))); + + // Upload image data to texture + resourceUpdates->uploadTexture(_rhiTexture.get(), QRhiTextureUploadEntry(0, 0, QRhiTextureSubresourceUploadDescription(textureData))); if (hasMipmaps()) { - resourceUpdates->generateMips(_rhiTexture); + resourceUpdates->generateMips(_rhiTexture.get()); _mipmapsGenerated = true; } + _dirty = false; + + // Notify the owner if the committed size differs from what setImage() reported + // (e.g. because the image was downscaled to fit the GPU texture size limit). + if (_onCommit) + _onCommit(_textureSize); } } // namespace qtAliceVision diff --git a/src/qtAliceVision/FloatTexture.hpp b/src/qtAliceVision/FloatTexture.hpp index bab2df75..1408bae1 100644 --- a/src/qtAliceVision/FloatTexture.hpp +++ b/src/qtAliceVision/FloatTexture.hpp @@ -5,14 +5,19 @@ #include #include +#include #include namespace qtAliceVision { +/** @brief 32-bit float RGBA image type used throughout the viewer pipeline. */ using FloatImage = aliceVision::image::Image; /** - * @brief A custom QSGTexture to display AliceVision images in QML + * @brief A custom QSGTexture that uploads an AliceVision floating-point image to the GPU for display in QML. + * + * The texture is marked dirty whenever a new image is set and uploads its data + * to the RHI backend during the next commitTextureOperations() call. */ class FloatTexture : public QSGTexture { @@ -20,40 +25,91 @@ class FloatTexture : public QSGTexture FloatTexture(); ~FloatTexture() override; - virtual qint64 comparisonKey() const override; - virtual QRhiTexture* rhiTexture() const override; - virtual void commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* resourceUpdates) override; + /** @brief Returns an integer key used by the scene graph to compare and deduplicate textures. */ + qint64 comparisonKey() const override; + /** @brief Returns the underlying QRhiTexture, or nullptr if not yet uploaded. */ + QRhiTexture* rhiTexture() const override; + + /** + * @brief Uploads pending image data to the GPU via the provided RHI resource update batch. + * @param rhi The RHI instance managing GPU resources. + * @param resourceUpdates Batch to which upload commands are appended. + */ + void commitTextureOperations(QRhi* rhi, QRhiResourceUpdateBatch* resourceUpdates) override; + + /** + * @brief Returns the size of the uploaded texture in pixels. + * @return Texture dimensions; QSize() if no image has been uploaded yet. + */ QSize textureSize() const override { return _textureSize; } + /** @brief Returns true; this texture always carries an alpha channel. */ bool hasAlphaChannel() const override { return true; } + /** @brief Returns true if mipmap filtering is enabled on the texture. */ bool hasMipmaps() const override { return mipmapFiltering() != QSGTexture::None; } - void setImage(std::shared_ptr& image); - const FloatImage& image() { return *_srcImage; } + /** + * @brief Sets the source image to be uploaded on the next commitTextureOperations() call. + * @param image Shared pointer to the floating-point RGBA source image. + */ + void setImage(const std::shared_ptr& image); /** - * @brief Get the maximum dimension of a texture. + * @brief Registers a callback invoked on the render thread after commitTextureOperations() + * finishes, passing the final (possibly downscaled) texture size. * - * If the provided image is too large, it will be downscaled to fit the max dimension. + * Use QMetaObject::invokeMethod with Qt::QueuedConnection inside the callback to + * safely marshal work back to the GUI thread. + */ + void setOnCommit(std::function callback) { _onCommit = std::move(callback); } + + /** + * @brief Returns a read-only reference to the currently assigned source image. + * @return Const reference to the FloatImage held by this texture. + */ + const FloatImage& image() const { return *_srcImage; } + + /** + * @brief Returns the maximum supported texture dimension (width or height) in pixels. * - * @return -1 if unknown else the max size of a texture + * If the source image exceeds this dimension it will be downscaled before upload. + * + * @return Maximum texture size in pixels, or -1 if the limit has not yet been queried from the GPU. */ static int maxTextureSize() { return _maxTextureSize; } private: + /** + * @brief Returns true if the texture has a valid RHI texture object and a non-empty source image. + */ bool isValid() const; - private: std::shared_ptr _srcImage; + std::function _onCommit; - QRhiTexture* _rhiTexture = nullptr; + /** + * @brief Custom deleter for the QRhiTexture unique_ptr. + * + * QRhiTexture follows a two-step teardown: destroy() releases the underlying GPU + * resource (framebuffer object, Vulkan image, etc.) while the C++ wrapper object + * itself must be freed separately with delete. Using this deleter as the second + * template argument of std::unique_ptr ensures both steps always happen together, + * preventing both GPU resource leaks and C++ heap leaks on every image update and + * at object destruction. + */ + struct RhiTextureDeleter + { + void operator()(QRhiTexture* t) const; + }; + std::unique_ptr _rhiTexture; QSize _textureSize; bool _dirty = false; bool _mipmapsGenerated = false; + /** @brief GPU-queried maximum texture dimension. -1 until first queried. */ static int _maxTextureSize; }; diff --git a/src/qtAliceVision/ImageCache.cpp b/src/qtAliceVision/ImageCache.cpp index 45e870c7..7424940e 100644 --- a/src/qtAliceVision/ImageCache.cpp +++ b/src/qtAliceVision/ImageCache.cpp @@ -2,7 +2,7 @@ namespace qtAliceVision { -ImageCache::ImageCache(unsigned long maxSize, const aliceVision::image::ImageReadOptions& options) +ImageCache::ImageCache(unsigned long long int maxSize, const aliceVision::image::ImageReadOptions& options) : _info(maxSize), _options(options), _referenceFrameId(0) @@ -45,6 +45,7 @@ void ImageCache::cleanup(size_t requestedSize, const CacheKey& toAdd) { _imagePtrs.erase(key); _info.update(_imagePtrs); + _info.incrementRemoved(); erased = true; break; } @@ -79,6 +80,8 @@ void ImageCache::cleanup(size_t requestedSize, const CacheKey& toAdd) const CacheKey* pKey = orderedKeys.rbegin()->second; _imagePtrs.erase(*pKey); _info.update(_imagePtrs); + _info.incrementRemoved(); + erased = true; } } diff --git a/src/qtAliceVision/ImageCache.hpp b/src/qtAliceVision/ImageCache.hpp index 8e61267e..60753c08 100644 --- a/src/qtAliceVision/ImageCache.hpp +++ b/src/qtAliceVision/ImageCache.hpp @@ -1,5 +1,14 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -60,6 +69,8 @@ class CacheValue template CacheValue(unsigned frameId, std::shared_ptr> img, bool missingFile, bool loadError) : _vimg(img), + _originalWidth(0), + _originalHeight(0), _frameId(frameId), _missingFile(missingFile), _loadError(loadError) @@ -132,8 +143,8 @@ class CacheValue unsigned _originalHeight; oiio::ParamValueList _metadatas; unsigned _frameId; - bool _loadError; bool _missingFile; + bool _loadError; }; /** @@ -142,7 +153,7 @@ class CacheValue class CacheInfo { public: - CacheInfo(unsigned long int maxSize) + CacheInfo(unsigned long long int maxSize) : _maxSize(maxSize) {} @@ -158,6 +169,12 @@ class CacheInfo _nbLoadFromDisk++; } + void incrementRemoved() + { + const std::scoped_lock lock(_mutex); + _nbRemoveUnused++; + } + unsigned long long int getCapacity() const { const std::scoped_lock lock(_mutex); @@ -169,6 +186,7 @@ class CacheInfo std::scoped_lock lock(_mutex); _contentSize = 0; + _nbImages = 0; for (const auto& [key, value] : images) { _contentSize += value.memorySize(); @@ -207,12 +225,19 @@ class CacheInfo return _nbLoadFromDisk; } + int getLoadFromCache() const + { + const std::scoped_lock lock(_mutex); + return _nbLoadFromCache; + } + void setMaxMemory(unsigned long long int maxSize) { std::scoped_lock lock(_mutex); _maxSize = maxSize; } + private: /// memory usage limits unsigned long long int _maxSize; @@ -239,7 +264,7 @@ class ImageCache * @param[in] maxSize the cache maximal size (in bytes) * @param[in] options the reading options that will be used when loading images through this cache */ - ImageCache(unsigned long maxSize, const aliceVision::image::ImageReadOptions& options); + ImageCache(unsigned long long int maxSize, const aliceVision::image::ImageReadOptions& options); /** * @brief Destroy the cache and the unused images it contains. @@ -413,7 +438,7 @@ std::optional ImageCache::load(const CacheKey& key, unsigned frameId int th = static_cast(std::max(1, int(std::ceil(dh)))); using TInfo = aliceVision::image::ColorTypeInfo; - cleanup(tw * th * std::size_t(TInfo::size), key); + cleanup(static_cast(tw) * static_cast(th) * TInfo::size, key); // apply downscale aliceVision::imageAlgo::resizeImage(tw, th, img, *resized); diff --git a/src/qtAliceVision/MFeatures.cpp b/src/qtAliceVision/MFeatures.cpp index 255d4731..f31fb129 100644 --- a/src/qtAliceVision/MFeatures.cpp +++ b/src/qtAliceVision/MFeatures.cpp @@ -207,5 +207,3 @@ int MFeatures::nbFeatures(QString describerType, int viewId) const } } // namespace qtAliceVision - -#include "MFeatures.moc" diff --git a/src/qtAliceVision/MSfMData.cpp b/src/qtAliceVision/MSfMData.cpp index 035a1563..4b49e098 100644 --- a/src/qtAliceVision/MSfMData.cpp +++ b/src/qtAliceVision/MSfMData.cpp @@ -158,5 +158,3 @@ int MSfMData::nbLandmarks(QString describerType, int viewId) const } } // namespace qtAliceVision - -#include "MSfMData.moc" diff --git a/src/qtAliceVision/MTracks.cpp b/src/qtAliceVision/MTracks.cpp index b5f1ee53..59dbafed 100644 --- a/src/qtAliceVision/MTracks.cpp +++ b/src/qtAliceVision/MTracks.cpp @@ -228,5 +228,3 @@ int MTracks::nbMatches(QString describerType, int viewId) const } } // namespace qtAliceVision - -#include "MTracks.moc" diff --git a/src/qtAliceVision/Painter.cpp b/src/qtAliceVision/Painter.cpp index 68ff5603..4f516878 100644 --- a/src/qtAliceVision/Painter.cpp +++ b/src/qtAliceVision/Painter.cpp @@ -4,6 +4,8 @@ #include #include +#include + #if QT_CONFIG(opengl) #include #else @@ -39,7 +41,11 @@ class PointMaterial : public QSGMaterial int compare(const QSGMaterial* other) const override { Q_ASSERT(other && type() == other->type()); - return other == this ? 0 : (other > this ? 1 : -1); + if (other == this) + { + return 0; + } + return std::less{}(this, other) ? -1 : 1; } QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; diff --git a/src/qtAliceVision/PhongImageViewer.cpp b/src/qtAliceVision/PhongImageViewer.cpp index e2dde5b2..85c4eadf 100644 --- a/src/qtAliceVision/PhongImageViewer.cpp +++ b/src/qtAliceVision/PhongImageViewer.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace qtAliceVision { namespace { @@ -24,7 +26,12 @@ class PhongImageViewerMaterial : public QSGMaterial int compare(const QSGMaterial* other) const override { Q_ASSERT(other && type() == other->type()); - return other == this ? 0 : (other > this ? 1 : -1); + if (other == this) + { + return 0; + } + + return std::less{}(this, other) ? -1 : 1; } QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; @@ -304,7 +311,7 @@ void PhongImageViewer::clearImages() Q_EMIT imageChanged(); } -QSGNode* PhongImageViewer::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* data) +QSGNode* PhongImageViewer::updatePaintNode(QSGNode* oldNode, QQuickItem::UpdatePaintNodeData* /*data*/) { auto* node = static_cast(oldNode); bool isNewNode = false; diff --git a/src/qtAliceVision/SequenceCache.cpp b/src/qtAliceVision/SequenceCache.cpp index 7f30722f..8632bb8e 100644 --- a/src/qtAliceVision/SequenceCache.cpp +++ b/src/qtAliceVision/SequenceCache.cpp @@ -23,12 +23,8 @@ SequenceCache::SequenceCache(QObject* parent) _fetcher.setAutoDelete(false); - // Cache does not exist - // Let's create a new one! - { - ImageCache::uptr cache = std::make_unique(_maxMemory, image::EImageColorSpace::LINEAR); - _fetcher.setCache(std::move(cache)); - } + ImageCache::uptr cache = std::make_unique(_maxMemory, image::EImageColorSpace::LINEAR); + _fetcher.setCache(std::move(cache)); } SequenceCache::~SequenceCache() @@ -53,8 +49,7 @@ void SequenceCache::setSequence(const QVariantList& paths) _fetcher.setSequence(sequence); // Restart if needed - const bool isAsync = true; - setAsyncFetching(isAsync); + setAsyncFetching(true); } void SequenceCache::setResizeRatio(double ratio) @@ -88,7 +83,7 @@ void SequenceCache::setAsyncFetching(bool fetching) if (fetching) { - connect(&_fetcher, &AsyncFetcher::onAsyncFetchProgressed, this, &SequenceCache::onAsyncFetchProgressed); + connect(&_fetcher, &AsyncFetcher::onAsyncFetchProgressed, this, &SequenceCache::onAsyncFetchProgressed, Qt::UniqueConnection); _threadPool.start(&_fetcher); } } @@ -98,7 +93,7 @@ void SequenceCache::setPrefetching(bool prefetching) _fetcher.setPrefetching(prefetching); } -bool SequenceCache::getPrefetching() +bool SequenceCache::getPrefetching() const { return _fetcher.getPrefetching(); } @@ -109,7 +104,7 @@ QPointF SequenceCache::getRamInfo() const const auto memInfo = aliceVision::system::getMemoryInfo(); double availableRam = static_cast(memInfo.availableRam) / (1024. * 1024. * 1024.); - double contentSize = static_cast(_fetcher.getCacheSize()) / (1024. * 1024. * 1024. * 1024.); + double contentSize = static_cast(_fetcher.getCacheSize()) / (1024. * 1024. * 1024.); // Return in GB return QPointF(availableRam, contentSize); @@ -166,5 +161,3 @@ void SequenceCache::onAsyncFetchProgressed() } // namespace imgserve } // namespace qtAliceVision - -#include "SequenceCache.moc" diff --git a/src/qtAliceVision/SequenceCache.hpp b/src/qtAliceVision/SequenceCache.hpp index 91559a29..53affa30 100644 --- a/src/qtAliceVision/SequenceCache.hpp +++ b/src/qtAliceVision/SequenceCache.hpp @@ -61,7 +61,7 @@ class SequenceCache : public QObject, public ImageServer * @brief Get the boolean flag indicating if the sequence is performing prefetching (only if async). * @return true if prefetching */ - bool getPrefetching(); + bool getPrefetching() const; /** * @brief Get the maximum memory that can be filled by the cache. @@ -76,8 +76,8 @@ class SequenceCache : public QObject, public ImageServer void setMemoryLimit(std::size_t memory); /** - * @brief Get the maximum available RAM on the system. - * @return maximum available RAM in bytes + * @brief Get RAM usage information. + * @return QPointF where x is the available system RAM in GB and y is the current cache content size in GB */ QPointF getRamInfo() const; diff --git a/src/qtAliceVision/SingleImageLoader.cpp b/src/qtAliceVision/SingleImageLoader.cpp index 7652d1ca..628096ca 100644 --- a/src/qtAliceVision/SingleImageLoader.cpp +++ b/src/qtAliceVision/SingleImageLoader.cpp @@ -118,5 +118,3 @@ void SingleImageLoadingIORunnable::run() } // namespace imgserve } // namespace qtAliceVision - -#include "SingleImageLoader.moc" diff --git a/src/qtAliceVision/AttributeItemDelegate.frag b/src/qtAliceVision/shaders/AttributeItemDelegate.frag similarity index 100% rename from src/qtAliceVision/AttributeItemDelegate.frag rename to src/qtAliceVision/shaders/AttributeItemDelegate.frag diff --git a/src/qtAliceVision/FeaturesViewer.frag b/src/qtAliceVision/shaders/FeaturesViewer.frag similarity index 100% rename from src/qtAliceVision/FeaturesViewer.frag rename to src/qtAliceVision/shaders/FeaturesViewer.frag diff --git a/src/qtAliceVision/FeaturesViewer.vert b/src/qtAliceVision/shaders/FeaturesViewer.vert similarity index 100% rename from src/qtAliceVision/FeaturesViewer.vert rename to src/qtAliceVision/shaders/FeaturesViewer.vert diff --git a/src/qtAliceVision/FloatImageViewer.frag b/src/qtAliceVision/shaders/FloatImageViewer.frag similarity index 100% rename from src/qtAliceVision/FloatImageViewer.frag rename to src/qtAliceVision/shaders/FloatImageViewer.frag diff --git a/src/qtAliceVision/FloatImageViewer.vert b/src/qtAliceVision/shaders/FloatImageViewer.vert similarity index 100% rename from src/qtAliceVision/FloatImageViewer.vert rename to src/qtAliceVision/shaders/FloatImageViewer.vert diff --git a/src/qtAliceVision/ImageOverlay.frag b/src/qtAliceVision/shaders/ImageOverlay.frag similarity index 100% rename from src/qtAliceVision/ImageOverlay.frag rename to src/qtAliceVision/shaders/ImageOverlay.frag diff --git a/src/qtAliceVision/PhongImageViewer.frag b/src/qtAliceVision/shaders/PhongImageViewer.frag similarity index 100% rename from src/qtAliceVision/PhongImageViewer.frag rename to src/qtAliceVision/shaders/PhongImageViewer.frag diff --git a/src/qtAliceVision/PhongImageViewer.vert b/src/qtAliceVision/shaders/PhongImageViewer.vert similarity index 100% rename from src/qtAliceVision/PhongImageViewer.vert rename to src/qtAliceVision/shaders/PhongImageViewer.vert