Skip to content

Commit 624f063

Browse files
committed
Remove dirty rect processing & improve restore
Dirty rect 'optimizations' were actually lowering performance, and causing edge case issues with device restoration and partial updates - removed this functionality. Also improved device reset/restore handling (force repaint)
1 parent 2090d12 commit 624f063

File tree

4 files changed

+42
-199
lines changed

4 files changed

+42
-199
lines changed

Client/cefweb/CWebView.cpp

Lines changed: 40 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -20,106 +20,6 @@
2020
namespace
2121
{
2222
const int CEF_PIXEL_STRIDE = 4;
23-
24-
// Threshold for switching from dirty rect updates to full frame copy
25-
// If dirty area exceeds this fraction of total area, full copy is more efficient
26-
constexpr float DIRTY_RECT_THRESHOLD = 0.25f;
27-
28-
// Calculate total area covered by dirty rects (accounting for overlaps approximately)
29-
size_t CalculateDirtyArea(const CefRenderHandler::RectList& dirtyRects, int frameWidth, int frameHeight)
30-
{
31-
if (dirtyRects.empty())
32-
return 0;
33-
34-
// For a single rect, just return its area
35-
if (dirtyRects.size() == 1)
36-
{
37-
const auto& rect = dirtyRects[0];
38-
return static_cast<size_t>(rect.width) * static_cast<size_t>(rect.height);
39-
}
40-
41-
// For multiple rects, calculate bounding box area as upper bound estimate
42-
// This is faster than precise overlap calculation and gives good results
43-
int minX = frameWidth, minY = frameHeight;
44-
int maxX = 0, maxY = 0;
45-
size_t totalRectArea = 0;
46-
47-
for (const auto& rect : dirtyRects)
48-
{
49-
if (rect.width <= 0 || rect.height <= 0)
50-
continue;
51-
52-
minX = std::min(minX, rect.x);
53-
minY = std::min(minY, rect.y);
54-
maxX = std::max(maxX, rect.x + rect.width);
55-
maxY = std::max(maxY, rect.y + rect.height);
56-
totalRectArea += static_cast<size_t>(rect.width) * static_cast<size_t>(rect.height);
57-
}
58-
59-
// Use the smaller of: sum of rect areas, or bounding box area
60-
// This gives a reasonable estimate without expensive overlap calculation
61-
const size_t boundingArea = static_cast<size_t>(maxX - minX) * static_cast<size_t>(maxY - minY);
62-
return std::min(totalRectArea, boundingArea);
63-
}
64-
65-
// Check if two rects are adjacent or overlapping (can be merged)
66-
bool RectsAreAdjacent(const CefRect& a, const CefRect& b, int tolerance = 1)
67-
{
68-
// Check if rects overlap or touch within tolerance
69-
return !(a.x + a.width + tolerance < b.x ||
70-
b.x + b.width + tolerance < a.x ||
71-
a.y + a.height + tolerance < b.y ||
72-
b.y + b.height + tolerance < a.y);
73-
}
74-
75-
// Merge two rects into their bounding box
76-
CefRect MergeRects(const CefRect& a, const CefRect& b)
77-
{
78-
const int minX = std::min(a.x, b.x);
79-
const int minY = std::min(a.y, b.y);
80-
const int maxX = std::max(a.x + a.width, b.x + b.width);
81-
const int maxY = std::max(a.y + a.height, b.y + b.height);
82-
return CefRect(minX, minY, maxX - minX, maxY - minY);
83-
}
84-
85-
// Merge adjacent dirty rects to reduce number of copy operations
86-
// Returns optimized list with fewer, larger rects
87-
std::vector<CefRect> MergeAdjacentRects(const CefRenderHandler::RectList& dirtyRects)
88-
{
89-
if (dirtyRects.size() <= 1)
90-
return std::vector<CefRect>(dirtyRects.begin(), dirtyRects.end());
91-
92-
std::vector<CefRect> merged(dirtyRects.begin(), dirtyRects.end());
93-
94-
// Simple greedy merge - keep merging until no more merges possible
95-
// Limited iterations to prevent excessive processing
96-
constexpr int maxIterations = 3;
97-
for (int iter = 0; iter < maxIterations; ++iter)
98-
{
99-
bool mergedAny = false;
100-
101-
for (size_t i = 0; i < merged.size() && merged.size() > 1; ++i)
102-
{
103-
for (size_t j = i + 1; j < merged.size(); ++j)
104-
{
105-
if (RectsAreAdjacent(merged[i], merged[j]))
106-
{
107-
merged[i] = MergeRects(merged[i], merged[j]);
108-
merged.erase(merged.begin() + j);
109-
mergedAny = true;
110-
break;
111-
}
112-
}
113-
if (mergedAny)
114-
break;
115-
}
116-
117-
if (!mergedAny)
118-
break;
119-
}
120-
121-
return merged;
122-
}
12323
}
12424

