Skip to content

Commit 09aea51

Browse files
SG-40852: Improve paint speed degrading as you draw many annotations (#984)
### Linked issues SG-40852 ### Summarize your change. Pretty significant fix in the management of the annotation fbo cache for the current frame, which got broken some years ago. In a nutshell, the rendered fbo cache should always have all of the paint commands (eg: annotation) rendered in it, except for the last paint command, in case we're currently editing it (eg: drawing a stroke). Somehow, this cache management was really borked. It would fail to find a cache, then draw all annotations in the cache, keep the cache. At the next redraw, it would find the cache, render the start of a new stroke, but as you edited it, it wouldn't find the cache anymore, recreated the cache, found apply the last stroke, which would somehow invalidate the cache, and at the next update it would go through this cycle again. In other words, it would redraw everything, every time. I've made a fix such that when a new stroke is added, it will (unfortunately) still draw everything, but as the stroke updates, it will keep only rendering a single paint command on top of the cache, until you start a new stroke. Repainting all strokes when you start a new one, if you have many, like 250+ srokes, might take like 150ms, which is a definitely a lot, but it fixes the problem where you'd pay the 150ms at every single mouse move (ugh). Anyway, things should be be much better now. ### Describe the reason for the change. customer complaints, and could reproduce this easily with rv_tests scripts. ### Describe what you have tested and on which operating system. macOS. ### Add a list of changes, and note any that might need special attention during the review. During the review, you should make sure that nothing got broken in the process. Try all tools: reaser, pen, dodge, burn, etc. You should also verify that simulateneous paint strokes in live review collab mode still works. In particular, @bernie-laberge, please make sure that your fix for recomputing the render ID still works. During testing, please do the following: - Start OLD RV (eg:last week). Open media. Press F10 to show annotate tools - Start NEW RV (this VT build). Open media. Press F10 also Both RV, do: -> Menu -> RV Tests -> Automatic Annotation Drawing -> start drawing circles. (load rv tests package if not done so already) Both RVs should start to draw circles. Let it got for a few minutes. After a while, maybe 5-6-10 minutes, the OLD RV should be extremely slow. The NEW RV will have slowed down a bit, but not nearly as bad. (note: this will create hundreds and hundreds of strokes at that frame, ### If possible, provide screenshots. --------- Signed-off-by: Patrick Bergeron <[email protected]>
1 parent b5914bb commit 09aea51

File tree

2 files changed

+38
-33
lines changed

2 files changed

+38
-33
lines changed

src/lib/ip/IPCore/ImageRenderer.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <IPCore/IPNode.h>
1717
#include <IPCore/ShaderProgram.h>
1818
#include <IPCore/Application.h>
19+
#include <IPCore/PaintCommand.h>
1920
#include <TwkExc/TwkExcException.h>
2021
#include <TwkGLF/GL.h>
2122
#include <TwkGLF/GLState.h>
@@ -1595,8 +1596,7 @@ namespace IPCore
15951596
{
15961597
uploadRoot = root;
15971598
}
1598-
m_uploadThreadPrefetch =
1599-
(root == uploadRoot) ? false : true;
1599+
m_uploadThreadPrefetch = (root == uploadRoot) ? false : true;
16001600

16011601
//
16021602
// traverse our IPTree, and create gl textures/buffers for
@@ -4854,9 +4854,6 @@ namespace IPCore
48544854
return;
48554855

48564856
const string prenderID = imageToFBOIdentifier(root);
4857-
// these two are for pingpong
4858-
const GLFBO* tempfbo1 = m_imageFBOManager.newImageFBO(fbo, m_fullRenderSerialNumber, prenderID)->fbo();
4859-
const GLFBO* tempfbo2 = m_imageFBOManager.newImageFBO(fbo, m_fullRenderSerialNumber, prenderID)->fbo();
48604857

48614858
assert(fbo);
48624859

@@ -4905,11 +4902,26 @@ namespace IPCore
49054902
if (foundCachedFBO)
49064903
{
49074904
startCmd = lastCmdNum;
4908-
cachedFBO->fbo()->copyTo(tempfbo1);
49094905
}
49104906
else
49114907
{
49124908
cachedFBO = m_imageFBOManager.newImageFBO(fbo, m_fullRenderSerialNumber, newRenderID.str());
4909+
}
4910+
}
4911+
4912+
// Allocate temp FBOs AFTER cache lookup so the cached FBO is protected from being reused
4913+
const GLFBO* tempfbo1 = m_imageFBOManager.newImageFBO(fbo, m_fullRenderSerialNumber, prenderID)->fbo();
4914+
const GLFBO* tempfbo2 = m_imageFBOManager.newImageFBO(fbo, m_fullRenderSerialNumber, prenderID)->fbo();
4915+
4916+
// Copy initial render to temp buffer
4917+
if (root->commands.size() > 1)
4918+
{
4919+
if (foundCachedFBO)
4920+
{
4921+
cachedFBO->fbo()->copyTo(tempfbo1);
4922+
}
4923+
else
4924+
{
49134925
fbo->copyTo(tempfbo1);
49144926
}
49154927
}
@@ -5049,7 +5061,9 @@ namespace IPCore
50495061
Paint::renderPaintCommands(paintContext);
50505062
}
50515063

