From be74509b31b26e714c6b380e17ed3935ec0b6cf0 Mon Sep 17 00:00:00 2001 From: Andrei Karas Date: Thu, 12 Jun 2014 23:23:00 +0300 Subject: In modernopengl add support for buffered draw tile collection. This is first cached function implemented in modernopengl. Also add support for bind buffer attributes. Add finalize method to convert any cached data into data for GPU. --- src/graphicsvertexes.cpp | 6 ++ src/graphicsvertexes.h | 2 + src/gui/widgets/avatarlistbox.cpp | 1 + src/gui/widgets/button.cpp | 1 + src/gui/widgets/emotepage.cpp | 1 + src/gui/widgets/popup.cpp | 3 +- src/gui/widgets/progressbar.cpp | 1 + src/gui/widgets/scrollarea.cpp | 3 + src/gui/widgets/shortcutcontainer.cpp | 1 + src/gui/widgets/slider.cpp | 1 + src/gui/widgets/tabs/tab.cpp | 1 + src/gui/widgets/window.cpp | 1 + src/gui/windows/equipmentwindow.cpp | 1 + src/render/graphics.h | 6 ++ src/render/modernopenglgraphics.cpp | 185 ++++++++++++++++++++++++++++++++++ src/render/modernopenglgraphics.h | 12 +++ src/resources/map/maplayer.cpp | 3 +- src/resources/map/maplayer.h | 2 +- src/test/testlauncher.cpp | 4 +- src/touchmanager.cpp | 1 + 20 files changed, 232 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/graphicsvertexes.cpp b/src/graphicsvertexes.cpp index 7b3e66f98..297762146 100644 --- a/src/graphicsvertexes.cpp +++ b/src/graphicsvertexes.cpp @@ -20,6 +20,8 @@ #include "graphicsvertexes.h" +#include "render/mgl.h" + #include "utils/dtor.h" #include "debug.h" @@ -57,6 +59,7 @@ OpenGLGraphicsVertexes::OpenGLGraphicsVertexes() : mShortVertPool.reserve(30); mIntTexPool.reserve(30); mVp.reserve(30); + mVbo.reserve(30); } OpenGLGraphicsVertexes::~OpenGLGraphicsVertexes() @@ -94,6 +97,9 @@ void OpenGLGraphicsVertexes::clear() } mIntTexPool.clear(); + mglDeleteBuffers(mVbo.size(), &mVbo[0]); + mVbo.clear(); + mVp.clear(); if (ptr) { diff --git a/src/graphicsvertexes.h b/src/graphicsvertexes.h index 84597ad73..2acd7ae06 100644 --- a/src/graphicsvertexes.h +++ b/src/graphicsvertexes.h @@ -124,6 +124,7 @@ class OpenGLGraphicsVertexes final std::vector mIntVertPool; std::vector mShortVertPool; std::vector mIntTexPool; + std::vector mVbo; }; #endif @@ -146,6 +147,7 @@ class ImageVertexes final }; typedef std::vector ImageVertexesVector; +typedef ImageVertexesVector::iterator ImageCollectionIter; typedef ImageVertexesVector::const_iterator ImageCollectionCIter; class ImageCollection final diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp index 9a7ed36dc..07115640b 100644 --- a/src/gui/widgets/avatarlistbox.cpp +++ b/src/gui/widgets/avatarlistbox.cpp @@ -139,6 +139,7 @@ void AvatarListBox::draw(Graphics *graphics) { graphics->calcTileCollection(&vertexes, icon, mImagePadding, y + mPadding); + graphics->finalize(&vertexes); } else { diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp index 54d15391b..de2bd0c8b 100644 --- a/src/gui/widgets/button.cpp +++ b/src/gui/widgets/button.cpp @@ -586,6 +586,7 @@ void Button::draw(Graphics *graphics) imageX, imageY); } } + graphics->finalize(mVertexes2); } graphics->drawTileCollection(mVertexes2); } diff --git a/src/gui/widgets/emotepage.cpp b/src/gui/widgets/emotepage.cpp index 68d508a5a..2c20cf438 100644 --- a/src/gui/widgets/emotepage.cpp +++ b/src/gui/widgets/emotepage.cpp @@ -92,6 +92,7 @@ void EmotePage::draw(Graphics *graphics) } } } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); } diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp index ff6296d75..506b0f050 100644 --- a/src/gui/widgets/popup.cpp +++ b/src/gui/widgets/popup.cpp @@ -106,7 +106,8 @@ void Popup::draw(Graphics *graphics) mDimension.width, mDimension.height, mSkin->getBorder()); } - + // need cache or remove calc call. + graphics->finalize(mVertexes); graphics->drawTileCollection(mVertexes); } else diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp index 9a10eedb7..8368ce71b 100644 --- a/src/gui/widgets/progressbar.cpp +++ b/src/gui/widgets/progressbar.cpp @@ -226,6 +226,7 @@ void ProgressBar::render(Graphics *graphics) width, mDimension.height - pad, mFillRect); } } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); diff --git a/src/gui/widgets/scrollarea.cpp b/src/gui/widgets/scrollarea.cpp index fc7ae91d5..028c2e7b1 100644 --- a/src/gui/widgets/scrollarea.cpp +++ b/src/gui/widgets/scrollarea.cpp @@ -320,6 +320,7 @@ void ScrollArea::draw(Graphics *graphics) { if (!mOpaque) updateCalcFlag(graphics); + // need add caching or remove calc calls. // if (mRedraw) { mVertexes->clear(); @@ -344,6 +345,7 @@ void ScrollArea::draw(Graphics *graphics) calcHBar(graphics); calcHMarker(graphics); } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); } @@ -436,6 +438,7 @@ void ScrollArea::drawFrame(Graphics *graphics) 0, 0, w, h, background); + graphics->finalize(mVertexes2); } graphics->drawTileCollection(mVertexes2); } diff --git a/src/gui/widgets/shortcutcontainer.cpp b/src/gui/widgets/shortcutcontainer.cpp index 313e37262..eeca4dacd 100644 --- a/src/gui/widgets/shortcutcontainer.cpp +++ b/src/gui/widgets/shortcutcontainer.cpp @@ -134,6 +134,7 @@ void ShortcutContainer::drawBackground(Graphics *g) (i % mGridWidth) * mBoxWidth, (i / mGridWidth) * mBoxHeight); } + g->finalize(mVertexes); } g->drawTileCollection(mVertexes); } diff --git a/src/gui/widgets/slider.cpp b/src/gui/widgets/slider.cpp index 6c2224150..e36c24b2c 100644 --- a/src/gui/widgets/slider.cpp +++ b/src/gui/widgets/slider.cpp @@ -282,6 +282,7 @@ void Slider::draw(Graphics *graphics) (mDimension.height - img->getHeight()) / 2); } } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); } diff --git a/src/gui/widgets/tabs/tab.cpp b/src/gui/widgets/tabs/tab.cpp index 16018e5c3..465a901c9 100644 --- a/src/gui/widgets/tabs/tab.cpp +++ b/src/gui/widgets/tabs/tab.cpp @@ -278,6 +278,7 @@ void Tab::draw(Graphics *graphics) padding); } } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp index 74c6d1cfc..caf7dae91 100644 --- a/src/gui/widgets/window.cpp +++ b/src/gui/widgets/window.cpp @@ -313,6 +313,7 @@ void Window::draw(Graphics *graphics) mGripRect.x, mGripRect.y); } + graphics->finalize(mVertexes); } else { diff --git a/src/gui/windows/equipmentwindow.cpp b/src/gui/windows/equipmentwindow.cpp index 22d7a8567..7ec505160 100644 --- a/src/gui/windows/equipmentwindow.cpp +++ b/src/gui/windows/equipmentwindow.cpp @@ -196,6 +196,7 @@ void EquipmentWindow::draw(Graphics *graphics) } i ++; } + graphics->finalize(mVertexes); } graphics->drawTileCollection(mVertexes); } diff --git a/src/render/graphics.h b/src/render/graphics.h index 9b63ba6c6..2b7ce188d 100644 --- a/src/render/graphics.h +++ b/src/render/graphics.h @@ -454,6 +454,12 @@ class Graphics notfinal virtual void postInit() { } + virtual void finalize(ImageCollection *const col) + { } + + virtual void finalize(ImageVertexes *const vert) + { } + int mWidth; int mHeight; int mActualWidth; diff --git a/src/render/modernopenglgraphics.cpp b/src/render/modernopenglgraphics.cpp index 11fda1b41..b2e33cbc9 100644 --- a/src/render/modernopenglgraphics.cpp +++ b/src/render/modernopenglgraphics.cpp @@ -99,6 +99,7 @@ ModernOpenGLGraphics::ModernOpenGLGraphics() : mVao(0U), mVbo(0U), mVboCached(0U), + mAttributesCached(0U), mColorAlpha(false), mTextureDraw(false), #ifdef DEBUG_BIND_TEXTURE @@ -168,6 +169,7 @@ void ModernOpenGLGraphics::postInit() mglBindVertexBuffer(0, mVbo, 0, 4 * sizeof(GLfloat)); mglVertexAttribBinding(mPosAttrib, 0); + mAttributesCached = mVbo; screenResized(); } @@ -323,6 +325,7 @@ bool ModernOpenGLGraphics::drawImageInline(const Image *const image, #endif bindTexture(GL_TEXTURE_2D, image->mGLImage); setTexturingAndBlending(true); + bindArrayBufferAndAttributes(mVbo); setColorAlpha(image->mAlpha); const ClipRect &clipArea = mClipStack.top(); @@ -369,6 +372,7 @@ bool ModernOpenGLGraphics::drawRescaledImage(const Image *const image, #endif bindTexture(OpenGLImageHelper::mTextureType, image->mGLImage); setTexturingAndBlending(true); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); // Draw a textured quad. @@ -416,6 +420,7 @@ void ModernOpenGLGraphics::drawPatternInline(const Image *const image, bindTexture(OpenGLImageHelper::mTextureType, image->mGLImage); setTexturingAndBlending(true); + bindArrayBufferAndAttributes(mVbo); setColorAlpha(image->mAlpha); unsigned int vp = 0; @@ -478,6 +483,7 @@ void ModernOpenGLGraphics::drawRescaledPattern(const Image *const image, bindTexture(OpenGLImageHelper::mTextureType, image->mGLImage); setTexturingAndBlending(true); + bindArrayBufferAndAttributes(mVbo); setColorAlpha(image->mAlpha); unsigned int vp = 0; @@ -531,6 +537,26 @@ void ModernOpenGLGraphics::drawRescaledPattern(const Image *const image, inline void ModernOpenGLGraphics::drawVertexes(const OpenGLGraphicsVertexes &ogl) { + const std::vector &vp = ogl.mVp; + const std::vector &vbos = ogl.mVbo; + std::vector::const_iterator ivp; + std::vector::const_iterator ivbo; + const std::vector::const_iterator ivp_end = vp.end(); + + logger->log("drawVertexes start"); + for (ivp = vp.begin(), ivbo = vbos.begin(); + ivp != ivp_end; + ++ ivp, ++ ivbo) + { + logger->log("bind vbo: %d", *ivbo); + logger->log("buf size in vetexes: %d", (*ivp) / 4); + bindArrayBufferAndAttributes(*ivbo); +#ifdef DEBUG_DRAW_CALLS + mDrawCalls ++; +#endif + glDrawArrays(GL_TRIANGLES, 0, *ivp / 4); + } + logger->log("drawVertexes end"); } void ModernOpenGLGraphics::calcPattern(ImageVertexes *const vert, @@ -546,7 +572,59 @@ void ModernOpenGLGraphics::calcPatternInline(ImageVertexes *const vert, const int x, const int y, const int w, const int h) const { + if (!image || !vert) + return; + + const SDL_Rect &imageRect = image->mBounds; + const int srcX = imageRect.x; + const int srcY = imageRect.y; + const int iw = imageRect.w; + const int ih = imageRect.h; + + if (iw == 0 || ih == 0) + return; + + const float tw = static_cast(image->mTexWidth); + const float th = static_cast(image->mTexHeight); + const ClipRect &clipArea = mClipStack.top(); + const int x2 = x + clipArea.xOffset; + const int y2 = y + clipArea.yOffset; + + const unsigned int vLimit = mMaxVertices * 4; + + OpenGLGraphicsVertexes &ogl = vert->ogl; + unsigned int vp = ogl.continueVp(); + const float texX1 = static_cast(srcX) / tw; + const float texY1 = static_cast(srcY) / th; + + GLfloat *floatArray = ogl.continueFloatTexArray(); + + for (int py = 0; py < h; py += ih) + { + const int height = (py + ih >= h) ? h - py : ih; + const int dstY = y2 + py; + const float texY2 = static_cast(srcY + height) / th; + for (int px = 0; px < w; px += iw) + { + const int width = (px + iw >= w) ? w - px : iw; + const int dstX = x2 + px; + const float texX2 = static_cast(srcX + width) / tw; + + vertFill2D(floatArray, + texX1, texY1, texX2, texY2, + dstX, dstY, width, height); + + vp += 24; + if (vp >= vLimit) + { + floatArray = ogl.switchFloatTexArray(); + ogl.switchVp(vp); + vp = 0; + } + } + } + ogl.switchVp(vp); } void ModernOpenGLGraphics::calcTileCollection(ImageCollection *const vertCol, @@ -571,6 +649,22 @@ void ModernOpenGLGraphics::calcTileCollection(ImageCollection *const vertCol, void ModernOpenGLGraphics::drawTileCollection(const ImageCollection *const vertCol) { + setTexturingAndBlending(true); +// bindArrayBuffer(vbo); + const ImageVertexesVector &draws = vertCol->draws; + const ImageCollectionCIter it_end = draws.end(); + for (ImageCollectionCIter it = draws.begin(); it != it_end; ++ it) + { + const ImageVertexes *const vert = *it; + const Image *const image = vert->image; + + setColorAlpha(image->mAlpha); +#ifdef DEBUG_BIND_TEXTURE + debugBindTexture(image); +#endif + bindTexture(OpenGLImageHelper::mTextureType, image->mGLImage); + drawVertexes(vert->ogl); + } } void ModernOpenGLGraphics::calcPattern(ImageCollection* const vertCol, @@ -774,6 +868,7 @@ void ModernOpenGLGraphics::popClipArea() void ModernOpenGLGraphics::drawPoint(int x, int y) { setTexturingAndBlending(false); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); GLfloat vertices[] = { @@ -790,6 +885,7 @@ void ModernOpenGLGraphics::drawPoint(int x, int y) void ModernOpenGLGraphics::drawLine(int x1, int y1, int x2, int y2) { setTexturingAndBlending(false); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); GLfloat vertices[] = { @@ -807,6 +903,7 @@ void ModernOpenGLGraphics::drawLine(int x1, int y1, int x2, int y2) void ModernOpenGLGraphics::drawRectangle(const Rect& rect) { setTexturingAndBlending(false); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); const int x1 = rect.x + clipArea.xOffset; const int y1 = rect.y + clipArea.yOffset; @@ -831,6 +928,7 @@ void ModernOpenGLGraphics::drawRectangle(const Rect& rect) void ModernOpenGLGraphics::fillRectangle(const Rect& rect) { setTexturingAndBlending(false); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); const int x1 = rect.x + clipArea.xOffset; const int y1 = rect.y + clipArea.yOffset; @@ -900,6 +998,7 @@ bool ModernOpenGLGraphics::drawNet(const int x1, const int y1, const unsigned int vLimit = mMaxVertices * 4; setTexturingAndBlending(false); + bindArrayBufferAndAttributes(mVbo); const ClipRect &clipArea = mClipStack.top(); const GLfloat dx = clipArea.xOffset; const GLfloat dy = clipArea.yOffset; @@ -971,6 +1070,36 @@ void ModernOpenGLGraphics::bindArrayBuffer(const GLuint vbo) { mVboCached = vbo; mglBindBuffer(GL_ARRAY_BUFFER, vbo); + mAttributesCached = 0U; + } +} + +void ModernOpenGLGraphics::bindArrayBufferAndAttributes(const GLuint vbo) +{ + if (mVboCached != vbo) + { + mVboCached = vbo; + mglBindBuffer(GL_ARRAY_BUFFER, vbo); + + mAttributesCached = mVboCached; + mglBindVertexBuffer(0, mVboCached, 0, 4 * sizeof(GLfloat)); +// mglVertexAttribBinding(mPosAttrib, 0); + } + else if (mAttributesCached != mVboCached) + { + mAttributesCached = mVboCached; + mglBindVertexBuffer(0, mVboCached, 0, 4 * sizeof(GLfloat)); +// mglVertexAttribBinding(mPosAttrib, 0); + } +} + +void ModernOpenGLGraphics::bindAttributes() +{ + if (mAttributesCached != mVboCached) + { + mAttributesCached = mVboCached; + mglBindVertexBuffer(0, mVboCached, 0, 4 * sizeof(GLfloat)); +// mglVertexAttribBinding(mPosAttrib, 0); } } @@ -1013,6 +1142,51 @@ void ModernOpenGLGraphics::clearScreen() const glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } +void ModernOpenGLGraphics::finalize(ImageCollection *const col) +{ + FOR_EACH (ImageCollectionIter, it, col->draws) + finalize(*it); +} + +void ModernOpenGLGraphics::finalize(ImageVertexes *const vert) +{ + // in future need convert in each switchVp/continueVp + + OpenGLGraphicsVertexes &ogl = vert->ogl; + const std::vector &vp = ogl.mVp; + std::vector::const_iterator ivp; + const std::vector::const_iterator ivp_end = vp.end(); + std::vector &floatTexPool = ogl.mFloatTexPool; + std::vector::const_iterator ft; + const std::vector::const_iterator ft_end = floatTexPool.end(); + std::vector &vbos = ogl.mVbo; + std::vector::const_iterator ivbo; + + const int sz = floatTexPool.size(); + vbos.resize(sz); + mglGenBuffers(sz, &vbos[0]); + + logger->log("finalize start"); + for (ft = floatTexPool.begin(), ivp = vp.begin(), ivbo = vbos.begin(); + ft != ft_end && ivp != ivp_end; + ++ ft, ++ ivp, ++ ivbo) + { + logger->log("bind vbo: %d", *ivbo); + logger->log("buf size in vetexes: %d", *ivp / 4); + bindArrayBuffer(*ivbo); + mglBufferData(GL_ARRAY_BUFFER, (*ivp) * sizeof(GLfloat), + *ft, GL_DYNAMIC_DRAW); + } + + for (std::vector::iterator it = floatTexPool.begin(); + it != floatTexPool.end(); ++ it) + { + delete [] (*it); + } + floatTexPool.clear(); + logger->log("finalize end"); +} + void ModernOpenGLGraphics::drawTriangleArray(const int size) { mglBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), @@ -1023,6 +1197,17 @@ void ModernOpenGLGraphics::drawTriangleArray(const int size) glDrawArrays(GL_TRIANGLES, 0, size / 4); } +void ModernOpenGLGraphics::drawTriangleArray(const GLfloat *const array, + const int size) +{ + mglBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), + array, GL_DYNAMIC_DRAW); +#ifdef DEBUG_DRAW_CALLS + mDrawCalls ++; +#endif + glDrawArrays(GL_TRIANGLES, 0, size / 4); +} + void ModernOpenGLGraphics::drawLineArrays(const int size) { mglBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), diff --git a/src/render/modernopenglgraphics.h b/src/render/modernopenglgraphics.h index 651a9462f..d3523ea65 100644 --- a/src/render/modernopenglgraphics.h +++ b/src/render/modernopenglgraphics.h @@ -66,6 +66,10 @@ class ModernOpenGLGraphics final : public Graphics void screenResized(); + void finalize(ImageCollection *const col) override final; + + void finalize(ImageVertexes *const vert) override final; + #include "render/graphicsdef.hpp" #include "render/openglgraphicsdef.hpp" @@ -87,10 +91,17 @@ class ModernOpenGLGraphics final : public Graphics inline void drawTriangleArray(const int size); + inline void drawTriangleArray(const GLfloat *const array, + const int size); + inline void drawLineArrays(const int size); inline void bindArrayBuffer(const GLuint vbo); + inline void bindArrayBufferAndAttributes(const GLuint vbo); + + inline void bindAttributes(); + GLfloat *mFloatArray; GLfloat *mFloatArrayCached; ShaderProgram *mProgram; @@ -108,6 +119,7 @@ class ModernOpenGLGraphics final : public Graphics GLuint mVao; GLuint mVbo; GLuint mVboCached; + GLuint mAttributesCached; bool mColorAlpha; bool mTextureDraw; #ifdef DEBUG_BIND_TEXTURE diff --git a/src/resources/map/maplayer.cpp b/src/resources/map/maplayer.cpp index 51e076e81..c1f1f2d7a 100644 --- a/src/resources/map/maplayer.cpp +++ b/src/resources/map/maplayer.cpp @@ -246,7 +246,7 @@ void MapLayer::updateSDL(const Graphics *const graphics, BLOCK_END("MapLayer::updateSDL") } -void MapLayer::updateOGL(const Graphics *const graphics, +void MapLayer::updateOGL(Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY, @@ -323,6 +323,7 @@ void MapLayer::updateOGL(const Graphics *const graphics, } } } + graphics->finalize(imgVert); BLOCK_END("MapLayer::updateOGL") } diff --git a/src/resources/map/maplayer.h b/src/resources/map/maplayer.h index fc1fe611d..e82aa5870 100644 --- a/src/resources/map/maplayer.h +++ b/src/resources/map/maplayer.h @@ -96,7 +96,7 @@ class MapLayer final: public ConfigListener #ifdef USE_OPENGL void drawOGL(Graphics *const graphics); - void updateOGL(const Graphics *const graphics, + void updateOGL(Graphics *const graphics, int startX, int startY, int endX, int endY, const int scrollX, const int scrollY, diff --git a/src/test/testlauncher.cpp b/src/test/testlauncher.cpp index 25cb639a4..86d2be370 100644 --- a/src/test/testlauncher.cpp +++ b/src/test/testlauncher.cpp @@ -396,13 +396,15 @@ int TestLauncher::testDraw() mainGraphics->drawPattern(img[0], 10, 400, 300, 180); mainGraphics->calcPattern(col, img[1], 500, 400, 150, 100); - mainGraphics->drawTileCollection(col); + mainGraphics->finalize(col); mainGraphics->drawRescaledImage(img[0], 250, 350, 35, 90); mainGraphics->setColor(Color(0x00U, 0xFFU, 0x00U, 0x90U)); mainGraphics->drawNet(450, 10, 600, 300, 32, 20); + mainGraphics->drawTileCollection(col); + img[0]->setAlpha(0.3f); mainGraphics->drawRescaledPattern(img[0], 250, 150, 250, 300, 30, 100); diff --git a/src/touchmanager.cpp b/src/touchmanager.cpp index adf1e4809..c4f1c59d2 100644 --- a/src/touchmanager.cpp +++ b/src/touchmanager.cpp @@ -220,6 +220,7 @@ void TouchManager::draw() } } } + mainGraphics->finalize(mVertexes); } mainGraphics->drawTileCollection(mVertexes); } -- cgit v1.2.3-60-g2f50