void ofxTimeMeasurements::draw(int x, int y) { if (!enabled){ //drawString(ofToString(fr, msPrecision), 10, fontSize); return; } float fr = ofGetFrameRate(); uint64_t timeNow; if(internalBenchmark){ timeNow = TM_GET_MICROS(); } currentFrameNum = ofGetFrameNum(); drawLines.clear(); float percentTotal = 0.0f; float timePerFrame = 1000.0f / desiredFrameRate; mutex.lock(); vector<TimeMeasurement*> toResetUpdatedLastFrameFlag; //update time stuff, build draw lists for( unordered_map<string,TimeMeasurement*>::iterator ii = times.begin(); ii != times.end(); ++ii ){ TimeMeasurement* t = ii->second; string key = ii->first; if(!t->measuring){ if (t->life > 0.01){ t->life *= idleTimeColorDecay; //decrease life }else{ //life decays very slow when very low t->life *= deadThreadExtendedLifeDecSpeed; //decrease life very slowly } } // if (!t->updatedLastFrame && averaging){ // if we didnt update that time, make it tend to zero slowly // t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration; // } toResetUpdatedLastFrameFlag.push_back(t); } unordered_map<ThreadId, ThreadInfo>::iterator ii; vector<ThreadId> expiredThreads; //lets make sure the Main Thread is always on top vector< ThreadContainer > sortedThreadList; for( ii = threadInfo.begin(); ii != threadInfo.end(); ++ii ){ //walk all thread trees ThreadContainer cont; cont.id = ii->first; cont.info = &ii->second; if (isMainThread(ii->first)){ //is main thread sortedThreadList.insert(sortedThreadList.begin(), cont); }else{ sortedThreadList.push_back(cont); } } std::sort(sortedThreadList.begin(), sortedThreadList.end(), compareThreadPairs); #if defined(USE_OFX_HISTORYPLOT) vector<ofxHistoryPlot*> plotsToDraw; #endif for( int k = 0; k < sortedThreadList.size(); k++ ){ //walk all thread trees ThreadId thread = sortedThreadList[k].id; core::tree<string> &tr = sortedThreadList[k].info->tree; ThreadInfo & tinfo = threadInfo[thread]; PrintedLine header; header.formattedKey = "+ " + *tr; header.color = tinfo.color; header.lineBgColor = ofColor(header.color, dimColorA * 2); //header twice as alpha header.key = *tr; //key for selection, is thread name drawLines.push_back(header); //add header to drawLines int numAlive = 0; int numAdded = 0; core::tree<string>::iterator wholeTreeWalker = tr.in(); bool finishedWalking = false; float winW = ofGetWidth(); while( !finishedWalking ){ string key = *wholeTreeWalker; TimeMeasurement * t = times[*wholeTreeWalker]; if(t->thread == thread){ #if defined(USE_OFX_HISTORYPLOT) bool plotActive = false; ofxHistoryPlot* plot = plots[key]; if(plot){ if(t->settings.plotting){ if(t->updatedLastFrame){ //update plot res every now and then if(currentFrameNum%120 == 1) plot->setMaxHistory(MIN(maxPlotSamples, winW * plotResolution)); if (!freeze) { if (t->accumulating) { plot->update(t->microsecondsAccum / 1000.0f); } else { plot->update(t->avgDuration / 1000.0f); } } } plotsToDraw.push_back(plot); plotActive = true; } } #endif bool visible = t->settings.visible; bool alive = t->life > 0.0001; if(alive){ numAlive++; } if (visible && (removeExpiredTimings ? alive : true)){ PrintedLine l; l.key = key; l.tm = t; l.lineBgColor = ofColor(tinfo.color, dimColorA); int depth = wholeTreeWalker.level(); for(int i = 0; i < depth; ++i) l.formattedKey += " "; if (wholeTreeWalker.size() == 0){ l.formattedKey += "-"; }else{ l.formattedKey += "+"; } l.formattedKey += key + string(t->accumulating ? "[" + ofToString(t->numAccumulations)+ "]" : "" ); l.isAccum = t->accumulating; l.time = getTimeStringForTM(t); if(drawPercentageAsGraph){ l.percentGraph = getPctForTM(t); } l.color = tinfo.color * ((1.0 - idleTimeColorFadePercent) + idleTimeColorFadePercent * t->life); if (!t->settings.enabled){ if(t->ifClause){ l.color = disabledTextColor; }else{ l.color = disabledTextColor.getInverted(); } } #if defined(USE_OFX_HISTORYPLOT) if(plotActive){ l.plotColor = ofColor(plots[key]->getColor(), 200); } #endif if (menuActive && t->key == selection){ if(currentFrameNum%5 < 4){ l.color.invert(); l.lineBgColor = ofColor(tinfo.color, dimColorA * 1.5); } } drawLines.push_back(l); numAdded++; } //only update() and draw() count to the final % if(key == TIME_MEASUREMENTS_DRAW_KEY || key == TIME_MEASUREMENTS_UPDATE_KEY){ percentTotal += (t->avgDuration * 0.1f) / timePerFrame; } //reset accumulator t->accumulating = false; t->numAccumulations = 0; t->microsecondsAccum = 0; } //control the iterator to walk the tree "recursively" without doing so. if(wholeTreeWalker.size()){ wholeTreeWalker = wholeTreeWalker.in(); }else{ if ( wholeTreeWalker.next() == wholeTreeWalker.end() ){ wholeTreeWalker = wholeTreeWalker.out(); while( wholeTreeWalker.next() == wholeTreeWalker.end() && wholeTreeWalker != tr){ wholeTreeWalker = wholeTreeWalker.out(); } if(wholeTreeWalker == tr){ finishedWalking = true; }else{ wholeTreeWalker++; } }else{ ++wholeTreeWalker; } } } #if defined(USE_OFX_HISTORYPLOT) numActivePlots = plotsToDraw.size(); #endif if (numAlive == 0 && removeExpiredThreads){ //drop that whole section if all entries in it are not alive for(int i = 0; i < numAdded + 1; i++){ if(drawLines.size() > 0){ int delID = drawLines.size() - 1; //clear selection if needed if (selection == drawLines[delID].key){ selection = TIME_MEASUREMENTS_UPDATE_KEY; } drawLines.erase(drawLines.begin() + delID); } } expiredThreads.push_back(thread); } } //delete expired threads for(int i = 0; i < expiredThreads.size(); i++){ unordered_map<ThreadId, ThreadInfo>::iterator treeIt = threadInfo.find(expiredThreads[i]); if (treeIt != threadInfo.end()) threadInfo.erase(treeIt); } mutex.unlock(); updateLongestLabel(); //find headers int tempMaxW = -1; vector<int> headerLocations; for( int i = 0; i < drawLines.size(); i++ ){ if (drawLines[i].tm){ //its a measurement //add padding to draw in columns for(int j = drawLines[i].formattedKey.length(); j < longestLabel; j++){ drawLines[i].formattedKey += " "; } if (!drawLines[i].tm->error){ drawLines[i].shouldDrawPctGraph = true; drawLines[i].fullLine = drawLines[i].formattedKey + " " + drawLines[i].time; }else{ drawLines[i].shouldDrawPctGraph = true; drawLines[i].fullLine = drawLines[i].formattedKey + " Error!" ; } int len = drawLines[i].fullLine.length(); if(len > tempMaxW) tempMaxW = len; if(drawLines[i].tm->measuring) drawLines[i].shouldDrawPctGraph = false; }else{ //its a header drawLines[i].fullLine = drawLines[i].formattedKey; drawLines[i].shouldDrawPctGraph = false; headerLocations.push_back(i); } } int numInstructionLines = 0; if(menuActive){ //add instructions line if menu active PrintedLine l; //title line l.color = hilightColor; l.lineBgColor = ofColor(hilightColor, dimColorA * 2); l.fullLine = " KEYBOARD COMMANDS "; //len = 23 int numPad = 2 + ceil((getWidth() - charW * (23)) / charW); for(int i = 0; i < floor(numPad/2.0); i++ ) l.fullLine = "#" + l.fullLine; for(int i = 0; i < ceil(numPad/2.0); i++ ) l.fullLine += "#"; l.fullLine = " " + l.fullLine; drawLines.push_back(l); numInstructionLines++; //key command lines l.lineBgColor = ofColor(hilightColor, dimColorA); l.fullLine = " 'UP/DOWN' select measur."; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'LFT/RGHT' expand/collaps"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'RET' toggle code section"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'A' average measurements"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'F' freeze measurements"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'L' cycle widget location"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'PG_DWN' en/disable addon"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'B' internal benchmark"; drawLines.push_back(l); numInstructionLines++; l.fullLine = " 'V' expand all"; drawLines.push_back(l); numInstructionLines++; #if defined USE_OFX_HISTORYPLOT l.fullLine = " 'P' plot selectd measur."; drawLines.push_back(l); numInstructionLines++; #endif } maxW = tempMaxW; ofSetupScreen(); //mmmm---- ofPushStyle(); ofSetRectMode(OF_RECTMODE_CORNER); ofSetDrawBitmapMode(OF_BITMAPMODE_SIMPLE); ofEnableAlphaBlending(); ofPushMatrix(); ofScale(uiScale,uiScale); ofFill(); //draw all plots #if defined(USE_OFX_HISTORYPLOT) //int numCols = plotsToDraw.size() float highest = FLT_MIN; for(auto plot : plotsToDraw){ if(allPlotsTogether){ //lets find the range that covers all the plots float high = plot->getHigerRange(); if (high > highest) highest = high; plot->setDrawTitle(false); plot->setDrawBackground(false); plot->setShowSmoothedCurve(false); }else{ plot->setDrawTitle(true); plot->setDrawBackground(true); plot->setLowerRange(0); plot->setShowSmoothedCurve(true); } } float canvasW = ofGetWidth(); float canvasH = ofGetHeight(); if(allPlotsTogether && plotsToDraw.size()){ ofSetColor(0, 230); ofDrawRectangle(0, canvasH - plotHeight, canvasW / uiScale, plotHeight); } for(int i = 0; i < plotsToDraw.size(); i++){ int y = (plotBaseY == 0 ? canvasH : plotBaseY) / uiScale - plotHeight * (i + 1); if(allPlotsTogether){ plotsToDraw[i]->setRange(0, highest); y = ((plotBaseY == 0 ? canvasH : plotBaseY) - plotHeight) / uiScale; } plotsToDraw[i]->draw(0, y, canvasW / uiScale, plotHeight); if(!allPlotsTogether){ ofSetColor(99); if(i != plotsToDraw.size() -1){ ofLine(0, y, canvasW / uiScale, y ); } } if(allPlotsTogether){ ofSetColor(plotsToDraw[i]->getColor()); deque<float>& vals = plotsToDraw[i]->getValues(); float val = 0.0f; if(!vals.empty()) val = vals.back(); string msg = plotsToDraw[i]->getVariableName() + " " + ofToString(val, 2); drawString(msg, canvasW - charW * msg.size() - 2, ofGetHeight() - plotHeight - 4 - charH * (plotsToDraw.size() -1 - i)); } } #endif float totalW = getWidth(); float totalH = getHeight(); //draw bg rect ofSetColor(bgColor); ofDrawRectangle(x, y + 1, totalW, totalH); //draw all lines for(int i = 0; i < drawLines.size(); i++){ ofSetColor(drawLines[i].lineBgColor); ofRectangle lineRect = ofRectangle(x, y + 2 + i * charH, totalW, charH + (drawLines[i].tm ? 0 : 1)); ofDrawRectangle(lineRect); if(drawLines[i].isAccum && drawLines[i].tm != NULL){ ofSetColor(drawLines[i].color, 128); ofDrawRectangle(x + totalW, y + 3 + i * charH, -5, charH - 1 ); } ofSetColor(drawLines[i].color); drawString(drawLines[i].fullLine, x , y + (i + 1) * charH); if(drawLines[i].plotColor.a > 0){ //plot highlight on the sides ofSetColor(drawLines[i].plotColor); float y1 = y + 2.4f + i * charH; ofDrawTriangle( x, y1, x, y1 + charH, x + charW * 0.7f, y1 + charH * 0.5f); } if(drawPercentageAsGraph && drawLines[i].shouldDrawPctGraph && drawLines[i].percentGraph > 0.02f){ float ww = charW * 5.5; float xx = lineRect.x + lineRect.width - charW * 7; float pct = MIN(drawLines[i].percentGraph, 1.0); unsigned char a = 64; ofColor gC; if(drawLines[i].percentGraph > 1.0){ gC = ofColor(255,0,0, (currentFrameNum%4 > 2) ? 1.5 * a : a); }else{ gC = ofColor(drawLines[i].lineBgColor, a) * (1.0f - pct) + ofColor(255,0,0,a) * pct; } ofSetColor(gC); ofDrawRectangle( xx, lineRect.y + 0.2 * lineRect.height , ww * pct, lineRect.height * 0.65 ); } } if(internalBenchmark){ float offset = 0; if(drawLocation == TIME_MEASUREMENTS_TOP_LEFT || drawLocation == TIME_MEASUREMENTS_TOP_RIGHT || drawLocation == TIME_MEASUREMENTS_CUSTOM_LOCATION ){ offset = (drawLines.size() + 2.5) * charH; } ofSetColor(0); ofDrawRectangle(x, offset + y - charH, totalW, charH); ofSetColor(currentFrameNum%3 ? 255 : 64); drawString(" Meas: " + ofToString(wastedTimeAvg / 1000.f, 2) + "ms " + " Draw: " + ofToString(wastedTimeDrawingAvg / 1000.f, 2) + "ms ", x, offset + y - charH * 0.12); } if (freeze) { if(currentFrameNum%5 < 4) ofSetColor(frozenColor); else ofSetColor(ofColor::white); drawString("Frozen! 'F'", x + totalW - 13 * charW, y + charH ); } {//lines ofSetColor(hilightColor); ofMesh lines; ofSetLineWidth(0.1); lines.setMode(OF_PRIMITIVE_LINES); float fuzzyFix = 0.5; float yy = y+1 + fuzzyFix; lines.addVertex(ofVec2f(x, yy)); lines.addVertex(ofVec2f(x + totalW, yy)); yy = y + totalH - charH - 3 + fuzzyFix; lines.addVertex(ofVec2f(x, yy)); lines.addVertex(ofVec2f(x + totalW, yy)); yy = y + totalH + fuzzyFix; lines.addVertex(ofVec2f(x, yy)); lines.addVertex(ofVec2f(x + totalW, yy)); if(menuActive){ yy = y + totalH + fuzzyFix - (numInstructionLines + 1) * charH - 3; lines.addVertex(ofVec2f(x, yy)); lines.addVertex(ofVec2f(x + totalW, yy)); yy = y + totalH + fuzzyFix - (numInstructionLines) * charH - 3; lines.addVertex(ofVec2f(x, yy)); lines.addVertex(ofVec2f(x + totalW, yy)); } lines.draw(); }//lines //print bottom line, fps and stuff bool missingFrames = ( fr < desiredFrameRate - 1.0 ); // tolerance of 1 fps TODO! static char msg[128]; sprintf(msg, "%2.1f fps % 5.1f%%", fr, percentTotal ); if(missingFrames){ ofSetColor(170,33,33); //reddish fps below desired fps }else{ ofSetColor(hilightColor); } int len = strlen(msg); string pad = " "; int diff = (maxW - len) - 1; for(int i = 0; i < diff; i++) pad += " "; int lastLine = ( drawLines.size() + 1 ) * charH + 2; drawString( pad + msg, x, y + lastLine ); //show activate menu key if(menuActive ) ofSetColor(hilightColor.getInverted()); else ofSetColor(hilightColor); drawString(" '" + ofToString(char(activateKey)) + "'", x, y + lastLine); //show averaging warning if (averaging) { if (currentFrameNum % 5 < 2) ofSetColor(hilightColor); else ofSetColor(ofColor::limeGreen); drawString(" avg!", x + charW * 3.5, y + lastLine); } for(int i = 0; i < toResetUpdatedLastFrameFlag.size(); i++){ toResetUpdatedLastFrameFlag[i]->updatedLastFrame = false; } ofPopMatrix(); ofPopStyle(); if(internalBenchmark){ wastedTimeDrawingThisFrame += TM_GET_MICROS() - timeNow; wastedTimeAvg = wastedTimeThisFrame * 0.025f + 0.975f * wastedTimeAvg; wastedTimeDrawingAvg = wastedTimeDrawingThisFrame * 0.025f + 0.975f * wastedTimeDrawingAvg; wastedTimeThisFrame = wastedTimeDrawingThisFrame = 0; } }
void ofxTimeMeasurements::draw(float x, float y) { if (!enabled) return; drawLines.clear(); float percentTotal = 0.0f; float timePerFrame = 1000.0f / desiredFrameRate; mutex.lock(); vector<TimeMeasurement*> toResetUpdatedLastFrameFlag; //update time stuff, build draw lists for( unordered_map<string,TimeMeasurement*>::iterator ii = times.begin(); ii != times.end(); ++ii ){ TimeMeasurement* t = ii->second; string key = ii->first; if(!t->measuring){ if (t->life > 0.01){ t->life *= idleTimeColorDecay; //decrease life }else{ //life decays very slow when very low t->life *= deadThreadExtendedLifeDecSpeed; //decrease life very slowly } } if (!t->updatedLastFrame && timeAveragePercent < 1.0f){ // if we didnt update that time, make it tend to zero slowly t->avgDuration = (1.0f - timeAveragePercent) * t->avgDuration; } toResetUpdatedLastFrameFlag.push_back(t); } unordered_map<Poco::Thread*, ThreadInfo>::iterator ii; vector<Poco::Thread*> expiredThreads; //lets make sure the Main Thread is always on top vector< pair<Poco::Thread*, ThreadInfo> > sortedThreadList; for( ii = threadInfo.begin(); ii != threadInfo.end(); ++ii ){ //walk all thread trees if (ii->first == NULL){ //main thread is NULL! sortedThreadList.insert(sortedThreadList.begin(), *ii); }else{ sortedThreadList.push_back(*ii); } } std::sort(sortedThreadList.begin(), sortedThreadList.end(), compareThreadPairs); #if defined(USE_OFX_HISTORYPLOT) vector<ofxHistoryPlot*> plotsToDraw; #endif for( int k = 0; k < sortedThreadList.size(); k++ ){ //walk all thread trees Poco::Thread* thread = sortedThreadList[k].first; core::tree<string> &tr = sortedThreadList[k].second.tree; PrintedLine header; header.formattedKey = "+" + *tr; header.color = threadInfo[thread].color; header.key = *tr; //key for selection, is thread name drawLines.push_back(header); //add header to drawLines int numAlive = 0; int numAdded = 0; core::tree<string>::iterator wholeTreeWalker = tr.in(); bool finishedWalking = false; while( !finishedWalking ){ string key = *wholeTreeWalker; TimeMeasurement * t = times[*wholeTreeWalker]; #if defined(USE_OFX_HISTORYPLOT) bool plotActive = false; if(plots[key]){ if(t->settings.plotting){ if(t->updatedLastFrame){ if (t->accumulating){ plots[key]->update(t->microsecondsAccum / 1000.0f); }else{ plots[key]->update(t->avgDuration / 1000.0f); } } plotsToDraw.push_back(plots[key]); plotActive = true; } } #endif bool visible = t->settings.visible; bool alive = t->life > 0.0001; if(alive){ numAlive++; } if (visible){ PrintedLine l; l.key = key; l.tm = t; int depth = wholeTreeWalker.level(); for(int i = 0; i < depth; ++i) l.formattedKey += " "; if (wholeTreeWalker.size() == 0){ l.formattedKey += "-"; }else{ l.formattedKey += "+"; } l.formattedKey += key; #if defined(USE_OFX_HISTORYPLOT) if(plotActive) l.formattedKey += " [p]"; #endif l.time = getTimeStringForTM(t); l.color = threadInfo[thread].color * ((1.0 - idleTimeColorFadePercent) + idleTimeColorFadePercent * t->life); if (!t->settings.enabled){ l.color = disabledTextColor; } if (t->key == selection && menuActive){ if(ofGetFrameNum()%5 < 4){ l.color.invert(); } } drawLines.push_back(l); numAdded++; } //only update() and draw() count to the final % if(key == TIME_MEASUREMENTS_DRAW_KEY || key == TIME_MEASUREMENTS_UPDATE_KEY){ percentTotal += (t->avgDuration * 0.1f) / timePerFrame; } t->accumulating = false; t->microsecondsAccum = 0; //control the iterator to walk the tree "recursivelly" without doing so. if(wholeTreeWalker.size()){ wholeTreeWalker = wholeTreeWalker.in(); }else{ if ( wholeTreeWalker.next() == wholeTreeWalker.end() ){ wholeTreeWalker = wholeTreeWalker.out(); while( wholeTreeWalker.next() == wholeTreeWalker.end() && wholeTreeWalker != tr){ wholeTreeWalker = wholeTreeWalker.out(); } if(wholeTreeWalker == tr){ finishedWalking = true; }else{ wholeTreeWalker++; } }else{ ++wholeTreeWalker; } } } if (numAlive == 0 && removeExpiredThreads){ //drop that whole section if all entries in it are not alive for(int i = 0; i < numAdded + 1; i++){ if(drawLines.size() > 0){ int delID = drawLines.size() - 1; //clear selection if needed if (selection == drawLines[delID].key){ selection = TIME_MEASUREMENTS_UPDATE_KEY; } drawLines.erase(drawLines.begin() + delID); } } expiredThreads.push_back(thread); } } //delete expired threads for(int i = 0; i < expiredThreads.size(); i++){ unordered_map<Poco::Thread*, ThreadInfo>::iterator treeIt = threadInfo.find(expiredThreads[i]); if (treeIt != threadInfo.end()) threadInfo.erase(treeIt); } mutex.unlock(); updateLongestLabel(); //update max width, find headers int tempMaxW = -1; vector<int> headerLocations; for( int i = 0; i < drawLines.size(); i++ ){ if (drawLines[i].tm){ //its a measurement //add padding to draw in columns for(int j = drawLines[i].formattedKey.length(); j < longestLabel; j++){ drawLines[i].formattedKey += " "; } if (!drawLines[i].tm->error){ drawLines[i].fullLine = drawLines[i].formattedKey + " " + drawLines[i].time; }else{ drawLines[i].fullLine = drawLines[i].formattedKey + " Error!" ; } int len = drawLines[i].fullLine.length(); if(len > tempMaxW) tempMaxW = len; }else{ //its a header drawLines[i].fullLine = drawLines[i].formattedKey; headerLocations.push_back(i); } } maxW = tempMaxW; ofSetupScreen(); //mmmm---- ofPushStyle(); ofPushMatrix(); ofScale(uiScale,uiScale); ofSetDrawBitmapMode(OF_BITMAPMODE_SIMPLE); ofFill(); ofEnableAlphaBlending(); #if defined(USE_OFX_HISTORYPLOT) //int numCols = plotsToDraw.size() for(int i = 0; i < plotsToDraw.size(); i++){ int y = ofGetHeight() / uiScale - plotHeight * (i + 1); plotsToDraw[i]->draw(0, y, ofGetWidth() / uiScale, plotHeight); ofSetColor(99); if(i != plotsToDraw.size() -1){ ofLine(0, y, ofGetWidth() / uiScale, y ); } } #endif float totalW = getWidth(); float totalH = getHeight(); ofSetColor(bgColor, 245); int barH = 1; ofRect(x, y + 1, totalW, totalH); //thread header bg highlight for(int i = 0; i < headerLocations.size(); i++){ int loc = headerLocations[i]; //whole section ofSetColor(drawLines[loc].color, 40); int h = charH * ((i < headerLocations.size() - 1) ? headerLocations[i+1] - headerLocations[i] : drawLines.size() - loc ); ofRect(x, y + 2 + loc * charH, totalW, h); //thread header ofSetColor(drawLines[loc].color, 40); ofRect(x, y + 2 + loc * charH, totalW, charH + 1); } ofSetColor(hilightColor); ofRect(x, y + 1, totalW, barH); ofRect(x, y + totalH - charH - 4 , totalW, barH); ofRect(x, y + totalH, totalW - barH, barH); for(int i = 0; i < drawLines.size(); i++){ ofSetColor(drawLines[i].color); drawString(drawLines[i].fullLine, x , y + (i + 1) * charH); } //print bottom line, fps and stuff bool missingFrames = ( ofGetFrameRate() < desiredFrameRate - 1.0 ); // tolerance of 1 fps TODO! static char msg[128]; sprintf(msg, "%2.1f fps % 5.1f%%", ofGetFrameRate(), percentTotal ); if(missingFrames){ ofSetColor(170,33,33); }else{ ofSetColor(hilightColor); } int len = strlen(msg); string pad = " "; int diff = (maxW - len) - 1; for(int i = 0; i < diff; i++) pad += " "; int lastLine = ( drawLines.size() + 1 ) * charH + 2; drawString( pad + msg, x, y + lastLine ); ofSetColor(hilightColor); drawString( " '" + ofToString(char(activateKey)) + "'" + string(timeAveragePercent < 1.0 ? " avgd!" : ""), x, y + lastLine ); for(int i = 0; i < toResetUpdatedLastFrameFlag.size(); i++){ toResetUpdatedLastFrameFlag[i]->updatedLastFrame = false; } ofPopMatrix(); ofPopStyle(); }