5052-
if (!paintContext.cacheUpdated && cachedFBO)
5064+
// Only clear the cache identifier if we didn't use an existing cache
5065+
// If we had a cache hit (foundCachedFBO), keep the identifier even if we didn't update it
5066+
if (!paintContext.cacheUpdated && cachedFBO && !foundCachedFBO)
50535067
cachedFBO->identifier = "";
50545068

50555069
/////////////////// clean up /////////////////////////////////////

src/lib/ip/IPCore/PaintCommand.cpp

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -784,32 +784,6 @@ namespace IPCore
784784
size_t count = 0;
785785
for (size_t i = 0; i < context.commands.size(); count++)
786786
{
787-
//
788-
// update the cache, only up to the second to the last cmd we
789-
// don't cache the render with the very last cmd because doing
790-
// so will cause the last cmd to be rendered twice, and in
791-
// case of alpha not 0, or 1, it will not be correct
792-
//
793-
// Note: There is no cache to update when there is only 1
794-
// command to render as the context.cachedfbo is already up to
795-
// date. In other words when the last command is also the first
796-
// command, we don't update the cache.
797-
//
798-
799-
if (context.updateCache && context.cachedfbo && (context.commands.size() > 1)
800-
&& (context.commands[i] == context.lastCommand))
801-
{
802-
if (i > 0)
803-
{
804-
currentFBO->copyTo(context.cachedfbo);
805-
}
806-
else
807-
{
808-
tempfbo1->copyTo(context.cachedfbo);
809-
}
810-
context.cacheUpdated = true;
811-
}
812-
813787
const Paint::Command* cmd = context.commands[i];
814788
if (cmd->getType() == Paint::Command::ExecuteAllBefore)
815789
{
@@ -875,6 +849,23 @@ namespace IPCore
875849
cmd->execute(commandContext);
876850
++i;
877851
}
852+
// Update the cache AFTER command execution
853+
// We cache up to the second-to-last command in the batch (not the very last, which might still be dragging)
854+
// count is the number of commands rendered so far (1-indexed after increment)
855+
// Cache after rendering the second-to-last command: count == curCmdNum - 1
856+
bool shouldCache = context.updateCache && context.cachedfbo && (curCmdNum > 1) && (count == curCmdNum - 1);
857+
858+
if (shouldCache)
859+
{
860+
// Always save to cache from tempfbo1 to maintain consistent ping-pong state
861+
// If currentFBO is tempfbo2, copy it to tempfbo1 first
862+
if (currentFBO != tempfbo1)
863+
{
864+
currentFBO->copyTo(tempfbo1);
865+
}
866+
tempfbo1->copyTo(context.cachedfbo);
867+
context.cacheUpdated = true;
868+
}
878869
}
879870

880871
context.commandExecuted = count;

0 commit comments

Comments
 (0)