Add mesh gradient support - #4081
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new "Mesh" gradient type, adding UI support in the gradient tool and implementing an initial SVG rendering path that approximates the mesh by subdividing it into an 8x8 grid of polygons. Feedback identifies critical bugs in the rendering logic, including potential panics if the geometry has fewer than four points or segments and an incorrect return statement that prevents subsequent elements from rendering. Additionally, the reviewer noted that the mesh colors are currently hardcoded and that the Vello and SVG stops table implementations are still placeholders.
4b7a823 to
847b8e9
Compare
15fcaac to
d5f0140
Compare
5785002 to
231773c
Compare
6575890 to
f097784
Compare
f2939fa to
e5de2e0
Compare
e5de2e0 to
ededf6c
Compare
- Click to add/select mesh gradient - Show transform widget inside Fill nodes
# Conflicts: # editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs # editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs # editor/src/messages/portfolio/document/graph_operation/utility_types.rs # editor/src/messages/portfolio/document/node_graph/node_properties.rs # editor/src/messages/portfolio/document_migration.rs # node-graph/libraries/graphic-types/src/graphic.rs # node-graph/libraries/rendering/src/render_ext.rs # node-graph/libraries/rendering/src/renderer.rs # node-graph/nodes/path-bool/src/lib.rs # node-graph/nodes/vector/src/vector_nodes.rs
55d7bda to
4ef8f6f
Compare
|
@cubic-dev-ai Please review |
@YohYamasaki I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 36 files
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
# Conflicts: # node-graph/libraries/graphic-types/src/graphic.rs # node-graph/libraries/rendering/src/render_ext.rs # node-graph/libraries/rendering/src/renderer.rs # node-graph/nodes/path-bool/src/lib.rs
|
@cubic-dev-ai Please review |
@YohYamasaki I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 36 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 36 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
# Conflicts: # editor/src/messages/portfolio/document/storage_tests/round_trip_tests.rs # node-graph/libraries/graphic-types/src/appearance.rs # node-graph/nodes/graphic/src/graphic.rs # node-graph/nodes/vector/src/vector_nodes.rs
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Confidence score: 3/5
- In
node-graph/libraries/rendering/src/renderer.rs, non-1 rasterrender_params.scaleis applied after adaptive subdivision but omitted fromviewport_zoom, which can leave large-scale output with incorrect subpatch positioning or coverage; include the scale when computingviewport_zoom. - In
node-graph/libraries/rendering/src/renderer/mesh_gradient.rs, the transform guard accepts a collapsedparent_transform * mesh_transformwhen both columns are nonzero, allowing displacement-map generation from a singular transform; validate the smallest singular value before proceeding.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="node-graph/libraries/rendering/src/renderer.rs">
<violation number="1" location="node-graph/libraries/rendering/src/renderer.rs:3031">
P2: When raster output uses a `render_params.scale` other than 1, the final Vello scene is scaled after adaptive subdivision, but this value is omitted from `viewport_zoom`. Large output scales therefore leave subpatch position errors and seam inflation sized in logical pixels, producing visible approximation errors; include the physical render scale when deriving the value passed to subdivision and subpatch rendering.</violation>
</file>
<file name="node-graph/libraries/rendering/src/renderer/mesh_gradient.rs">
<violation number="1" location="node-graph/libraries/rendering/src/renderer/mesh_gradient.rs:1025">
P2: When `parent_transform * mesh_transform` is singular but both columns are nonzero, this guard accepts a collapsed output transform and proceeds to build the displacement map. Check the smallest singular value before rendering the patch.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| let space: GradientSpace = item.attribute_cloned_or_default(ATTR_GRADIENT_SPACE); | ||
| let interpolation_method: GradientInterpolation = item.attribute_cloned_or_default(ATTR_GRADIENT_INTERPOLATION); | ||
| let Some(evaluator) = mesh_gradient.evaluator(space, interpolation_method) else { return }; | ||
| let viewport_zoom = if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. }; |
There was a problem hiding this comment.
P2: When raster output uses a render_params.scale other than 1, the final Vello scene is scaled after adaptive subdivision, but this value is omitted from viewport_zoom. Large output scales therefore leave subpatch position errors and seam inflation sized in logical pixels, producing visible approximation errors; include the physical render scale when deriving the value passed to subdivision and subpatch rendering.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/rendering/src/renderer.rs, line 3031:
<comment>When raster output uses a `render_params.scale` other than 1, the final Vello scene is scaled after adaptive subdivision, but this value is omitted from `viewport_zoom`. Large output scales therefore leave subpatch position errors and seam inflation sized in logical pixels, producing visible approximation errors; include the physical render scale when deriving the value passed to subdivision and subpatch rendering.</comment>
<file context>
@@ -3028,7 +3028,9 @@ fn render_mesh_gradient_item_to_vello(item: ItemRef<'_, MeshGradient>, scene: &m
let interpolation_method: GradientInterpolation = item.attribute_cloned_or_default(ATTR_GRADIENT_INTERPOLATION);
let Some(evaluator) = mesh_gradient.evaluator(space, interpolation_method) else { return };
- let Some(subpatches) = subdivide_patches_adaptive(&evaluator, mesh_transform, parent_transform, MESH_POSITION_ERROR_TOLERANCE, MESH_COLOR_ERROR_TOLERANCE) else {
+ let viewport_zoom = if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. };
+ let position_error_tolerance = MESH_POSITION_ERROR_TOLERANCE / viewport_zoom;
+ let Some(subpatches) = subdivide_patches_adaptive(&evaluator, mesh_transform, parent_transform, position_error_tolerance, MESH_COLOR_ERROR_TOLERANCE) else {
</file context>
| let viewport_zoom = if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. }; | |
| let viewport_zoom = (if render_params.viewport_zoom > 0. { render_params.viewport_zoom } else { 1. }) * (if render_params.scale > 0. { render_params.scale } else { 1. }); |
| // so a local space that reaches the output non-uniformly displaces both axes by the wrong amount there. | ||
| let mesh_to_output = (self.parent_transform * self.mesh_transform).matrix2; | ||
| let output_scales = DVec2::new(mesh_to_output.x_axis.length(), mesh_to_output.y_axis.length()); | ||
| if !output_scales.is_finite() || output_scales.min_element() <= f64::EPSILON { |
There was a problem hiding this comment.
P2: When parent_transform * mesh_transform is singular but both columns are nonzero, this guard accepts a collapsed output transform and proceeds to build the displacement map. Check the smallest singular value before rendering the patch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At node-graph/libraries/rendering/src/renderer/mesh_gradient.rs, line 1025:
<comment>When `parent_transform * mesh_transform` is singular but both columns are nonzero, this guard accepts a collapsed output transform and proceeds to build the displacement map. Check the smallest singular value before rendering the patch.</comment>
<file context>
@@ -1009,15 +1016,20 @@ impl<'mesh, 'field> SvgMeshPatchRenderer<'mesh, 'field> {
+ // so a local space that reaches the output non-uniformly displaces both axes by the wrong amount there.
+ let mesh_to_output = (self.parent_transform * self.mesh_transform).matrix2;
+ let output_scales = DVec2::new(mesh_to_output.x_axis.length(), mesh_to_output.y_axis.length());
+ if !output_scales.is_finite() || output_scales.min_element() <= f64::EPSILON {
return;
}
</file context>
# Conflicts: # editor/src/messages/portfolio/document/overlays/utility_functions.rs # node-graph/libraries/rendering/src/render_ext.rs # node-graph/libraries/rendering/src/renderer.rs # node-graph/libraries/vector-types/src/lib.rs # node-graph/libraries/vector-types/src/vector/vector_attributes.rs # node-graph/nodes/vector/src/vector_nodes.rs
Closes #3990
Screen.Recording.2026-08-17.at.13.03.19.mp4
Add the Coons patch-based mesh gradient paint.
Neither the current SVG specification nor Vello natively supports mesh gradient rendering, so the current implementations approximate mesh gradient behavior using linear gradients and alpha masks.
A Coons patch's shape is defined by its Bézier outlines. For SVG, it is replicated by
feDisplacementMap, and approximated subdividing into multiple parallelograms for Vello. This ensures that the original shape, before transformation, can be a rectangle with affine transformation. Then, the color in the rectangle is blended bi-linearly or bi-cubically depending on the chosen interpolation space.Known issues & limitations