12525
CWebView::CWebView(bool bIsLocal, CWebBrowserItem* pWebBrowserRenderItem, bool bTransparent)
@@ -389,10 +289,7 @@ void CWebView::SetRenderingPaused(bool bPaused)
389289
m_RenderData.popupShown = false;
390290
m_RenderData.buffer.reset();
391291
m_RenderData.bufferSize = 0;
392-
m_RenderData.dirtyRects.clear();
393-
m_RenderData.dirtyRects.shrink_to_fit();
394292
m_RenderData.popupBuffer.reset();
395-
m_RenderData.needsFullCopy = true; // Force full copy when unpaused
396293
}
397294
}
398295
}
@@ -468,7 +365,20 @@ void CWebView::UpdateTexture()
468365
if (m_RenderData.changed && (m_pWebBrowserRenderItem->m_uiSizeX != m_RenderData.width || m_pWebBrowserRenderItem->m_uiSizeY != m_RenderData.height))
469366
{
470367
m_RenderData.changed = false;
471-
m_RenderData.needsFullCopy = true; // Force full copy after size change
368+
}
369+
370+
// After device reset (minimize/restore), force full copy from our buffer to new texture
371+
if (m_pWebBrowserRenderItem->m_bTextureWasRecreated)
372+
{
373+
m_pWebBrowserRenderItem->m_bTextureWasRecreated = false;
374+
375+
// If we have valid buffer data matching texture size, trigger full update
376+
if (m_RenderData.buffer && m_RenderData.bufferSize > 0 &&
377+
m_RenderData.width == static_cast<int>(m_pWebBrowserRenderItem->m_uiSizeX) &&
378+
m_RenderData.height == static_cast<int>(m_pWebBrowserRenderItem->m_uiSizeY))
379+
{
380+
m_RenderData.changed = true;
381+
}
472382
}
473383

