-
Notifications
You must be signed in to change notification settings - Fork 3
Restructure rendering pipeline with type-safe container classes, per-container stencil isolation, and optimized VAO/VBO caching #225
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
Conversation
Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
… mask rendering Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
…scrollable container overflow Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
| for (const auto &[layer, layerData] : layeredInstances_) | ||
| { | ||
| if (layerData.renderableInstances && layerData.renderableInstances->count() > 0) | ||
| callback(layer, *layerData.renderableInstances); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot 这里可以直接返回 layerData.renderableInstances 和 layerData. scrollableContainerInstances,而不是分成两个方法。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated iterateLayers to directly return both layerData.renderableInstances and layerData.scrollableContainerInstances via function parameters, eliminating the need for separate methods. Commit 0337380
| * Iterate through scrollable container instances for mask/stencil rendering. | ||
| * These instances are used to create masks that constrain rendering to scrollable regions. | ||
| */ | ||
| inline void iterateScrollableContainerLayers(std::function<void(RenderLayer, RenderableInstancesList &)> callback) const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot 不需要这个方法,因为在 iterateLayers 就可以遍历每一层的 scrollable container instances 和可渲染的 instances。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed iterateScrollableContainerLayers method as iterateLayers now provides access to both scrollable container instances and renderable instances for each layer. Commit 0337380
| { | ||
| // Check if this layer has scrollable containers for masking | ||
| bool hasScrollableContainers = false; | ||
| instancedMesh.iterateScrollableContainerLayers([&](RenderLayer scrollableLayer, RenderableInstancesList &scrollableList) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
由于现在 iterateLayers 会传入 scrollable container instances 和可渲染的 instances,因此这里不再需要使用 iterateScrollableContainerLayers 去遍历了,只需要按照描述的逻辑,先渲染 mask,再渲染可渲染实例
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the material rendering to use the simplified iterateLayers API that directly provides both instance lists. The logic now renders masks first, then renderable instances, without needing to call iterateScrollableContainerLayers. Commit 0337380
…s and remove redundant method Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
| * - renderableInstances: Current instances used for normal rendering | ||
| * - scrollableContainerInstances: All scrollable container instances used for mask/stencil rendering | ||
| */ | ||
| struct LayeredInstancesData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot 现在的实现会有一个问题,当同一个层有多个 scrollable container 时,由于使用了单次 instanced draw call,因此 stencil buffer 的结果也只能是一种,因此当绘制下一层时,会出现其中一个 container 中的元素被隐藏的部分正好会显示在另一个 container 中,因此需要调整渲染逻辑为:
- 渲染 container mask 时,需要一个个去渲染,而不是一次性把所有 mask 都绘制了
- 渲染 container 内部元素时,需要按照当前层 + 当前 container id 来渲染(这意味着每个 instance 都需要存储当前所属的 container id)
基于这个渲染逻辑,你需要:
- 调整 LayeredInstancesData 的结构,现在只需要一个 container instance,但是需要添加对应的 RenderLayer
- 修改 layeredInstances_ 为 vector(保证按照 layer 顺序 0 - 1 - 2)
- 调整 LayoutObject 中更新 WebContent layer 相关数据的逻辑,需要保证每个 WebContent 都有一个对应的 scrollable container entity(作为之前说的 container id),因为每次是递归调用 updateLayer(),因此实现这个调整需要保证更新性能,减少冗余查询
- 调整渲染逻辑,即遍历 layeredInstances_,每次先使用 drawElements 先渲染 mask(container),然后再渲染对应的 renderables 对象
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're absolutely right about the stencil buffer collision issue. The current implementation renders all scrollable containers in a layer with a single draw call, creating overlapping stencil regions that cause content leakage between containers. I'll implement the container-specific rendering approach:
- Add container ID tracking to InstanceData
- Restructure LayeredInstancesData to represent individual containers
- Change layeredInstances_ to vector for per-container iteration
- Update rendering to mask and render content per container
Starting implementation now.
3022afe to
7779ad2
Compare
|
|
||
| // All container instances for mask/stencil rendering | ||
| // (updated regardless of whether they are rendered in current frame) | ||
| std::shared_ptr<RenderableInstancesList> containerInstances; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot The new LayeredInstancesData should own only one instance for its container, you could add a new class for this that owns a weak reference to the container instance, and you need to abstract this class with RenderableInstancesList, both them will have vao/vbo for renderer using.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated LayeredInstancesData to represent individual containers. The struct now has a single containerInstance (weak reference via shared_ptr) and contentInstances, both inheriting from RenderableInstancesList for VAO/VBO access. Added RenderLayer field as requested. Commit b7ebb36
| // All container instances for mask/stencil rendering | ||
| // (updated regardless of whether they are rendered in current frame) | ||
| std::shared_ptr<RenderableInstancesList> containerInstances; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to add a new RenderLayer field for LayeredInstancesData.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added RenderLayer field to LayeredInstancesData structure as requested. Commit b7ebb36
| std::shared_ptr<RenderableInstancesList> opaqueInstances_; | ||
| std::shared_ptr<RenderableInstancesList> transparentInstances_; | ||
| std::map<RenderLayer, std::shared_ptr<RenderableInstancesList>> layeredInstances_; | ||
| std::map<RenderLayer, LayeredInstancesData> layeredInstances_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| std::map<RenderLayer, LayeredInstancesData> layeredInstances_; | |
| std::vector<LayeredInstancesData> layeredInstances_; |
Now this map should be converted to a vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converted layeredInstances_ from std::map to std::vector as suggested. Updated corresponding logic to work with vector-based iteration. Commit b7ebb36
src/client/layout/layout_object.cpp
Outdated
| else | ||
| { | ||
| webContent->setIsScrollableContainer(false); | ||
| webContent->setBelongsToScrollableContainer(containingScrollContainer()->entity()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please optimize containingScrollContainer(), now it walks from the current node up until found a scrollable container, may you introduce a cache for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimized containingScrollContainer() with caching using mutable cached_containing_scroll_container_ field. Added cache invalidation in setParent() when parent hierarchy changes. This avoids repeated tree walks for the same container lookups. Commit b7ebb36
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Revert the caching in the containingScrollContainer() method, actually updateLayer will do walk for node's children, and it will call recalcLayer to the parent's layer and check if current is a scrollable container, you may redesign this process to get each node's scrollable container without calling containingScrollContainer ().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted the caching optimization in containingScrollContainer() method back to the simple tree walking implementation. The cache has been removed from both the header and implementation files. Commit aed25cf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reverted the caching optimization in containingScrollContainer() method. The method now returns to its original simple implementation that walks up the parent tree to find scroll containers without caching. Commit aed25cf
…rollContainer caching - Restructure LayeredInstancesData to represent individual containers with layer and containerId fields - Convert layeredInstances_ from map to vector for per-container iteration - Update iterateLayers callback signature for per-container processing - Add containerId field to InstanceData for container identification - Implement setContainerId method for instance container assignment - Refactor material rendering to use per-container stencil isolation - Add caching to containingScrollContainer() method with invalidation on parent changes Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
| } | ||
|
|
||
| void RenderableInstancesList::update(const InstanceMap &instances, SortingOrder sortingOrder) | ||
| void InstanceListBase::update(const InstanceMap &instances, SortingOrder sortingOrder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot update() in InstanceListBase must not have update(), because only the contents instances list needs to update from a InstanceMap with sorting, the container instance could use a setInstance() method enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed update() method from InstanceListBase and moved it to ContentInstancesList only, since container instances don't need complex updating from InstanceMap. Added setInstance() method to ContainerInstance for simple single instance assignment. Commit aed25cf
| } | ||
|
|
||
| void RenderableInstancesList::beforeInstancedDraw(WebGL2Context &glContext, CSSBorderDataTexture *borderDataTexture) | ||
| void InstanceListBase::beforeInstancedDraw(WebGL2Context &glContext, CSSBorderDataTexture *borderDataTexture) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The beforeInstancedDraw and afterInstancedDraw can be virtual, because borderDataTexture is only used by the content instances list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made beforeInstancedDraw and afterInstancedDraw virtual methods in InstanceListBase since borderDataTexture is only used by content instances, allowing for specialized implementations in derived classes. Commit aed25cf
| uint32_t containerId; // Unique container ID (0 = no container) | ||
| std::shared_ptr<RenderableInstancesList> containerInstance; // Single container mask | ||
| std::shared_ptr<RenderableInstancesList> contentInstances; // Content for this container | ||
| uint32_t containerId; // Unique container ID (0 = no container) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be moved to ContainerInstance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved containerId from LayeredInstancesData to ContainerInstance where it belongs - the container ID is now encapsulated within the container instance itself rather than being duplicated in the data structure. Commit aed25cf
src/client/layout/layout_object.cpp
Outdated
| webContent->setIsScrollableContainer(false); | ||
| webContent->setBelongsToScrollableContainer(containingScrollContainer()->entity()); | ||
| } | ||
| webContent->setIsScrollableContainer(isScrollContainer()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot You are missing the lines to update BelongsToScrollableContainer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added missing setBelongsToScrollableContainer call in updateLayer() method. Now properly updates WebContent components with their containing scroll container entity ID during layer updates. Commit aed25cf
…g container updates Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
| layerData.containerInstance = make_shared<ContainerInstance>(index++, | ||
| glContext->createVertexArray(), | ||
| glContext->createBuffer()); | ||
| layerData.containerInstance->configureAttribs(glContext, program, mesh3d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot You don't need to create vao/vbo and reconfigure them at each time to update, you can cache the LayeredInstancesData by the layer and the container index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented caching system to avoid recreating VAO/VBO on every update. Added layerDataPool_ to cache LayeredInstancesData objects by layer+container ID, and VAO/VBO are now created only once per combination. Commit 056942f
| { | ||
| if (!defaultLayeredData.has_value()) | ||
| { | ||
| defaultLayeredData = LayeredInstancesData(layer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, the default LayeredInstancesData might be initialized once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added defaultLayerData_ class member that's initialized once for non-container instances, eliminating recreation on every update. Commit 056942f
| layerData.contentInstances = make_shared<ContentInstancesList>(InstanceFilter::kTransparent, | ||
| glContext->createVertexArray(), | ||
| glContext->createBuffer()); | ||
| layerData.contentInstances->configureAttribs(glContext, program, mesh3d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot Ditto, you need to initialize (create vao/vbo and configure attribs) the content instances once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Content instances are now initialized once through the caching system. VAO/VBO creation and attribute configuration only happen once per layer+container combination, with existing objects being reused. Commit 056942f
| std::shared_ptr<RenderableInstancesList> transparentInstances_; | ||
| std::map<RenderLayer, std::shared_ptr<RenderableInstancesList>> layeredInstances_; | ||
| std::shared_ptr<RenderableInstancesList> depthOnlyInstances_; | ||
| std::vector<LayeredInstancesData> layeredInstances_; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot As I mentioned above, you need to add a new LayeredInstancesData field for the default layer (no container layer), and then a pool to store LayeredInstancesData objects, and layeredInstances_ might store the sorted pointers to the active LayeredInstancesData objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented the requested architecture: added defaultLayerData_ field for the default layer, created layerDataPool_ to store LayeredInstancesData objects, and changed layeredInstances_ to store sorted pointers to active cached objects. This ensures VAO/VBO resources are created once and reused efficiently. Commit 056942f
…every update Co-authored-by: yorkie <1935767+yorkie@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR restructures the rendering pipeline to support per-container stencil isolation, preventing content leakage between scrollable containers, while adding performance optimizations through VAO/VBO caching and implementing a type-safe class hierarchy for better separation of concerns.
- Implements per-container stencil isolation to prevent content leakage between overlapping scrollable containers
- Adds performance optimizations through VAO/VBO caching system to eliminate redundant OpenGL resource creation
- Introduces type-safe class hierarchy with
InstanceListBase,ContainerInstance, andContentInstancesListfor better separation of concerns
Reviewed Changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/renderer/render_api_opengles.cpp | Enhanced stencil buffer debugging and modified clear buffer logic to allow stencil-only clears |
| src/renderer/gles/common.hpp | Renamed and added utility functions for converting OpenGL enums to strings for debugging |
| src/renderer/gles/common.cpp | Implemented new utility functions for stencil operation and depth/stencil function string conversion |
| src/client/layout/layout_view.cpp | Changed viewport units from percentage to viewport units for proper sizing |
| src/client/layout/layout_text.cpp | Updated to pass scroll container reference to updateLayer method |
| src/client/layout/layout_object.hpp | Modified updateLayer method signature to accept scroll container parameter |
| src/client/layout/layout_object.cpp | Enhanced container tracking by propagating scroll container information through layout hierarchy |
| src/client/layout/layout_box_model_object.cpp | Updated to use new updateLayer signature with scroll container parameter |
| src/client/dom/node.hpp | Removed TODO comment for custom geometry implementation |
| src/client/dom/document_renderer.cpp | Fixed bounding box update to use fragment size instead of content size |
| src/client/builtin_scene/web_content_renderer.cpp | Increased SDF generator spread parameter for better text rendering quality |
| src/client/builtin_scene/web_content.hpp | Added container relationship tracking with belongsToScrollableContainer property |
| src/client/builtin_scene/renderer/scene_renderer.hpp | Removed volume mask functionality and related methods |
| src/client/builtin_scene/renderer/scene_renderer.cpp | Removed volume mask implementation code |
| src/client/builtin_scene/materials/web_content_instanced.cpp | Implemented new per-container stencil rendering logic with mask isolation |
| src/client/builtin_scene/instanced_mesh.hpp | Major restructuring with type-safe class hierarchy and caching system for LayeredInstancesData |
| src/client/builtin_scene/instanced_mesh.cpp | Implemented new class hierarchy, caching system, and container-based data organization |
| src/client/builtin_scene/ecs.hpp | Changed entity ID generator start value from 0 to 0x1 |
| src/client/builtin_scene/css_border_data_texture.hpp | Removed forward declaration of obsolete RenderableInstancesList |
| src/client/builtin_scene/client_renderer.hpp | Removed volume mask rendering method declaration |
| src/client/builtin_scene/client_renderer.cpp | Updated instance property setting and removed volume mask rendering logic |
| fixtures/html/css-overflow.html | Updated test fixture with improved styling and layout for testing overflow behavior |
| int maskRef = ((containerInstance->getContainerIndex() & 0x0f) << 4) | ((0xf - layer.index()) & 0x0f); | ||
| if (layer.index() == 0) | ||
| glContext->stencilFunc(WEBGL_ALWAYS, maskRef, 0xFF); | ||
| else | ||
| glContext->stencilFunc(WEBGL_LESS, maskRef, 0x0f); |
Copilot
AI
Aug 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic numbers 0x0f, 4, and 0xf should be defined as named constants to improve code readability and maintainability. Consider defining constants like CONTAINER_INDEX_MASK = 0x0f, LAYER_SHIFT = 4, etc.
| int maskRef = ((containerInstance->getContainerIndex() & 0x0f) << 4) | ((0xf - layer.index()) & 0x0f); | |
| if (layer.index() == 0) | |
| glContext->stencilFunc(WEBGL_ALWAYS, maskRef, 0xFF); | |
| else | |
| glContext->stencilFunc(WEBGL_LESS, maskRef, 0x0f); | |
| int maskRef = ((containerInstance->getContainerIndex() & CONTAINER_INDEX_MASK) << LAYER_SHIFT) | ((LAYER_INDEX_MASK - layer.index()) & LAYER_INDEX_MASK); | |
| if (layer.index() == 0) | |
| glContext->stencilFunc(WEBGL_ALWAYS, maskRef, STENCIL_MASK_FULL); | |
| else | |
| glContext->stencilFunc(WEBGL_LESS, maskRef, STENCIL_MASK_PARTIAL); |
This PR restructures the rendering pipeline to support per-container stencil isolation, preventing content leakage between scrollable containers when multiple containers exist in the same render layer, and adds performance optimizations through VAO/VBO caching.
Problem
The previous implementation rendered all scrollable containers in a layer with a single instanced draw call, creating a unified stencil mask. This caused content meant for container A to appear in container B's region when their stencil areas overlapped. Additionally, VAO/VBO objects were being recreated on every update, causing performance issues.
Solution
Type-Safe Class Hierarchy
Implemented a proper class hierarchy to distinguish container instances from content instances with compile-time type safety:
Container-Based Data Structure
Restructured from layer-based to container-based organization with typed instance lists:
Performance Optimization with VAO/VBO Caching
Implemented a caching system to eliminate redundant OpenGL resource creation:
Key optimizations:
defaultLayerData_handles non-container instances without recreationlayerDataPool_caches LayeredInstancesData objects by layer+container IDlayeredInstances_stores pointers to active objects instead of copyingOptimized VBO Data Structure
Removed
containerIdfield fromInstanceDatasince it's not needed for GPU rendering - container association is now managed at the list level rather than per-instance, reducing VBO size and improving performance.Isolated Container Rendering
Updated the rendering pipeline to process each container separately:
Enhanced Container Tracking
Added proper container relationship tracking in the layout system:
API Changes
The
iterateLayers()callback signature changed to support typed per-container iteration:Virtual Method Design
Made draw methods virtual to allow content-specific behavior:
beforeInstancedDraw()andafterInstancedDraw()are now virtual sinceborderDataTextureis only needed by content instancesThis ensures each container's content is rendered only within its own mask boundaries, with proper stencil isolation, type safety, significantly improved performance through optimized VAO/VBO caching, and better separation of concerns between container masks and content rendering.
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.