From c66bf1689ac1ca3a7e6106956c8a585c88640a34 Mon Sep 17 00:00:00 2001 From: Duncan Ogilvie Date: Tue, 14 Dec 2021 23:21:36 +0100 Subject: [PATCH] Improve rich text painting rendering performance (hopefully) --- .gitignore | 1 + src/gui/Src/BasicView/Disassembly.cpp | 376 ++++++++++++++++++------ src/gui/Src/BasicView/Disassembly.h | 29 ++ src/gui/Src/Disassembler/QBeaEngine.cpp | 1 + src/gui/Src/Utils/RichTextPainter.h | 1 + 5 files changed, 323 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index 53181730..2e992fd6 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ build-wrapper* My Amplifier Results - */ My Inspector Results - */ /bin/x96dbg.ini +/src/gui/msvc_make.bat diff --git a/src/gui/Src/BasicView/Disassembly.cpp b/src/gui/Src/BasicView/Disassembly.cpp index ef030283..cf65ccd3 100644 --- a/src/gui/Src/BasicView/Disassembly.cpp +++ b/src/gui/Src/BasicView/Disassembly.cpp @@ -127,6 +127,8 @@ void Disassembly::updateFonts() { setFont(ConfigFont("Disassembly")); invalidateCachedFont(); + mTextLayout.setFont(font()); + mTextLayout.setCacheEnabled(true); } void Disassembly::updateConfigSlot() @@ -208,15 +210,21 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse switch(col) { - case 0: // Draw address (+ label) + case ColAddress: // Draw address (+ label) { + RichTextPainter::CustomRichText_t richText; + richText.underline = false; + richText.textColor = mTextColor; + richText.flags = RichTextPainter::FlagColor; + char label[MAX_LABEL_SIZE] = ""; QString addrText = getAddrText(cur_addr, label); + richText.text = addrText; BPXTYPE bpxtype = DbgGetBpxTypeAt(cur_addr); bool isbookmark = DbgGetBookmarkAt(cur_addr); if(rvaToVa(mInstBuffer.at(rowOffset).rva) == mCipVa && !Bridge::getBridge()->mIsRunning && DbgMemFindBaseAddr(DbgValFromString("cip"), nullptr)) //cip + not running + valid cip { - painter->fillRect(QRect(x, y, w, h), QBrush(mCipBackgroundColor)); + richText.textBackground = mCipBackgroundColor; if(!isbookmark) //no bookmark { if(bpxtype & bp_normal) //normal breakpoint @@ -226,7 +234,7 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse bpColor = mBreakpointColor; if(bpColor == mCipBackgroundColor) bpColor = mCipColor; - painter->setPen(bpColor); + richText.textColor = bpColor; } else if(bpxtype & bp_hardware) //hardware breakpoint only { @@ -235,11 +243,11 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse hwbpColor = mHardwareBreakpointColor; if(hwbpColor == mCipBackgroundColor) hwbpColor = mCipColor; - painter->setPen(hwbpColor); + richText.textColor = hwbpColor; } else //no breakpoint { - painter->setPen(mCipColor); + richText.textColor = mCipColor; } } else //bookmark @@ -249,7 +257,7 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse bookmarkColor = mBookmarkColor; if(bookmarkColor == mCipBackgroundColor) bookmarkColor = mCipColor; - painter->setPen(bookmarkColor); + richText.textColor = bookmarkColor; } } else //non-cip address @@ -260,25 +268,25 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse { if(bpxtype == bp_none) //label only : fill label background { - painter->setPen(mLabelColor); //red -> address + label text - painter->fillRect(QRect(x, y, w, h), QBrush(mLabelBackgroundColor)); //fill label background + richText.textColor = mLabelColor; + richText.textBackground = mLabelBackgroundColor; } else //label + breakpoint { if(bpxtype & bp_normal) //label + normal breakpoint { - painter->setPen(mBreakpointColor); - painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + richText.textColor = mBreakpointColor; + richText.textBackground = mBreakpointBackgroundColor; } else if(bpxtype & bp_hardware) //label + hardware breakpoint only { - painter->setPen(mHardwareBreakpointColor); - painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill ? + richText.textColor = mHardwareBreakpointColor; + richText.textBackground = mHardwareBreakpointBackgroundColor; } else //other cases -> do as normal { - painter->setPen(mLabelColor); //red -> address + label text - painter->fillRect(QRect(x, y, w, h), QBrush(mLabelBackgroundColor)); //fill label background + richText.textColor = mLabelColor; + richText.textBackground = mLabelBackgroundColor; } } } @@ -286,47 +294,41 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse { if(bpxtype == bp_none) //no label, no breakpoint { - QColor background; if(wIsSelected) { - background = mSelectedAddressBackgroundColor; - painter->setPen(mSelectedAddressColor); //black address (DisassemblySelectedAddressColor) + richText.textColor = mSelectedAddressColor; + richText.textBackground = mSelectedAddressBackgroundColor; } else { - background = mAddressBackgroundColor; - painter->setPen(mAddressColor); //DisassemblyAddressColor + richText.textColor = mAddressColor; + richText.textBackground = mAddressBackgroundColor; } - if(background.alpha()) - painter->fillRect(QRect(x, y, w, h), QBrush(background)); //fill background } else //breakpoint only { if(bpxtype & bp_normal) //normal breakpoint { - painter->setPen(mBreakpointColor); - painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + richText.textColor = mBreakpointColor; + richText.textBackground = mBreakpointBackgroundColor; } else if(bpxtype & bp_hardware) //hardware breakpoint only { - painter->setPen(mHardwareBreakpointColor); - painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill red + richText.textColor = mHardwareBreakpointColor; + richText.textBackground = mHardwareBreakpointBackgroundColor; } else //other cases (memory breakpoint in disassembly) -> do as normal { - QColor background; if(wIsSelected) { - background = mSelectedAddressBackgroundColor; - painter->setPen(mSelectedAddressColor); //black address (DisassemblySelectedAddressColor) + richText.textColor = mSelectedAddressColor; + richText.textBackground = mSelectedAddressBackgroundColor; } else { - background = mAddressBackgroundColor; - painter->setPen(mAddressColor); + richText.textColor = mAddressColor; + richText.textBackground = mAddressBackgroundColor; } - if(background.alpha()) - painter->fillRect(QRect(x, y, w, h), QBrush(background)); //fill background } } } @@ -337,22 +339,22 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse { if(bpxtype == bp_none) //label + bookmark { - painter->setPen(mLabelColor); //red -> address + label text - painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill label background + richText.textColor = mLabelColor; + richText.textBackground = mBookmarkBackgroundColor; } else //label + breakpoint + bookmark { QColor color = mBookmarkBackgroundColor; if(!color.alpha()) //we don't want transparent text color = mAddressColor; - painter->setPen(color); + richText.textColor = color; if(bpxtype & bp_normal) //label + bookmark + normal breakpoint { - painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + richText.textBackground = mBreakpointBackgroundColor; } else if(bpxtype & bp_hardware) //label + bookmark + hardware breakpoint only { - painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill ? + richText.textBackground = mHardwareBreakpointBackgroundColor; } } } @@ -360,37 +362,45 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse { if(bpxtype == bp_none) //bookmark only { - painter->setPen(mBookmarkColor); //black address - painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill bookmark color + richText.textColor = mBookmarkColor; + richText.textBackground = mBookmarkBackgroundColor; } else //bookmark + breakpoint { QColor color = mBookmarkBackgroundColor; if(!color.alpha()) //we don't want transparent text color = mAddressColor; - painter->setPen(color); + richText.textColor = color; if(bpxtype & bp_normal) //bookmark + normal breakpoint { - painter->fillRect(QRect(x, y, w, h), QBrush(mBreakpointBackgroundColor)); //fill red + richText.textBackground = mBreakpointBackgroundColor; } else if(bpxtype & bp_hardware) //bookmark + hardware breakpoint only { - painter->fillRect(QRect(x, y, w, h), QBrush(mHardwareBreakpointBackgroundColor)); //fill red + richText.textBackground = mHardwareBreakpointBackgroundColor; } else //other cases (bookmark + memory breakpoint in disassembly) -> do as normal { - painter->setPen(mBookmarkColor); //black address - painter->fillRect(QRect(x, y, w, h), QBrush(mBookmarkBackgroundColor)); //fill bookmark color + richText.textColor = mBookmarkColor; + richText.textBackground = mBookmarkBackgroundColor; } } } } } - painter->drawText(QRect(x + 4, y, w - 4, h), Qt::AlignVCenter | Qt::AlignLeft, addrText); + + if(richText.textBackground.alpha()) + { + painter->fillRect(QRect(x, y, w, h), richText.textBackground); + } + + RichTextPainter::List list; + list.emplace_back(std::move(richText)); + paintRichText(x, y, w, h, 2, std::move(list), rowOffset, col); } break; - case 1: //draw bytes + case ColBytes: //draw bytes { const Instruction_t & instr = mInstBuffer.at(rowOffset); //draw functions @@ -449,11 +459,11 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse //draw bytes auto richBytes = getRichBytes(instr, wIsSelected); - RichTextPainter::paintRichText(painter, x, y, getColumnWidth(col), getRowHeight(), jumpsize + funcsize, richBytes, mFontMetrics); + paintRichText(x, y, getColumnWidth(col), getRowHeight(), jumpsize + funcsize, std::move(richBytes), rowOffset, col); } break; - case 2: //draw disassembly (with colours needed) + case ColDisassembly: //draw disassembly (with colours needed) { int loopsize = 0; int depth = 0; @@ -496,13 +506,13 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse ZydisTokenizer::TokenToRichText(token, richText, &mHighlightToken); else ZydisTokenizer::TokenToRichText(token, richText, 0); - int xinc = 4; - RichTextPainter::paintRichText(painter, x + loopsize, y, getColumnWidth(col) - loopsize, getRowHeight(), xinc, richText, mFontMetrics); + int xinc = 4 + loopsize; + paintRichText(x, y, getColumnWidth(col), getRowHeight(), xinc, std::move(richText), rowOffset, col); token.x = x + loopsize + xinc; } break; - case 3: //draw comments + case ColComment: //draw comments { //draw arguments Function_t funcType; @@ -529,49 +539,48 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse } int argsize = funcType == Function_none ? 3 : paintFunctionGraphic(painter, x, y, funcType, false); + RichTextPainter::CustomRichText_t richComment; + richComment.underline = false; + richComment.textColor = mTextColor; + richComment.textBackground = mBackgroundColor; + richComment.flags = RichTextPainter::FlagAll; + QString comment; bool autoComment = false; char label[MAX_LABEL_SIZE] = ""; if(GetCommentFormat(cur_addr, comment, &autoComment)) { - QColor backgroundColor; if(autoComment) { - painter->setPen(mAutoCommentColor); - backgroundColor = mAutoCommentBackgroundColor; + richComment.textColor = mAutoCommentColor; + richComment.textBackground = mAutoCommentBackgroundColor; } else //user comment { - painter->setPen(mCommentColor); - backgroundColor = mCommentBackgroundColor; + richComment.textColor = mCommentColor; + richComment.textBackground = mCommentBackgroundColor; } - int width = mFontMetrics->width(comment); - if(width > w) - width = w; - if(width) - painter->fillRect(QRect(x + argsize, y, width, h), QBrush(backgroundColor)); //fill comment color - painter->drawText(QRect(x + argsize, y, width, h), Qt::AlignVCenter | Qt::AlignLeft, comment); - argsize += width + 3; + richComment.text = std::move(comment); } else if(DbgGetLabelAt(cur_addr, SEG_DEFAULT, label)) // label but no comment { - QString labelText(label); - QColor backgroundColor; - painter->setPen(mLabelColor); - backgroundColor = mLabelBackgroundColor; - - int width = mFontMetrics->width(labelText); - if(width > w) - width = w; - if(width) - painter->fillRect(QRect(x + argsize, y, width, h), QBrush(backgroundColor)); //fill comment color - painter->drawText(QRect(x + argsize, y, width, h), Qt::AlignVCenter | Qt::AlignLeft, labelText); - argsize += width + 3; + richComment.textColor = mLabelColor; + richComment.textBackground = mLabelBackgroundColor; + richComment.text = label; } + RichTextPainter::List richText; + richText.emplace_back(std::move(richComment)); + if(mShowMnemonicBrief) { + RichTextPainter::CustomRichText_t richBrief; + richBrief.underline = false; + richBrief.textColor = mMnemonicBriefColor; + richBrief.textBackground = mMnemonicBriefBackgroundColor; + richBrief.flags = RichTextPainter::FlagAll; + char brief[MAX_STRING_SIZE] = ""; QString mnem; for(const ZydisTokenizer::SingleToken & token : mInstBuffer.at(rowOffset).tokens.tokens) @@ -590,20 +599,22 @@ QString Disassembly::paintContent(QPainter* painter, dsint rowBase, int rowOffse mnem.truncate(index); DbgFunctions()->GetMnemonicBrief(mnem.toUtf8().constData(), MAX_STRING_SIZE, brief); - painter->setPen(mMnemonicBriefColor); - QString mnemBrief = brief; if(mnemBrief.length()) { - int width = mFontMetrics->width(mnemBrief); - if(width > w) - width = w; - if(width) - painter->fillRect(QRect(x + argsize, y, width, h), QBrush(mMnemonicBriefBackgroundColor)); //mnemonic brief background color - painter->drawText(QRect(x + argsize, y, width, h), Qt::AlignVCenter | Qt::AlignLeft, mnemBrief); + RichTextPainter::CustomRichText_t space; + space.underline = false; + space.flags = RichTextPainter::FlagNone; + space.text = " "; + richText.emplace_back(std::move(space)); + + richBrief.text = std::move(mnemBrief); + + richText.emplace_back(std::move(richBrief)); } - break; } + + paintRichText(x, y, w, h, argsize, std::move(richText), rowOffset, col); } break; } @@ -1316,6 +1327,30 @@ int Disassembly::paintFunctionGraphic(QPainter* painter, int x, int y, Function_ return x_add + line_width + end_add; } +void Disassembly::paintRichText(int x, int y, int w, int h, int xinc, const RichTextPainter::List & richText, int rowOffset, int column) +{ + RichTextInfo & info = mRichText[column][rowOffset]; + info.x = x; + info.y = y; + info.w = w; + info.h = h; + info.xinc = xinc; + info.richText = richText; + info.alive = true; +} + +void Disassembly::paintRichText(int x, int y, int w, int h, int xinc, RichTextPainter::List && richText, int rowOffset, int column) +{ + RichTextInfo & info = mRichText[column][rowOffset]; + info.x = x; + info.y = y; + info.w = w; + info.h = h; + info.xinc = xinc; + info.richText = std::move(richText); + info.alive = true; +} + /************************************************************************************ Instructions Management ***********************************************************************************/ @@ -1703,7 +1738,7 @@ RichTextPainter::List Disassembly::getRichBytes(const Instruction_t & instr, boo DBGPATCHINFO patchInfo; if(isReal && DbgFunctions()->PatchGetEx(byteAddr, &patchInfo)) { - if((unsigned char)(instr.dump.at(byteIdx)) == patchInfo.newbyte) + if((unsigned char)(instr.dump.at((int)byteIdx)) == patchInfo.newbyte) { curByte.textColor = mModifiedBytesColor; curByte.textBackground = mModifiedBytesBackgroundColor; @@ -1771,6 +1806,16 @@ void Disassembly::prepareData() } setNbrOfLineToPrint(wCount); + + mRichText.resize(getColumnCount()); + for(size_t i = 0; i < mRichText.size(); i++) + { + mRichText[i].resize(wViewableRowsCount); + for(size_t j = 0; j < mRichText[i].size(); j++) + { + mRichText[i][j].alive = false; + } + } } void Disassembly::reloadData() @@ -1779,6 +1824,167 @@ void Disassembly::reloadData() AbstractTableView::reloadData(); } +void Disassembly::paintEvent(QPaintEvent* event) +{ + AbstractTableView::paintEvent(event); + + // Delay paint the rich text + QPainter painter(this->viewport()); + painter.setFont(font()); + int x = -horizontalScrollBar()->value(); + + for(int column = 0; column < mRichText.size(); column++) + { + int w = getColumnWidth(column); + int h = getViewableRowsCount() * getRowHeight(); + + const bool optimizationEnabled = true; + if(optimizationEnabled) + { + QString columnText; + columnText.reserve(getColumnWidth(column) * getViewableRowsCount() / getCharWidth()); + + QVector selections; + + for(int rowOffset = 0; rowOffset < mRichText[column].size(); rowOffset++) + { + if(rowOffset > 0) + columnText += QChar::LineSeparator; + + const RichTextInfo & info = mRichText[column][rowOffset]; + if(!info.alive) + continue; + + for(const RichTextPainter::CustomRichText_t & curRichText : info.richText) + { + if(curRichText.text.isEmpty()) + continue; + + if(mFormatCache.empty()) + { + mFormatCache.emplace_back(); + } + + QTextLayout::FormatRange range = std::move(mFormatCache.back()); + mFormatCache.pop_back(); + range.start = columnText.length(); + range.length = curRichText.text.length(); + + columnText += curRichText.text; + + QTextCharFormat & format = range.format; + switch(curRichText.flags) + { + case RichTextPainter::FlagNone: //defaults + { + format.clearForeground(); + format.clearBackground(); + } + break; + + case RichTextPainter::FlagColor: //color only + { + format.setForeground(curRichText.textColor); + format.clearBackground(); + } + break; + + case RichTextPainter::FlagBackground: //background only + { + if(curRichText.textBackground.alpha()) + { + format.setBackground(curRichText.textBackground); + } + else + { + format.clearBackground(); + } + format.clearForeground(); + } + break; + + case RichTextPainter::FlagAll: //color+background + { + if(curRichText.textBackground.alpha()) + { + format.setBackground(curRichText.textBackground); + } + else + { + format.clearBackground(); + } + format.setForeground(curRichText.textColor); + } + break; + } + + if(curRichText.underline) + { + range.format.setFontUnderline(true); + range.format.setUnderlineColor(curRichText.underlineColor); + } + else + { + range.format.setFontUnderline(false); + } + + selections.push_back(std::move(range)); + } + } + + QTextOption textOption; + textOption.setWrapMode(QTextOption::NoWrap); + mTextLayout.setTextOption(textOption); + + mTextLayout.setFormats(selections); + + while(!selections.empty()) + { + mFormatCache.push_back(std::move(selections.back())); + selections.pop_back(); + } + + mTextLayout.setText(columnText); + mTextLayout.beginLayout(); + + int rowHeight = getRowHeight(); + for(int i = 0, y = 0; ; i++, y += rowHeight) + { + QTextLine line = mTextLayout.createLine(); + if(!line.isValid()) + break; + const RichTextInfo & info = mRichText[column][i]; + line.setPosition(QPointF(info.xinc, y)); + } + + mTextLayout.endLayout(); + + QPixmap pixmap(w - 2, h); + pixmap.fill(Qt::transparent); + + QPainter clippedPainter; + clippedPainter.begin(&pixmap); + + mTextLayout.draw(&clippedPainter, QPointF(0, 0)); + + clippedPainter.end(); + + painter.drawPixmap(x, 0, pixmap); + } + else + { + for(int rowOffset = 0; rowOffset < mRichText[column].size(); rowOffset++) + { + const RichTextInfo & info = mRichText[column][rowOffset]; + if(info.alive) + RichTextPainter::paintRichText(&painter, info.x, info.y, info.w, info.h, info.xinc, info.richText, mFontMetrics); + } + } + + x += w; + } +} + /************************************************************************************ Public Methods diff --git a/src/gui/Src/BasicView/Disassembly.h b/src/gui/Src/BasicView/Disassembly.h index d02c7bdd..ebcdbbfb 100644 --- a/src/gui/Src/BasicView/Disassembly.h +++ b/src/gui/Src/BasicView/Disassembly.h @@ -2,6 +2,7 @@ #include "AbstractTableView.h" #include "QBeaEngine.h" +#include class CodeFoldingHelper; class MemoryPage; @@ -56,6 +57,8 @@ public: void prepareData() override; void reloadData() override; + void paintEvent(QPaintEvent* event) override; + // Public Methods duint rvaToVa(dsint rva) const; void disassembleClear(); @@ -153,6 +156,14 @@ private: QList mVaHistory; int mCurrentVa; + enum + { + ColAddress, + ColBytes, + ColDisassembly, + ColComment, + }; + protected: // Jumps Graphic int paintJumpsGraphic(QPainter* painter, int x, int y, dsint addr, bool isjmp); @@ -239,4 +250,22 @@ protected: bool mPermanentHighlightingMode; bool mNoCurrentModuleText; bool mIsMain = false; + + struct RichTextInfo + { + bool alive = true; + int x = 0; + int y = 0; + int w = 0; + int h = 0; + int xinc = 0; + RichTextPainter::List richText; + }; + + QTextLayout mTextLayout; + std::vector mFormatCache; + std::vector> mRichText; + + void paintRichText(int x, int y, int w, int h, int xinc, const RichTextPainter::List & richText, int rowOffset, int column); + void paintRichText(int x, int y, int w, int h, int xinc, RichTextPainter::List && richText, int rowOffset, int column); }; diff --git a/src/gui/Src/Disassembler/QBeaEngine.cpp b/src/gui/Src/Disassembler/QBeaEngine.cpp index 86d6b3cd..44c76d06 100644 --- a/src/gui/Src/Disassembler/QBeaEngine.cpp +++ b/src/gui/Src/Disassembler/QBeaEngine.cpp @@ -261,6 +261,7 @@ Instruction_t QBeaEngine::DisassembleAt(const byte_t* data, duint size, duint or wInst.regsReferenced.emplace_back(cp.FlagName(ZydisCPUFlag(i)), rai); } + wInst.regsReferenced.reserve(ZYDIS_REGISTER_MAX_VALUE); reginfo[ArchValue(ZYDIS_REGISTER_EIP, ZYDIS_REGISTER_RIP)] = Zydis::RAINone; for(int i = ZYDIS_REGISTER_NONE; i <= ZYDIS_REGISTER_MAX_VALUE; ++i) if(reginfo[i]) diff --git a/src/gui/Src/Utils/RichTextPainter.h b/src/gui/Src/Utils/RichTextPainter.h index ba5fc5e1..afb9190d 100644 --- a/src/gui/Src/Utils/RichTextPainter.h +++ b/src/gui/Src/Utils/RichTextPainter.h @@ -30,6 +30,7 @@ public: int underlineWidth = 2; bool underlineConnectPrev = false; }; + static_assert(std::is_move_assignable::value, "not movable"); typedef std::vector List;