Skip to content

Improve CEF DX utilization & thread-safety fixes #2831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 84 additions & 77 deletions Client/cefweb/CWebView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
#include <cef3/cef/include/cef_task.h>
#include "CWebDevTools.h"

namespace
{
const int CEF_PIXEL_STRIDE = 4;
}

CWebView::CWebView(bool bIsLocal, CWebBrowserItem* pWebBrowserRenderItem, bool bTransparent)
{
m_bIsLocal = bIsLocal;
Expand All @@ -39,9 +44,6 @@ CWebView::~CWebView()
// Ensure that CefRefPtr::~CefRefPtr doesn't try to release it twice (it has already been released in CWebView::OnBeforeClose)
m_pWebView = nullptr;

// Make sure we don't dead lock the CEF render thread
m_RenderData.cv.notify_all();

OutputDebugLine("CWebView::~CWebView");
}

Expand Down Expand Up @@ -75,9 +77,6 @@ void CWebView::CloseBrowser()
// CefBrowserHost::CloseBrowser calls the destructor after the browser has been destroyed
m_bBeingDestroyed = true;

// Make sure we don't dead lock the CEF render thread
m_RenderData.cv.notify_all();

if (m_pWebView)
m_pWebView->GetHost()->CloseBrowser(true);
}
Expand Down Expand Up @@ -182,15 +181,17 @@ void CWebView::ClearTexture()
IDirect3DSurface9* pD3DSurface = m_pWebBrowserRenderItem->m_pD3DRenderTargetSurface;
if (!pD3DSurface)
return;

D3DLOCKED_RECT LockedRect;

D3DSURFACE_DESC SurfaceDesc;
if (FAILED(pD3DSurface->GetDesc(&SurfaceDesc)))
return;

pD3DSurface->GetDesc(&SurfaceDesc);
pD3DSurface->LockRect(&LockedRect, NULL, 0);

memset(LockedRect.pBits, 0xFF, SurfaceDesc.Width * SurfaceDesc.Height * 4);
pD3DSurface->UnlockRect();
D3DLOCKED_RECT LockedRect;
if (SUCCEEDED(pD3DSurface->LockRect(&LockedRect, NULL, D3DLOCK_DISCARD)))
{
memset(LockedRect.pBits, 0xFF, SurfaceDesc.Height * LockedRect.Pitch);
pD3DSurface->UnlockRect();
}
}

