#pragma once #include <QGLWidget> #include <QOpenGLWidget> #include <QThreadPool> #include <QMouseEvent> #include <QOpenGLContext> #include <QOpenGLFunctions> #include <QOpenGLFunctions_2_0> #include <QMutex> #include <QPainter> //#include <qopengl.h> //#include <qopenglfunctions.h> //#include <qopenglcontext.h> #include <Mandel.h> #include "Bitmap.h" #include "Gradient.h" #include <atomic> #include <tuple> #include <deque> #include <chrono> #include <unordered_map> #include <unordered_set> using GridIndex = mnd::Integer; Q_DECLARE_METATYPE(GridIndex) Q_DECLARE_METATYPE(mnd::Real) class MandelView; class MandelWidget; class Texture { GLuint id; public: QOpenGLFunctions_2_0& gl; Texture(QOpenGLFunctions_2_0& gl, const Bitmap<RGBColor>& pict, GLint param = GL_LINEAR); ~Texture(void); Texture(const Texture& other) = delete; Texture& operator=(const Texture& other) = delete; Texture(Texture&& other); Texture& operator=(Texture&& other); private: void bind(void) const; public: inline GLuint getId(void) const { return id; } void drawRect(float x, float y, float width, float height); }; class CellImage { public: CellImage(void) = default; CellImage(CellImage&& b) = default; CellImage(const CellImage& b) = delete; virtual ~CellImage(void); virtual void drawRect(float x, float y, float width, float height) = 0; virtual std::shared_ptr<CellImage> clip(short i, short j) = 0; virtual int getRecalcPriority(void) const = 0; }; class TextureClip : public CellImage { std::shared_ptr<Texture> texture; float tx, ty, tw, th; public: inline TextureClip(std::shared_ptr<Texture> tex, float tx, float ty, float tw, float th) : texture{ std::move(tex) }, tx{ tx }, ty{ ty }, tw{ tw }, th{ th } {} inline TextureClip(std::shared_ptr<Texture> tex) : TextureClip{ tex, 0.0f, 0.0f, 1.0f, 1.0f } {} TextureClip(TextureClip&&) = default; TextureClip(const TextureClip&) = delete; virtual ~TextureClip(void); void drawRect(float x, float y, float width, float height); TextureClip clip(float x, float y, float w, float h); std::shared_ptr<CellImage> clip(short i, short j); int getRecalcPriority(void) const; }; class QuadImage : public CellImage { std::shared_ptr<CellImage> cells[2][2]; public: inline QuadImage(std::shared_ptr<CellImage> i00, std::shared_ptr<CellImage> i01, std::shared_ptr<CellImage> i10, std::shared_ptr<CellImage> i11) : cells{ { std::move(i00), std::move(i01) }, { std::move(i10), std::move(i11) } } {} QuadImage(QuadImage&&) = default; QuadImage(const QuadImage&) = delete; virtual ~QuadImage(void); void drawRect(float x, float y, float width, float height); std::shared_ptr<CellImage> clip(short i, short j); int getRecalcPriority(void) const; }; struct PairHash { template <typename T1, typename T2> std::size_t operator () (const std::pair<T1, T2>& p) const { auto h1 = std::hash<T1>{}(p.first); auto h2 = std::hash<T2>{}(p.second); //boost::hash_combine(h1, p.second); return (h1 ^ 234579245) * 23452354 + h2; } }; struct TripleHash { template <typename T1, typename T2, typename T3> std::size_t operator () (const std::tuple<T1, T2, T3>& p) const { auto h1 = std::hash<T1>{}(std::get<0>(p)); auto h2 = std::hash<T2>{}(std::get<1>(p)); auto h3 = std::hash<T3>{}(std::get<2>(p)); return (((h1 ^ 234579245) * 23452357 + h2) ^ 2345244345) * 23421 + h3; } }; struct GridElement { bool enoughResolution; std::shared_ptr<CellImage> img; inline GridElement(bool enoughResolution, std::shared_ptr<CellImage> img) : enoughResolution{ enoughResolution }, img{ std::move(img) } {} }; class TexGrid { public: MandelView& owner; int level; mnd::Real dpp; std::unordered_map<std::pair<GridIndex, GridIndex>, std::unique_ptr<GridElement>, PairHash> cells; public: //inline TexGrid(MandelV& owner) : level{ 1.0 }, owner{ owner } {} TexGrid(MandelView& owner, int level); std::pair<GridIndex, GridIndex> getCellIndices(mnd::Real x, mnd::Real y); std::pair<mnd::Real, mnd::Real> getPositions(GridIndex i, GridIndex j); GridElement* getCell(GridIndex i, GridIndex j); void setCell(GridIndex i, GridIndex j, std::unique_ptr<GridElement> tex); inline size_t countAllocatedCells(void) const { return cells.size(); } void clearCells(void); void clearUncleanCells(void); }; class Job : public QObject, public QRunnable { Q_OBJECT public: mnd::MandelGenerator* generator; const Gradient& gradient; MandelWidget& owner; TexGrid* grid; int level; GridIndex i, j; long calcState = 0; inline Job(mnd::MandelGenerator* generator, const Gradient& gradient, MandelWidget& owner, TexGrid* grid, int level, GridIndex i, GridIndex j, long calcState) : generator{ generator }, gradient{ gradient }, owner{ owner }, grid{ grid }, level{ level }, i{ i }, j{ j }, calcState{ calcState } {} void run() override; signals: void done(int level, GridIndex i, GridIndex j, long calcState, Bitmap<RGBColor>* bmp); }; class Calcer : public QObject { Q_OBJECT /// tuple contains level, i, j of the job std::unordered_map<std::tuple<int, GridIndex, GridIndex>, Job*, TripleHash> jobs; QMutex jobsMutex; mnd::MandelGenerator* generator; std::unique_ptr<QThreadPool> threadPool; MandelWidget& owner; const Gradient& gradient; int currentLevel; volatile unsigned int calcState = 0; public: Calcer(mnd::MandelGenerator* generator, MandelWidget& owner); void clearAll(void); void setGenerator(mnd::MandelGenerator* generator) { this->generator = generator; changeState(); } inline void changeState(void) { calcState++; } public slots: void calc(TexGrid& grid, int level, GridIndex i, GridIndex j, int priority); void setCurrentLevel(int level); void notFinished(int level, GridIndex i, GridIndex j); void redirect(int level, GridIndex i, GridIndex j, long calcState, Bitmap<RGBColor>* bmp); signals: void done(int level, GridIndex i, GridIndex j, Bitmap<RGBColor>* bmp); }; class MandelView : public QObject { Q_OBJECT public: std::unique_ptr<Texture> empty; // a grid should not be deleted once constructed. // to free up memory one can call TexGrid::clearCells() std::unordered_map<int, TexGrid> levels; mnd::MandelGenerator* generator; Calcer calcer; MandelWidget& owner; int width; int height; public: static const int chunkSize; MandelView(mnd::MandelGenerator* generator, MandelWidget& owner); int getLevel(mnd::Real dpp); mnd::Real getDpp(int level); TexGrid& getGrid(int level); void setGenerator(mnd::MandelGenerator* generator); void clearCells(void); void garbageCollect(int level, GridIndex i, GridIndex j); GridElement* searchAbove(int level, GridIndex i, GridIndex j, int recursionLevel); GridElement* searchUnder(int level, GridIndex i, GridIndex j, int recursionLevel); void paint(const mnd::MandelViewport& mvp, QPainter& qp); public slots: void cellReady(int level, GridIndex i, GridIndex j, Bitmap<RGBColor>* bmp); signals: void redrawRequested(void); }; class MandelWidget : public QOpenGLWidget { Q_OBJECT private: mnd::MandelContext& mndContext; mnd::MandelGenerator* generator; mnd::MandelInfo mandelInfo; bool initialized = false; Gradient gradient; volatile bool selectingPoint = false; float pointX; float pointY; volatile bool rubberbanding = false; QRectF rubberband; volatile bool dragging = false; int dragX, dragY; bool displayInfo = false; mnd::MandelViewport currentViewport; mnd::MandelViewport targetViewport; std::chrono::time_point<std::chrono::high_resolution_clock> lastAnimUpdate; std::unique_ptr<MandelView> mandelView; public: MandelWidget(mnd::MandelContext& ctxt, mnd::MandelGenerator* generator, QWidget* parent = nullptr); ~MandelWidget(void) override; inline const Gradient& getGradient(void) const { return gradient; } void setGradient(Gradient g); inline bool getSmoothColoring(void) const { return mandelInfo.smooth; } void setSmoothColoring(bool sc); inline bool doesDisplayInfo(void) const { return displayInfo; } void setDisplayInfo(bool di); inline int getMaxIterations(void) const { return mandelInfo.maxIter; } void setMaxIterations(int maxIter); inline const mnd::MandelInfo& getMandelInfo(void) const { return mandelInfo; } inline mnd::MandelInfo& getMandelInfo(void) { return mandelInfo; } void setJuliaPos(const mnd::Real& x, const mnd::Real& y); const mnd::Real& getJuliaX(void) { return mandelInfo.juliaX; } const mnd::Real& getJuliaY(void) { return mandelInfo.juliaY; } inline mnd::MandelGenerator* getGenerator(void) const { return generator; } void setGenerator(mnd::MandelGenerator* generator); void clearAll(void); void initializeGL(void) override; void resizeGL(int w, int h) override; void paintGL() override; private: void updateAnimations(void); void drawRubberband(void); void drawInfo(void); void drawPoint(void); public: void zoom(float scale, float x = 0.5f, float y = 0.5f); void setViewport(const mnd::MandelViewport& viewport); void selectPoint(void); void stopSelectingPoint(void); void requestRecalc(void); void resizeEvent(QResizeEvent* re) override; void mousePressEvent(QMouseEvent* me) override; void mouseMoveEvent(QMouseEvent* me) override; void mouseReleaseEvent(QMouseEvent* me) override; void wheelEvent(QWheelEvent * we) override; inline const mnd::MandelViewport& getViewport(void) const { return targetViewport; } signals: void needsUpdate(const mnd::MandelInfo vp); void pointSelected(mnd::Real x, mnd::Real y); };