474384
if (m_RenderData.changed || m_RenderData.popupShown) [[likely]]
@@ -516,109 +426,48 @@ void CWebView::UpdateTexture()
516426
{
517427
m_RenderData.changed = false;
518428

519-
const auto& dirtyRects = m_RenderData.dirtyRects;
520-
const auto frameArea = static_cast<size_t>(m_RenderData.width) * static_cast<size_t>(m_RenderData.height);
521-
522-
// Determine if we should do full frame copy or partial dirty rect update
523-
// Always do full copy on first update (texture may have garbage data)
524-
// Full copy is also more efficient when dirty area exceeds threshold
525-
bool doFullCopy = m_RenderData.needsFullCopy || dirtyRects.empty() ||
526-
(dirtyRects.size() == 1 && dirtyRects[0].width == m_RenderData.width && dirtyRects[0].height == m_RenderData.height);
527-
528-
if (!doFullCopy && frameArea > 0)
429+
// Always do full frame copy since D3DLOCK_DISCARD invalidates entire texture
430+
// Our buffer contains the complete frame from OnPaint's full memcpy
431+
if (destPitch == sourcePitch) [[likely]]
529432
{
530-
const auto dirtyArea = CalculateDirtyArea(dirtyRects, m_RenderData.width, m_RenderData.height);
531-
doFullCopy = (static_cast<float>(dirtyArea) / static_cast<float>(frameArea)) > DIRTY_RECT_THRESHOLD;
532-
}
533-
534-
if (doFullCopy)
535-
{
536-
// Clear the needsFullCopy flag after we do a full copy
537-
m_RenderData.needsFullCopy = false;
538-
539-
// Full frame update - copy entire buffer
540-
if (destPitch == sourcePitch) [[likely]]
541-
{
542-
if (m_RenderData.height > 0 &&
543-
static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(destPitch)) [[unlikely]]
544-
{
545-
pSurface->UnlockRect();
546-
m_RenderData.changed = false;
547-
m_RenderData.popupShown = false;
548-
return;
549-
}
550-
std::memcpy(destData, sourceData, static_cast<size_t>(destPitch) * static_cast<size_t>(m_RenderData.height));
551-
}
552-
else
433+
if (m_RenderData.height > 0 &&
434+
static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(destPitch)) [[unlikely]]
553435
{
554-
// Row-by-row copy when pitches differ
555-
if (destPitch <= 0 || sourcePitch <= 0) [[unlikely]]
556-
{
557-
pSurface->UnlockRect();
558-
m_RenderData.changed = false;
559-
m_RenderData.popupShown = false;
560-
return;
561-
}
562-
563-
if (m_RenderData.height > 0 &&
564-
(static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(destPitch) ||
565-
static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(sourcePitch))) [[unlikely]]
566-
{
567-
pSurface->UnlockRect();
568-
m_RenderData.changed = false;
569-
m_RenderData.popupShown = false;
570-
return;
571-
}
572-
573-
for (int y = 0; y < m_RenderData.height; ++y)
574-
{
575-
const auto sourceIndex = static_cast<size_t>(y) * static_cast<size_t>(sourcePitch);
576-
const auto destIndex = static_cast<size_t>(y) * static_cast<size_t>(destPitch);
577-
const auto copySize = std::min(static_cast<size_t>(sourcePitch), static_cast<size_t>(destPitch));
578-
579-
std::memcpy(&destData[destIndex], &sourceData[sourceIndex], copySize);
580-
}
436+
pSurface->UnlockRect();
437+
m_RenderData.changed = false;
438+
m_RenderData.popupShown = false;
439+
return;
581440
}
441+
std::memcpy(destData, sourceData, static_cast<size_t>(destPitch) * static_cast<size_t>(m_RenderData.height));
582442
}
583443
else
584444
{
585-
// Partial update using optimized dirty rects
586-
if (m_RenderData.height > 0 &&
587-
static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(destPitch)) [[unlikely]]
445+
// Row-by-row copy when pitches differ
446+
if (destPitch <= 0 || sourcePitch <= 0) [[unlikely]]
588447
{
589448
pSurface->UnlockRect();
590449
m_RenderData.changed = false;
591450
m_RenderData.popupShown = false;
592451
return;
593452
}
594453

595-
// Merge adjacent rects to reduce number of copy operations
596-
const auto mergedRects = MergeAdjacentRects(dirtyRects);
597-
598-
for (const auto& rect : mergedRects)
454+
if (m_RenderData.height > 0 &&
455+
(static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(destPitch) ||
456+
static_cast<size_t>(m_RenderData.height) > SIZE_MAX / static_cast<size_t>(sourcePitch))) [[unlikely]]
599457
{
600-
if (rect.x < 0 || rect.y < 0 || rect.width <= 0 || rect.height <= 0) [[unlikely]]
601-
continue;
602-
603-
if (rect.x >= m_RenderData.width || rect.y >= m_RenderData.height ||
604-
rect.width > m_RenderData.width || rect.height > m_RenderData.height ||
605-
rect.x > m_RenderData.width - rect.width || rect.y > m_RenderData.height - rect.height) [[unlikely]]
606-
continue;
607-
608-
const auto rectEndY = rect.y + rect.height;
609-
610-
if (static_cast<size_t>(destPitch) < static_cast<size_t>(rect.x + rect.width) * CEF_PIXEL_STRIDE) [[unlikely]]
611-
continue;
458+
pSurface->UnlockRect();
459+
m_RenderData.changed = false;
460+
m_RenderData.popupShown = false;
461+
return;
462+
}
612463

613-
for (int y = rect.y; y < rectEndY; ++y)
614-
{
615-
const auto sourceIndex = static_cast<size_t>(y) * static_cast<size_t>(sourcePitch) +
616-
static_cast<size_t>(rect.x) * CEF_PIXEL_STRIDE;
617-
const auto destIndex = static_cast<size_t>(y) * static_cast<size_t>(destPitch) +
618-
static_cast<size_t>(rect.x) * CEF_PIXEL_STRIDE;
464+
for (int y = 0; y < m_RenderData.height; ++y)
465+
{
466+
const auto sourceIndex = static_cast<size_t>(y) * static_cast<size_t>(sourcePitch);
467+
const auto destIndex = static_cast<size_t>(y) * static_cast<size_t>(destPitch);
468+
const auto copySize = std::min(static_cast<size_t>(sourcePitch), static_cast<size_t>(destPitch));
619469

620-
std::memcpy(&destData[destIndex], &sourceData[sourceIndex], static_cast<size_t>(rect.width) * CEF_PIXEL_STRIDE);
621-
}
470+
std::memcpy(&destData[destIndex], &sourceData[sourceIndex], copySize);
622471
}
623472
}
624473
}
@@ -672,10 +521,6 @@ void CWebView::UpdateTexture()
672521
m_RenderData.changed = false;
673522
m_RenderData.popupShown = false;
674523
}
675-
676-
// Clear dirty rects to prevent memory accumulation
677-
m_RenderData.dirtyRects.clear();
678-
m_RenderData.dirtyRects.shrink_to_fit();
679524
}
680525
}
681526

