Skip to content

Commit 2d11d96

Browse files
authored
Re-add upload texture (#2915)
* vello: code cleanup of resource overwrites * upload_texture: upload cpu textures as SRGBA8 * vello: fix wgpu::Texture leak within vello's `context.resource_overrides` HashMap * fix missing feature gate
1 parent 0d43ad2 commit 2d11d96

File tree

6 files changed

+161
-24
lines changed

6 files changed

+161
-24
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,86 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
10831083
description: Cow::Borrowed("TODO"),
10841084
properties: None,
10851085
},
1086+
#[cfg(feature = "gpu")]
1087+
DocumentNodeDefinition {
1088+
identifier: "Upload Texture",
1089+
category: "Debug: GPU",
1090+
node_template: NodeTemplate {
1091+
document_node: DocumentNode {
1092+
implementation: DocumentNodeImplementation::Network(NodeNetwork {
1093+
exports: vec![NodeInput::node(NodeId(2), 0)],
1094+
nodes: [
1095+
DocumentNode {
1096+
inputs: vec![NodeInput::scope("editor-api")],
1097+
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
1098+
..Default::default()
1099+
},
1100+
DocumentNode {
1101+
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
1102+
manual_composition: Some(generic!(T)),
1103+
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
1104+
..Default::default()
1105+
},
1106+
DocumentNode {
1107+
manual_composition: Some(generic!(T)),
1108+
inputs: vec![NodeInput::node(NodeId(1), 0)],
1109+
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
1110+
..Default::default()
1111+
},
1112+
]
1113+
.into_iter()
1114+
.enumerate()
1115+
.map(|(id, node)| (NodeId(id as u64), node))
1116+
.collect(),
1117+
..Default::default()
1118+
}),
1119+
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
1120+
..Default::default()
1121+
},
1122+
persistent_node_metadata: DocumentNodePersistentMetadata {
1123+
output_names: vec!["Texture".to_string()],
1124+
network_metadata: Some(NodeNetworkMetadata {
1125+
persistent_metadata: NodeNetworkPersistentMetadata {
1126+
node_metadata: [
1127+
DocumentNodeMetadata {
1128+
persistent_metadata: DocumentNodePersistentMetadata {
1129+
display_name: "Extract Executor".to_string(),
1130+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
1131+
..Default::default()
1132+
},
1133+
..Default::default()
1134+
},
1135+
DocumentNodeMetadata {
1136+
persistent_metadata: DocumentNodePersistentMetadata {
1137+
display_name: "Upload Texture".to_string(),
1138+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
1139+
..Default::default()
1140+
},
1141+
..Default::default()
1142+
},
1143+
DocumentNodeMetadata {
1144+
persistent_metadata: DocumentNodePersistentMetadata {
1145+
display_name: "Cache".to_string(),
1146+
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
1147+
..Default::default()
1148+
},
1149+
..Default::default()
1150+
},
1151+
]
1152+
.into_iter()
1153+
.enumerate()
1154+
.map(|(id, node)| (NodeId(id as u64), node))
1155+
.collect(),
1156+
..Default::default()
1157+
},
1158+
..Default::default()
1159+
}),
1160+
..Default::default()
1161+
},
1162+
},
1163+
description: Cow::Borrowed("TODO"),
1164+
properties: None,
1165+
},
10861166
DocumentNodeDefinition {
10871167
identifier: "Extract",
10881168
category: "Debug",

node-graph/gsvg-renderer/src/renderer.rs

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ use graphene_core::uuid::{NodeId, generate_uuid};
1616
use graphene_core::vector::VectorDataTable;
1717
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
1818
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
19-
use graphene_core::{AlphaBlending, Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
19+
use graphene_core::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
2020
use num_traits::Zero;
2121
use std::collections::{HashMap, HashSet};
2222
use std::fmt::Write;
23+
use std::ops::Deref;
24+
use std::sync::{Arc, LazyLock};
2325
#[cfg(feature = "vello")]
2426
use vello::*;
2527

@@ -145,7 +147,7 @@ impl Default for SvgRender {
145147
#[derive(Clone, Debug, Default)]
146148
pub struct RenderContext {
147149
#[cfg(feature = "vello")]
148-
pub resource_overrides: HashMap<u64, wgpu::Texture>,
150+
pub resource_overrides: Vec<(peniko::Image, wgpu::Texture)>,
149151
}
150152

151153
/// Static state used whilst rendering
@@ -1014,6 +1016,8 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
10141016
}
10151017
}
10161018

1019+
const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
1020+
10171021
impl GraphicElementRendered for RasterDataTable<GPU> {
10181022
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
10191023
log::warn!("tried to render texture as an svg");
@@ -1023,30 +1027,30 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
10231027
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
10241028
use vello::peniko;
10251029

1026-
let mut render_stuff = |image: peniko::Image, instance_transform: DAffine2, blend_mode: AlphaBlending| {
1027-
let image_transform = transform * instance_transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
1030+
for instance in self.instance_ref_iter() {
1031+
let blend_mode = *instance.alpha_blending;
10281032
let layer = blend_mode != Default::default();
1029-
1030-
let Some(bounds) = self.bounding_box(transform, true) else { return };
1031-
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
1032-
10331033
if layer {
1034+
let Some(bounds) = self.bounding_box(transform, true) else { return };
1035+
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
10341036
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
10351037
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
10361038
}
1039+
1040+
let image = peniko::Image::new(
1041+
peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
1042+
peniko::ImageFormat::Rgba8,
1043+
instance.instance.data().width(),
1044+
instance.instance.data().height(),
1045+
)
1046+
.with_extend(peniko::Extend::Repeat);
1047+
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
10371048
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
1049+
context.resource_overrides.push((image, instance.instance.data().clone()));
1050+
10381051
if layer {
10391052
scene.pop_layer()
10401053
}
1041-
};
1042-
1043-
for instance in self.instance_ref_iter() {
1044-
let image = peniko::Image::new(vec![].into(), peniko::ImageFormat::Rgba8, instance.instance.data().width(), instance.instance.data().height()).with_extend(peniko::Extend::Repeat);
1045-
1046-
let id = image.data.id();
1047-
context.resource_overrides.insert(id, instance.instance.data().clone());
1048-
1049-
render_stuff(image, *instance.transform, *instance.alpha_blending);
10501054
}
10511055
}
10521056

node-graph/wgpu-executor/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ futures = { workspace = true }
2525
web-sys = { workspace = true }
2626
winit = { workspace = true }
2727
vello = { workspace = true }
28+
bytemuck = { workspace = true }

node-graph/wgpu-executor/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod context;
2+
pub mod texture_upload;
23

34
use anyhow::Result;
45
pub use context::Context;
@@ -111,20 +112,19 @@ impl WgpuExecutor {
111112

112113
{
113114
let mut renderer = self.vello_renderer.lock().await;
114-
for (id, texture) in context.resource_overrides.iter() {
115-
let texture = texture.clone();
115+
for (image, texture) in context.resource_overrides.iter() {
116116
let texture_view = wgpu::TexelCopyTextureInfoBase {
117-
texture,
117+
texture: texture.clone(),
118118
mip_level: 0,
119119
origin: Origin3d::ZERO,
120120
aspect: TextureAspect::All,
121121
};
122-
renderer.override_image(
123-
&vello::peniko::Image::new(vello::peniko::Blob::from_raw_parts(Arc::new(vec![]), *id), vello::peniko::ImageFormat::Rgba8, 0, 0),
124-
Some(texture_view),
125-
);
122+
renderer.override_image(image, Some(texture_view));
126123
}
127124
renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?;
125+
for (image, _) in context.resource_overrides.iter() {
126+
renderer.override_image(image, None);
127+
}
128128
}
129129

130130
let surface_texture = surface_inner.get_current_texture()?;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use crate::WgpuExecutor;
2+
use graphene_core::color::SRGBA8;
3+
use graphene_core::instances::Instance;
4+
use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable};
5+
use graphene_core::{Ctx, ExtractFootprint};
6+
use wgpu::util::{DeviceExt, TextureDataOrder};
7+
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
8+
9+
#[node_macro::node(category(""))]
10+
pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: RasterDataTable<CPU>, executor: &'a WgpuExecutor) -> RasterDataTable<GPU> {
11+
let device = &executor.context.device;
12+
let queue = &executor.context.queue;
13+
let instances = input
14+
.instance_ref_iter()
15+
.map(|instance| {
16+
let image = instance.instance;
17+
let rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
18+
19+
let texture = device.create_texture_with_data(
20+
queue,
21+
&TextureDescriptor {
22+
label: Some("upload_texture node texture"),
23+
size: Extent3d {
24+
width: image.width,
25+
height: image.height,
26+
depth_or_array_layers: 1,
27+
},
28+
mip_level_count: 1,
29+
sample_count: 1,
30+
dimension: TextureDimension::D2,
31+
format: TextureFormat::Rgba8UnormSrgb,
32+
// I don't know what usages are actually necessary
33+
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
34+
view_formats: &[],
35+
},
36+
TextureDataOrder::LayerMajor,
37+
bytemuck::cast_slice(rgba8_data.as_slice()),
38+
);
39+
40+
Instance {
41+
instance: Raster::new_gpu(texture.into()),
42+
transform: *instance.transform,
43+
alpha_blending: *instance.alpha_blending,
44+
source_node_id: *instance.source_node_id,
45+
}
46+
})
47+
.collect();
48+
49+
queue.submit([]);
50+
instances
51+
}

0 commit comments

Comments
 (0)