void CWebView::UpdateTexture()
Expand All @@ -210,61 +211,76 @@ void CWebView::UpdateTexture()
{
// Lock surface
D3DLOCKED_RECT LockedRect;
pSurface->LockRect(&LockedRect, nullptr, 0);

// Dirty rect implementation, don't use this as loops are significantly slower than memcpy
auto surfaceData = static_cast<byte*>(LockedRect.pBits);
auto sourceData = static_cast<const byte*>(m_RenderData.buffer);
auto pitch = LockedRect.Pitch;

// Update view area
if (m_RenderData.changed)
if (SUCCEEDED(pSurface->LockRect(&LockedRect, nullptr, 0)))
{
// Update changed state
m_RenderData.changed = false;

if (m_RenderData.dirtyRects.size() > 0 && m_RenderData.dirtyRects[0].width == m_RenderData.width &&
m_RenderData.dirtyRects[0].height == m_RenderData.height)
// Dirty rect implementation, don't use this as loops are significantly slower than memcpy
const auto destData = static_cast<byte*>(LockedRect.pBits);
const auto sourceData = static_cast<const byte*>(m_RenderData.buffer);
const auto destPitch = LockedRect.Pitch;
const auto sourcePitch = m_RenderData.width * CEF_PIXEL_STRIDE;

// Update view area
if (m_RenderData.changed)
{
// Update whole texture
memcpy(surfaceData, sourceData, m_RenderData.width * m_RenderData.height * 4);
}
else
{
// Update dirty rects
for (auto& rect : m_RenderData.dirtyRects)
// Update changed state
m_RenderData.changed = false;

if (m_RenderData.dirtyRects.size() > 0 && m_RenderData.dirtyRects[0].width == m_RenderData.width &&
m_RenderData.dirtyRects[0].height == m_RenderData.height)
{
for (int y = rect.y; y < rect.y + rect.height; ++y)
// Note that D3D texture size can be hardware dependent(especially with dynamic texture)
// When destination and source pitches differ we must copy pixels row by row
if (destPitch == sourcePitch)
memcpy(destData, sourceData, destPitch * m_RenderData.height);
else
{
int index = y * pitch + rect.x * 4;
memcpy(&surfaceData[index], &sourceData[index], rect.width * 4);
for (int y = 0; y < m_RenderData.height; ++y)
{
const int sourceIndex = y * sourcePitch;
const int destIndex = y * destPitch;

memcpy(&destData[destIndex], &sourceData[sourceIndex], std::min(sourcePitch, destPitch));
}
}
}
else
{
// Update dirty rects
for (const auto& rect : m_RenderData.dirtyRects)
{
for (int y = rect.y; y < rect.y + rect.height; ++y)
{
// Note that D3D texture size can be hardware dependent(especially with dynamic texture)
// We cannot be sure that source and destination pitches are the same
const int sourceIndex = y * sourcePitch + rect.x * CEF_PIXEL_STRIDE;
const int destIndex = y * destPitch + rect.x * CEF_PIXEL_STRIDE;

memcpy(&destData[destIndex], &sourceData[sourceIndex], rect.width * CEF_PIXEL_STRIDE);
}
}
}
}
}

// Update popup area (override certain areas of the view texture)
bool popupSizeMismatches = m_RenderData.popupRect.x + m_RenderData.popupRect.width >= (int)m_pWebBrowserRenderItem->m_uiSizeX ||
m_RenderData.popupRect.y + m_RenderData.popupRect.height >= (int)m_pWebBrowserRenderItem->m_uiSizeY;
// Update popup area (override certain areas of the view texture)
const bool popupSizeMismatches = m_RenderData.popupRect.x + m_RenderData.popupRect.width >= (int)m_pWebBrowserRenderItem->m_uiSizeX ||
m_RenderData.popupRect.y + m_RenderData.popupRect.height >= (int)m_pWebBrowserRenderItem->m_uiSizeY;

if (m_RenderData.popupShown && !popupSizeMismatches)
{
auto popupPitch = m_RenderData.popupRect.width * 4;
for (int y = 0; y < m_RenderData.popupRect.height; ++y)
if (m_RenderData.popupShown && !popupSizeMismatches)
{
int sourceIndex = y * popupPitch;
int destIndex = (y + m_RenderData.popupRect.y) * pitch + m_RenderData.popupRect.x * 4;
const auto popupPitch = m_RenderData.popupRect.width * CEF_PIXEL_STRIDE;
for (int y = 0; y < m_RenderData.popupRect.height; ++y)
{
const int sourceIndex = y * popupPitch;
const int destIndex = (y + m_RenderData.popupRect.y) * destPitch + m_RenderData.popupRect.x * CEF_PIXEL_STRIDE;

memcpy(&surfaceData[destIndex], &m_RenderData.popupBuffer[sourceIndex], popupPitch);
memcpy(&destData[destIndex], &m_RenderData.popupBuffer[sourceIndex], popupPitch);
}
}
}

// Unlock surface
pSurface->UnlockRect();
// Unlock surface
pSurface->UnlockRect();
}
}

// Resume CEF render thread
m_RenderData.cv.notify_all();
}

void CWebView::ExecuteJavascript(const SString& strJavascriptCode)
Expand Down Expand Up @@ -431,9 +447,6 @@ void CWebView::Resize(const CVector2D& size)
// Send resize event to CEF
if (m_pWebView)
m_pWebView->GetHost()->WasResized();

// Tell CEF to render a new frame
m_RenderData.cv.notify_all();
}

CVector2D CWebView::GetSize()
Expand Down Expand Up @@ -661,7 +674,7 @@ void CWebView::OnPopupSize(CefRefPtr<CefBrowser> browser, const CefRect& rect)
m_RenderData.popupRect = rect;

// Resize buffer
m_RenderData.popupBuffer.reset(new byte[rect.width * rect.height * 4]);
m_RenderData.popupBuffer.reset(new byte[rect.width * rect.height * CEF_PIXEL_STRIDE]);
}

