mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 12:18:11 +08:00
Migrate legacy gradients via an isolated measurement pre-pass (#4268)
* Migrate legacy gradients via an isolated measurement pre-pass Convert legacy bounding-box-relative Fill::Gradient values to absolute space on document load by measuring each fill's evaluated geometry, rather than walking each layer's primary flow. The pre-pass scans the whole root network so fills on secondary branches (e.g. a Copy to Points instance) and fills in hidden, disabled, or orphaned branches are all found, redirecting the document export to each fill in turn to force its branch to evaluate and reading its geometry back from the inspect result. With every legacy gradient converted before any render, the legacy bounding-box render path is removed: the ATTR_GRADIENT_LEGACY marker, the renderer's legacy brush branch, and fill_to_graphic_list's bbox bake all go away. Gradients nested inside subgraph node networks are out of scope and warned about. * Address review: harden gradient migration against stalls and document switches - Advance the queue on dispatch early returns (missing export or channel send error) so a failure can't leave the migration permanently stuck with the document unable to render. - Tag the migration with its DocumentId: cancel-and-restart when a different document's migration is requested, and only apply a measurement to the active document's in-progress migration, so switching documents mid-migration can't stall or cross-apply. * Remove nested network non-migration warning * Cleanup * Also add migrations for Fill node backup colors * Apply migrations to the demo art
This commit is contained in:
@@ -225,8 +225,6 @@ pub enum DocumentMessage {
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<Arc<ClickTarget>>>,
|
||||
},
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
MigrateLegacyGradients,
|
||||
UpdateOutlines {
|
||||
outlines: HashMap<NodeId, Vec<Arc<ClickTarget>>>,
|
||||
},
|
||||
|
||||
@@ -1372,38 +1372,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
.collect();
|
||||
self.network_interface.update_click_targets(layer_click_targets);
|
||||
}
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
DocumentMessage::MigrateLegacyGradients => {
|
||||
if self.pending_gradient_migration {
|
||||
self.pending_gradient_migration = false;
|
||||
|
||||
// Read each layer's legacy gradient and compute its absolute form from the now-available local bounds
|
||||
let layers: Vec<_> = self.metadata().all_layers().collect();
|
||||
let conversions: Vec<(NodeId, Fill)> = layers
|
||||
.into_iter()
|
||||
.filter_map(|layer| {
|
||||
let gradient = graph_modification_utils::get_gradient(layer, &self.network_interface)?;
|
||||
if gradient.absolute {
|
||||
return None;
|
||||
}
|
||||
let fill_node_id = graph_modification_utils::get_fill_node_id(layer, &self.network_interface)?;
|
||||
let bounds = self.metadata().nonzero_bounding_box(layer);
|
||||
let bounding_box = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
let layer_transform = self.metadata().upstream_transform(layer.to_node());
|
||||
Some((fill_node_id, Fill::Gradient(gradient.to_absolute(bounding_box, layer_transform))))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let converted_any = !conversions.is_empty();
|
||||
for (fill_node_id, fill) in conversions {
|
||||
self.network_interface
|
||||
.set_input(&InputConnector::node(fill_node_id, 1), NodeInput::value(TaggedValue::Fill(fill), false), &[]);
|
||||
}
|
||||
if converted_any {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
DocumentMessage::UpdateOutlines { outlines } => {
|
||||
let layer_outlines = outlines
|
||||
.into_iter()
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use glam::{DVec2, IVec2};
|
||||
use graph_craft::application_io::resource::{DataSource, Resource, ResourceHash, ResourceId};
|
||||
use graph_craft::descriptor;
|
||||
@@ -1121,8 +1122,8 @@ pub fn document_migration_replace_resources_referenced_by_hash(document_serializ
|
||||
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
|
||||
document.network_interface.migrate_path_modify_node();
|
||||
|
||||
// Legacy `Fill::Gradient`s are converted to absolute by the deferred migration pass once the first graph run yields geometry bounds
|
||||
document.pending_gradient_migration = true;
|
||||
// Legacy `Fill::Gradient`s are converted to absolute by the deferred migration pre-pass that measures each fill's geometry
|
||||
document.pending_gradient_migration = !graph_modification_utils::legacy_gradient_fill_nodes(&document.network_interface).is_empty();
|
||||
|
||||
let network = document.network_interface.document_network().clone();
|
||||
|
||||
|
||||
@@ -1621,6 +1621,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
// Use exact physical dimensions from browser (via ResizeObserver's devicePixelContentBoxSize)
|
||||
let physical_resolution = viewport.size().to_physical().into_dvec2().round().as_uvec2();
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// A freshly-opened document with legacy gradients runs a one-time measurement pre-pass instead of rendering, until every gradient is converted to absolute space
|
||||
if document.pending_gradient_migration {
|
||||
self.executor.drive_gradient_migration(document, document_id, physical_resolution, scale, responses);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this when we do the SVG rendering with a separate library on desktop, thus avoiding a need for the hole punch.
|
||||
// TODO: See #3796. There is a second instance of this todo comment and code block (be sure to remove both).
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
@@ -1999,7 +2006,7 @@ impl PortfolioMessageHandler {
|
||||
return Err("No active document".to_string());
|
||||
};
|
||||
|
||||
let result = self.executor.poll_node_graph_evaluation(active_document, responses);
|
||||
let result = self.executor.poll_node_graph_evaluation(active_document, document_id, responses);
|
||||
if result.is_err() {
|
||||
let error = r#"
|
||||
<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
||||
use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||
@@ -319,6 +319,60 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
||||
Some(gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// The legacy bounding-box-relative gradient (`absolute == false`) in a "Fill" node's active `fill` input, if any.
|
||||
fn legacy_active_gradient_in_fill_node(fill_node_id: NodeId, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
let node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
let TaggedValue::Fill(Fill::Gradient(gradient)) = node.inputs.get(graphene_std::vector::fill::FillInput::<Fill>::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
(!gradient.absolute).then(|| gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// The legacy bounding-box-relative gradient (`absolute == false`) stashed in a "Fill" node's `_backup_gradient` input, if any.
|
||||
/// The backup is inert until the fill is toggled back to a gradient, at which point it becomes the active fill, so it needs converting too.
|
||||
fn legacy_backup_gradient_in_fill_node(fill_node_id: NodeId, network_interface: &NodeNetworkInterface) -> Option<Gradient> {
|
||||
let node = network_interface.document_network().nodes.get(&fill_node_id)?;
|
||||
let TaggedValue::FillGradient(gradient) = node.inputs.get(graphene_std::vector::fill::BackupGradientInput::INDEX)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
(!gradient.absolute).then(|| gradient.clone())
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// Convert a "Fill" node's legacy gradients (the active `fill` and/or the stashed `_backup_gradient`) to absolute space using
|
||||
/// the geometry's measured bounding box, writing each back in place. The active fill is written as a `Fill`, the backup as a bare `FillGradient`.
|
||||
pub fn migrate_fill_node_gradients_to_absolute(fill_node_id: NodeId, network_interface: &mut NodeNetworkInterface, bounding_box: DAffine2, layer_transform: DAffine2) {
|
||||
if let Some(gradient) = legacy_active_gradient_in_fill_node(fill_node_id, network_interface) {
|
||||
let absolute = gradient.to_absolute(bounding_box, layer_transform);
|
||||
let input = InputConnector::node(fill_node_id, graphene_std::vector::fill::FillInput::<Fill>::INDEX);
|
||||
network_interface.set_input(&input, NodeInput::value(TaggedValue::Fill(Fill::Gradient(absolute)), false), &[]);
|
||||
}
|
||||
if let Some(gradient) = legacy_backup_gradient_in_fill_node(fill_node_id, network_interface) {
|
||||
let absolute = gradient.to_absolute(bounding_box, layer_transform);
|
||||
let input = InputConnector::node(fill_node_id, graphene_std::vector::fill::BackupGradientInput::INDEX);
|
||||
network_interface.set_input(&input, NodeInput::value(TaggedValue::FillGradient(absolute), false), &[]);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
/// Find every root-network "Fill" node holding a legacy bounding-box-relative gradient, either as its active `fill` or as its `_backup_gradient`.
|
||||
///
|
||||
/// Scans the document network structurally instead of walking each layer's primary flow, so it also catches fills on
|
||||
/// secondary inputs and in hidden, disabled, or orphaned branches. Fills nested inside subgraph node networks are skipped.
|
||||
pub fn legacy_gradient_fill_nodes(network_interface: &NodeNetworkInterface) -> Vec<NodeId> {
|
||||
let fill_identifier = DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER);
|
||||
network_interface
|
||||
.document_network()
|
||||
.nodes
|
||||
.keys()
|
||||
.copied()
|
||||
.filter(|node_id| network_interface.reference(node_id, &[]).as_ref() == Some(&fill_identifier))
|
||||
.filter(|&node_id| legacy_active_gradient_in_fill_node(node_id, network_interface).is_some() || legacy_backup_gradient_in_fill_node(node_id, network_interface).is_some())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the gradient stops of a layer, if any.
|
||||
pub fn get_gradient_stops(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<GradientStops> {
|
||||
let gradient_value_node = network_interface.document_network().nodes.get(&get_upstream_gradient_value_node_id(layer, network_interface)?)?;
|
||||
|
||||
Reference in New Issue
Block a user