@@ -1218,8 +1063,6 @@ void CWebView::OnPaint(CefRefPtr<CefBrowser> browser, CefRenderHandler::PaintEle
12181063

12191064
m_RenderData.width = width;
12201065
m_RenderData.height = height;
1221-
m_RenderData.dirtyRects = dirtyRects;
1222-
m_RenderData.dirtyRects.shrink_to_fit();
12231066
m_RenderData.changed = true;
12241067
}
12251068

Client/cefweb/CWebView.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,13 @@ class CWebView : public CWebViewInterface,
276276
struct
277277
{
278278
bool changed = false;
279-
bool needsFullCopy = true; // Force full copy on first update or after texture reset
280279
std::mutex dataMutex;
281280

282281
// Main frame buffer - we now own this buffer (copied in OnPaint)
283282
std::unique_ptr<byte[]> buffer;
284283
size_t bufferSize = 0;
285284
int width = 0;
286285
int height = 0;
287-
CefRenderHandler::RectList dirtyRects;
288286

289287
CefRect popupRect;
290288
bool popupShown = false;

Client/core/Graphics/CRenderItem.WebBrowser.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ void CWebBrowserItem::OnLostDevice()
7575
void CWebBrowserItem::OnResetDevice()
7676
{
7777
CreateUnderlyingData();
78+
m_bTextureWasRecreated = true; // Force full repaint after device reset
7879
}
7980

8081
////////////////////////////////////////////////////////////////

Client/sdk/core/CRenderItemManagerInterface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,4 +563,5 @@ class CWebBrowserItem : public CTextureItem
563563
virtual void Resize(const CVector2D& size);
564564

565565
IDirect3DSurface9* m_pD3DRenderTargetSurface;
566+
bool m_bTextureWasRecreated = false; // Set after device reset to force full repaint
566567
};

0 commit comments

Comments
 (0)