////////////////////////////////////////////////////////////////////
Expand All @@ -677,31 +690,25 @@ void CWebView::OnPaint(CefRefPtr<CefBrowser> browser, CefRenderHandler::PaintEle
if (m_bBeingDestroyed)
return;

std::unique_lock<std::mutex> lock(m_RenderData.dataMutex);

// Copy popup buffer
if (paintType == PET_POPUP)
{
std::lock_guard<std::mutex> lock(m_RenderData.dataMutex);

// Copy popup buffer
if (paintType == PET_POPUP)
if (m_RenderData.popupBuffer)
{
if (m_RenderData.popupBuffer)
{
memcpy(m_RenderData.popupBuffer.get(), buffer, width * height * 4);
}

return; // We don't have to wait as we've copied the buffer already
memcpy(m_RenderData.popupBuffer.get(), buffer, width * height * CEF_PIXEL_STRIDE);
}

// Store render data
m_RenderData.buffer = buffer;
m_RenderData.width = width;
m_RenderData.height = height;
m_RenderData.dirtyRects = dirtyRects;
m_RenderData.changed = true;
return; // We don't have to wait as we've copied the buffer already
}

// Wait for the main thread to handle drawing the texture
std::unique_lock<std::mutex> lock(m_RenderData.cvMutex);
m_RenderData.cv.wait(lock);
// Store render data
m_RenderData.buffer = buffer;
m_RenderData.width = width;
m_RenderData.height = height;
m_RenderData.dirtyRects = dirtyRects;
m_RenderData.changed = true;
}

////////////////////////////////////////////////////////////////////
Expand Down
4 changes: 1 addition & 3 deletions Client/cefweb/CWebView.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class CWebView : public CWebViewInterface,
CefRefPtr<CefBrowser> m_pWebView;
CWebBrowserItem* m_pWebBrowserRenderItem;

bool m_bBeingDestroyed;
std::atomic_bool m_bBeingDestroyed;
bool m_bIsLocal;
bool m_bIsRenderingPaused;
bool m_bIsTransparent;
Expand All @@ -192,8 +192,6 @@ class CWebView : public CWebViewInterface,
{
bool changed = false;
std::mutex dataMutex;
std::mutex cvMutex;
std::condition_variable cv;

const void* buffer;
int width, height;
Expand Down
16 changes: 9 additions & 7 deletions Client/core/Graphics/CRenderItem.WebBrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,24 @@ void CWebBrowserItem::CreateUnderlyingData()
assert(!m_pD3DRenderTargetSurface);
assert(!m_pD3DTexture);

D3DXCreateTexture(m_pDevice, m_uiSizeX, m_uiSizeY, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, (IDirect3DTexture9**)&m_pD3DTexture);
// Check if texture is actually created. It can be failed in some conditions(e.g. lack of memory).
if (FAILED(D3DXCreateTexture(m_pDevice, m_uiSizeX, m_uiSizeY, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, (IDirect3DTexture9**)&m_pD3DTexture)) || !m_pD3DTexture)
return;

// Check texture created
if (!m_pD3DTexture)
// Get the render target surface here for convenience
if (FAILED(((IDirect3DTexture9*)m_pD3DTexture)->GetSurfaceLevel(0, &m_pD3DRenderTargetSurface)) || !m_pD3DRenderTargetSurface)
{
SAFE_RELEASE(m_pD3DTexture);
return;
}

// D3DXCreateTexture sets width and height to 1 if the argument value was 0
// See: https://docs.microsoft.com/en-us/windows/desktop/direct3d9/d3dxcreatetexture
if (m_uiSizeX == 0)
m_uiSizeX = 1;

if (m_uiSizeY == 0)
m_uiSizeY = 1;

// Get the render target surface here for convenience
((IDirect3DTexture9*)m_pD3DTexture)->GetSurfaceLevel(0, &m_pD3DRenderTargetSurface);
m_uiSizeY = 1;

// Update surface size, although it probably will be unchanged | Todo: Remove this
D3DSURFACE_DESC desc;
Expand Down