Fixed a regression in UIWebView that caused to basically don't have render culling:

- Added UISceneNode::getVisibleWorldBounds() with an optional bounds-node binding.
  - UIWebView binds its document scene to the scroll container’s clipped viewport.
  - UINode::nodeDraw() now culls against visible scene bounds instead of the full document extent.
  - Existing top-level scenes retain their previous behavior.
  - Added a regression test confirming offscreen document widgets are skipped and visible widgets render.
This commit is contained in:
Martín Lucas Golini
2026-08-01 01:16:13 -03:00
parent 24302ec9f1
commit 323d511eed
5 changed files with 47 additions and 1 deletions

View File

@@ -133,6 +133,19 @@ class EE_API UISceneNode : public SceneNode {
/** @return The explicit viewport size, or the scene extent when no override is set. */
const Sizef& getViewportPixelsSize() const;
/**
* @brief Sets the node whose world bounds delimit the visible portion of this scene.
*
* Embedded scenes can have an extent larger than their host viewport. Binding the visible
* bounds to the host's clipping node lets render culling use that viewport while preserving the
* full scene extent for layout and scrolling. The node must outlive this scene or clear the
* binding before it is destroyed.
*/
void setVisibleBoundsNode( Node* node );
/** @return The visible world bounds used for render culling. */
const Rectf& getVisibleWorldBounds();
/**
* @brief Sets the layout viewport size used by the scene root as the initial containing block.
*
@@ -975,6 +988,7 @@ class EE_API UISceneNode : public SceneNode {
Uint32 mCurrentMarker{ 0 };
Sizef mViewportPixelsSize;
bool mHasViewportPixelsSize{ false };
Node* mVisibleBoundsNode{ nullptr };
Sizef mLayoutViewportPixelsSize;
bool mHasLayoutViewportPixelsSize{ false };
bool mFollowParentSize{ true };

View File

@@ -1113,7 +1113,7 @@ void UINode::nodeDraw() {
smartClipStart( ClipType::BorderBox, needsClipPlanes );
bool intersected = mWorldBounds.intersect( mSceneNode->getWorldBounds() );
bool intersected = mWorldBounds.intersect( mUISceneNode->getVisibleWorldBounds() );
if ( intersected ) {
smartClipStart( ClipType::ContentBox, needsClipPlanes );

View File

@@ -1075,6 +1075,14 @@ const Sizef& UISceneNode::getViewportPixelsSize() const {
return mHasViewportPixelsSize ? mViewportPixelsSize : getPixelsSize();
}
void UISceneNode::setVisibleBoundsNode( Node* node ) {
mVisibleBoundsNode = node;
}
const Rectf& UISceneNode::getVisibleWorldBounds() {
return mVisibleBoundsNode ? mVisibleBoundsNode->getWorldBounds() : getWorldBounds();
}
void UISceneNode::setLayoutViewportPixelsSize( const Sizef& size ) {
if ( mHasLayoutViewportPixelsSize && mLayoutViewportPixelsSize == size )
return;

View File

@@ -168,6 +168,7 @@ UIWebView::UIWebView() : UIScrollView( "webview" ) {
mDocumentScene = UISceneNode::New();
mDocumentScene->setFollowParentSize( false );
mDocumentScene->setVisibleBoundsNode( mContainer );
mDocumentScene->setParent( mDocumentLayout );
mDocContainer = UIWebViewDocumentContainer::New();

View File

@@ -43,6 +43,15 @@ using namespace EE::Window;
using namespace EE::Scene;
using namespace EE::UI;
class CountingDrawWidget : public UIWidget {
public:
static CountingDrawWidget* New() { return eeNew( CountingDrawWidget, () ); }
void draw() override { drawCount++; }
int drawCount{ 0 };
};
static bool readHttpRequestHeaders( TcpSocket& client, std::string* headers = nullptr ) {
std::string request;
char buffer[1024];
@@ -144,6 +153,20 @@ UTEST( UIWebView, OwnedDocumentSceneScrollTarget ) {
EXPECT_NEAR( documentScene->getViewportPixelsSize().getHeight(), 300.f, 1.f );
EXPECT_GT( documentScene->getPixelsSize().getHeight(), 1000.f );
EXPECT_TRUE( webView->getVerticalScrollBar()->isVisible() );
EXPECT_TRUE( documentScene->getVisibleWorldBounds() ==
webView->getContainer()->getWorldBounds() );
EXPECT_LT( documentScene->getVisibleWorldBounds().getHeight(),
documentScene->getWorldBounds().getHeight() );
auto* drawProbe = CountingDrawWidget::New();
drawProbe->setPixelsSize( 20.f, 20.f );
drawProbe->setPixelsPosition( 10.f, 700.f );
drawProbe->setParent( documentScene->getRoot() );
drawProbe->nodeDraw();
EXPECT_EQ( drawProbe->drawCount, 0 );
drawProbe->setPixelsPosition( 10.f, 10.f );
drawProbe->nodeDraw();
EXPECT_EQ( drawProbe->drawCount, 1 );
ASSERT_TRUE( documentScene->getParent() != nullptr && documentScene->getParent()->isUINode() );
UINode* scrollTarget = documentScene->getParent()->asType<UINode>();