mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-24 06:54:07 +00:00
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
This commit is contained in:
parent
0d43ad2ea0
commit
2d11d96b4a
6 changed files with 161 additions and 24 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -5830,6 +5830,7 @@ name = "wgpu-executor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bytemuck",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
|
|
|
@ -1083,6 +1083,86 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
description: Cow::Borrowed("TODO"),
|
description: Cow::Borrowed("TODO"),
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Upload Texture",
|
||||||
|
category: "Debug: GPU",
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
|
nodes: [
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
|
||||||
|
manual_composition: Some(generic!(T)),
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
manual_composition: Some(generic!(T)),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
output_names: vec!["Texture".to_string()],
|
||||||
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
|
node_metadata: [
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Extract Executor".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Upload Texture".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Cache".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed("TODO"),
|
||||||
|
properties: None,
|
||||||
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Extract",
|
identifier: "Extract",
|
||||||
category: "Debug",
|
category: "Debug",
|
||||||
|
|
|
@ -16,10 +16,12 @@ use graphene_core::uuid::{NodeId, generate_uuid};
|
||||||
use graphene_core::vector::VectorDataTable;
|
use graphene_core::vector::VectorDataTable;
|
||||||
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
|
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
|
||||||
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
|
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
|
||||||
use graphene_core::{AlphaBlending, Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
|
use graphene_core::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::{Arc, LazyLock};
|
||||||
#[cfg(feature = "vello")]
|
#[cfg(feature = "vello")]
|
||||||
use vello::*;
|
use vello::*;
|
||||||
|
|
||||||
|
@ -145,7 +147,7 @@ impl Default for SvgRender {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct RenderContext {
|
pub struct RenderContext {
|
||||||
#[cfg(feature = "vello")]
|
#[cfg(feature = "vello")]
|
||||||
pub resource_overrides: HashMap<u64, wgpu::Texture>,
|
pub resource_overrides: Vec<(peniko::Image, wgpu::Texture)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Static state used whilst rendering
|
/// Static state used whilst rendering
|
||||||
|
@ -1014,6 +1016,8 @@ impl GraphicElementRendered for RasterDataTable<CPU> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LAZY_ARC_VEC_ZERO_U8: LazyLock<Arc<Vec<u8>>> = LazyLock::new(|| Arc::new(Vec::new()));
|
||||||
|
|
||||||
impl GraphicElementRendered for RasterDataTable<GPU> {
|
impl GraphicElementRendered for RasterDataTable<GPU> {
|
||||||
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
|
fn render_svg(&self, _render: &mut SvgRender, _render_params: &RenderParams) {
|
||||||
log::warn!("tried to render texture as an svg");
|
log::warn!("tried to render texture as an svg");
|
||||||
|
@ -1023,30 +1027,30 @@ impl GraphicElementRendered for RasterDataTable<GPU> {
|
||||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
|
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
let mut render_stuff = |image: peniko::Image, instance_transform: DAffine2, blend_mode: AlphaBlending| {
|
for instance in self.instance_ref_iter() {
|
||||||
let image_transform = transform * instance_transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
let blend_mode = *instance.alpha_blending;
|
||||||
let layer = blend_mode != Default::default();
|
let layer = blend_mode != Default::default();
|
||||||
|
|
||||||
let Some(bounds) = self.bounding_box(transform, true) else { return };
|
|
||||||
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
|
|
||||||
|
|
||||||
if layer {
|
if layer {
|
||||||
|
let Some(bounds) = self.bounding_box(transform, true) else { return };
|
||||||
|
let blending = peniko::BlendMode::new(blend_mode.blend_mode.to_peniko(), peniko::Compose::SrcOver);
|
||||||
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
|
||||||
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
|
scene.push_layer(blending, blend_mode.opacity, kurbo::Affine::IDENTITY, &rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let image = peniko::Image::new(
|
||||||
|
peniko::Blob::new(LAZY_ARC_VEC_ZERO_U8.deref().clone()),
|
||||||
|
peniko::ImageFormat::Rgba8,
|
||||||
|
instance.instance.data().width(),
|
||||||
|
instance.instance.data().height(),
|
||||||
|
)
|
||||||
|
.with_extend(peniko::Extend::Repeat);
|
||||||
|
let image_transform = transform * *instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||||
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
|
scene.draw_image(&image, kurbo::Affine::new(image_transform.to_cols_array()));
|
||||||
|
context.resource_overrides.push((image, instance.instance.data().clone()));
|
||||||
|
|
||||||
if layer {
|
if layer {
|
||||||
scene.pop_layer()
|
scene.pop_layer()
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for instance in self.instance_ref_iter() {
|
|
||||||
let image = peniko::Image::new(vec![].into(), peniko::ImageFormat::Rgba8, instance.instance.data().width(), instance.instance.data().height()).with_extend(peniko::Extend::Repeat);
|
|
||||||
|
|
||||||
let id = image.data.id();
|
|
||||||
context.resource_overrides.insert(id, instance.instance.data().clone());
|
|
||||||
|
|
||||||
render_stuff(image, *instance.transform, *instance.alpha_blending);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,3 +25,4 @@ futures = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
|
bytemuck = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod context;
|
mod context;
|
||||||
|
pub mod texture_upload;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
pub use context::Context;
|
pub use context::Context;
|
||||||
|
@ -111,20 +112,19 @@ impl WgpuExecutor {
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut renderer = self.vello_renderer.lock().await;
|
let mut renderer = self.vello_renderer.lock().await;
|
||||||
for (id, texture) in context.resource_overrides.iter() {
|
for (image, texture) in context.resource_overrides.iter() {
|
||||||
let texture = texture.clone();
|
|
||||||
let texture_view = wgpu::TexelCopyTextureInfoBase {
|
let texture_view = wgpu::TexelCopyTextureInfoBase {
|
||||||
texture,
|
texture: texture.clone(),
|
||||||
mip_level: 0,
|
mip_level: 0,
|
||||||
origin: Origin3d::ZERO,
|
origin: Origin3d::ZERO,
|
||||||
aspect: TextureAspect::All,
|
aspect: TextureAspect::All,
|
||||||
};
|
};
|
||||||
renderer.override_image(
|
renderer.override_image(image, Some(texture_view));
|
||||||
&vello::peniko::Image::new(vello::peniko::Blob::from_raw_parts(Arc::new(vec![]), *id), vello::peniko::ImageFormat::Rgba8, 0, 0),
|
|
||||||
Some(texture_view),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?;
|
renderer.render_to_texture(&self.context.device, &self.context.queue, scene, &target_texture.view, &render_params)?;
|
||||||
|
for (image, _) in context.resource_overrides.iter() {
|
||||||
|
renderer.override_image(image, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let surface_texture = surface_inner.get_current_texture()?;
|
let surface_texture = surface_inner.get_current_texture()?;
|
||||||
|
|
51
node-graph/wgpu-executor/src/texture_upload.rs
Normal file
51
node-graph/wgpu-executor/src/texture_upload.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::WgpuExecutor;
|
||||||
|
use graphene_core::color::SRGBA8;
|
||||||
|
use graphene_core::instances::Instance;
|
||||||
|
use graphene_core::raster_types::{CPU, GPU, Raster, RasterDataTable};
|
||||||
|
use graphene_core::{Ctx, ExtractFootprint};
|
||||||
|
use wgpu::util::{DeviceExt, TextureDataOrder};
|
||||||
|
use wgpu::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
|
||||||
|
|
||||||
|
#[node_macro::node(category(""))]
|
||||||
|
pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: RasterDataTable<CPU>, executor: &'a WgpuExecutor) -> RasterDataTable<GPU> {
|
||||||
|
let device = &executor.context.device;
|
||||||
|
let queue = &executor.context.queue;
|
||||||
|
let instances = input
|
||||||
|
.instance_ref_iter()
|
||||||
|
.map(|instance| {
|
||||||
|
let image = instance.instance;
|
||||||
|
let rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();
|
||||||
|
|
||||||
|
let texture = device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&TextureDescriptor {
|
||||||
|
label: Some("upload_texture node texture"),
|
||||||
|
size: Extent3d {
|
||||||
|
width: image.width,
|
||||||
|
height: image.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: TextureDimension::D2,
|
||||||
|
format: TextureFormat::Rgba8UnormSrgb,
|
||||||
|
// I don't know what usages are actually necessary
|
||||||
|
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::COPY_SRC,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
TextureDataOrder::LayerMajor,
|
||||||
|
bytemuck::cast_slice(rgba8_data.as_slice()),
|
||||||
|
);
|
||||||
|
|
||||||
|
Instance {
|
||||||
|
instance: Raster::new_gpu(texture.into()),
|
||||||
|
transform: *instance.transform,
|
||||||
|
alpha_blending: *instance.alpha_blending,
|
||||||
|
source_node_id: *instance.source_node_id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
queue.submit([]);
|
||||||
|
instances
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue