Add the Pixel Preview render mode (#3881)
* Add pixel preview render mode * Fix fmt * Remove unused sampler * Remove unnecessary mutex * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
35b812ccfe
commit
095c2a6d47
|
|
@ -1,3 +1,7 @@
|
||||||
|
// =============
|
||||||
|
// VERTEX SHADER
|
||||||
|
// =============
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
@location(0) tex_coords: vec2<f32>,
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
|
@ -6,19 +10,21 @@ struct VertexOutput {
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
|
|
||||||
let pos = array(
|
let pos = array(
|
||||||
vec2f( -1.0, -1.0),
|
vec2f(-1.0, -1.0),
|
||||||
vec2f( 3.0, -1.0),
|
vec2f(3.0, -1.0),
|
||||||
vec2f( -1.0, 3.0),
|
vec2f(-1.0, 3.0),
|
||||||
);
|
);
|
||||||
let xy = pos[vertex_index];
|
let xy = pos[vertex_index];
|
||||||
out.clip_position = vec4f(xy , 0.0, 1.0);
|
out.clip_position = vec4f(xy, 0.0, 1.0);
|
||||||
let coords = (xy / 2. + 0.5);
|
let coords = xy / 2. + 0.5;
|
||||||
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===============
|
||||||
|
// FRAGMENT SHADER
|
||||||
|
// ===============
|
||||||
|
|
||||||
struct Constants {
|
struct Constants {
|
||||||
viewport_scale: vec2<f32>,
|
viewport_scale: vec2<f32>,
|
||||||
|
|
|
||||||
|
|
@ -2519,11 +2519,12 @@ impl DocumentMessageHandler {
|
||||||
.icon("RenderModeOutline")
|
.icon("RenderModeOutline")
|
||||||
.tooltip_label("Render Mode: Outline")
|
.tooltip_label("Render Mode: Outline")
|
||||||
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()),
|
.on_update(|_| DocumentMessage::SetRenderMode { render_mode: RenderMode::Outline }.into()),
|
||||||
// TODO: See issue #320
|
RadioEntryData::new("PixelPreview").icon("RenderModePixels").tooltip_label("Render Mode: Pixel Preview").on_update(|_| {
|
||||||
// RadioEntryData::new("PixelPreview")
|
DocumentMessage::SetRenderMode {
|
||||||
// .icon("RenderModePixels")
|
render_mode: RenderMode::PixelPreview,
|
||||||
// .tooltip_label("Render Mode: Pixel Preview")
|
}
|
||||||
// .on_update(|_| todo!()),
|
.into()
|
||||||
|
}),
|
||||||
RadioEntryData::new("SvgPreview")
|
RadioEntryData::new("SvgPreview")
|
||||||
.icon("RenderModeSvg")
|
.icon("RenderModeSvg")
|
||||||
.tooltip_label("Render Mode: SVG Preview")
|
.tooltip_label("Render Mode: SVG Preview")
|
||||||
|
|
@ -2534,7 +2535,7 @@ impl DocumentMessageHandler {
|
||||||
if disabled {
|
if disabled {
|
||||||
for entry in &mut entries {
|
for entry in &mut entries {
|
||||||
entry.tooltip_description = "
|
entry.tooltip_description = "
|
||||||
*Normal* and *Outline* render modes are not available in this browser. For compatibility, *SVG Preview* mode is active as a fallback.\n\
|
*Normal*, *Outline*, and *Pixel Preview* render modes are not available in this browser. For compatibility, *SVG Preview* mode is active as a fallback.\n\
|
||||||
\n\
|
\n\
|
||||||
This functionality requires WebGPU support. Check webgpu.org for browser implementation status.
|
This functionality requires WebGPU support. Check webgpu.org for browser implementation status.
|
||||||
"
|
"
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||||
let render_node = DocumentNode {
|
let render_node = DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
exports: vec![NodeInput::node(NodeId(3), 0)],
|
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
call_argument: concrete!(Context),
|
call_argument: concrete!(Context),
|
||||||
|
|
@ -61,9 +61,19 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
DocumentNode {
|
||||||
|
call_argument: concrete!(Context),
|
||||||
|
inputs: vec![NodeInput::scope("editor-api"), NodeInput::node(NodeId(2), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::pixel_preview::pixel_preview::IDENTIFIER),
|
||||||
|
context_features: graphene_std::ContextDependencies {
|
||||||
|
extract: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||||
|
inject: ContextFeatures::FOOTPRINT | ContextFeatures::VARARGS,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
call_argument: concrete!(graphene_std::application_io::RenderConfig),
|
call_argument: concrete!(graphene_std::application_io::RenderConfig),
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::render_node::create_context::IDENTIFIER),
|
||||||
context_features: graphene_std::ContextDependencies {
|
context_features: graphene_std::ContextDependencies {
|
||||||
// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work
|
// We add the extract index annotation here to force the compiler to add a context nullification node before this node so the render context is properly nullified so the render cache node can do its's work
|
||||||
|
|
|
||||||
|
|
@ -665,8 +665,8 @@ pub enum RenderMode {
|
||||||
Normal = 0,
|
Normal = 0,
|
||||||
/// Render only the outlines of shapes at the current viewport resolution
|
/// Render only the outlines of shapes at the current viewport resolution
|
||||||
Outline,
|
Outline,
|
||||||
// /// Render with normal coloration at the document resolution, showing the pixels when the current viewport resolution is higher
|
/// Render with normal coloration at the document export resolution; at zoom > 100% this shows individual export pixels upscaled with nearest-neighbor filtering
|
||||||
// PixelPreview,
|
PixelPreview,
|
||||||
/// Render a preview of how the object would be exported as an SVG.
|
/// Render a preview of how the object would be exported as an SVG.
|
||||||
SvgPreview,
|
SvgPreview,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
mod context;
|
mod context;
|
||||||
|
mod resample;
|
||||||
pub mod shader_runtime;
|
pub mod shader_runtime;
|
||||||
pub mod texture_conversion;
|
pub mod texture_conversion;
|
||||||
|
|
||||||
|
use crate::resample::Resampler;
|
||||||
use crate::shader_runtime::ShaderRuntime;
|
use crate::shader_runtime::ShaderRuntime;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use core_types::Color;
|
use core_types::Color;
|
||||||
|
|
@ -9,7 +11,6 @@ use dyn_any::StaticType;
|
||||||
use futures::lock::Mutex;
|
use futures::lock::Mutex;
|
||||||
use glam::UVec2;
|
use glam::UVec2;
|
||||||
use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId};
|
use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle, SurfaceId};
|
||||||
pub use rendering::RenderContext;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
|
use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};
|
||||||
use wgpu::util::TextureBlitter;
|
use wgpu::util::TextureBlitter;
|
||||||
|
|
@ -17,6 +18,7 @@ use wgpu::{Origin3d, TextureAspect};
|
||||||
|
|
||||||
pub use context::Context as WgpuContext;
|
pub use context::Context as WgpuContext;
|
||||||
pub use context::ContextBuilder as WgpuContextBuilder;
|
pub use context::ContextBuilder as WgpuContextBuilder;
|
||||||
|
pub use rendering::RenderContext;
|
||||||
pub use wgpu::Backends as WgpuBackends;
|
pub use wgpu::Backends as WgpuBackends;
|
||||||
pub use wgpu::Features as WgpuFeatures;
|
pub use wgpu::Features as WgpuFeatures;
|
||||||
|
|
||||||
|
|
@ -24,6 +26,7 @@ pub use wgpu::Features as WgpuFeatures;
|
||||||
pub struct WgpuExecutor {
|
pub struct WgpuExecutor {
|
||||||
pub context: WgpuContext,
|
pub context: WgpuContext,
|
||||||
vello_renderer: Mutex<Renderer>,
|
vello_renderer: Mutex<Renderer>,
|
||||||
|
resampler: Resampler,
|
||||||
pub shader_runtime: ShaderRuntime,
|
pub shader_runtime: ShaderRuntime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,6 +157,10 @@ impl WgpuExecutor {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resample_texture(&self, source: &wgpu::Texture, target_size: UVec2, transform: &glam::DAffine2) -> wgpu::Texture {
|
||||||
|
self.resampler.resample(&self.context, source, target_size, transform)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
|
pub fn create_surface(&self, canvas: graphene_application_io::WasmSurfaceHandle) -> Result<SurfaceHandle<Surface>> {
|
||||||
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;
|
let surface = self.context.instance.create_surface(wgpu::SurfaceTarget::Canvas(canvas.surface))?;
|
||||||
|
|
@ -196,9 +203,12 @@ impl WgpuExecutor {
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to create Vello renderer: {:?}", e))
|
.map_err(|e| anyhow::anyhow!("Failed to create Vello renderer: {:?}", e))
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
|
let resampler = Resampler::new(&context.device);
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
shader_runtime: ShaderRuntime::new(&context),
|
shader_runtime: ShaderRuntime::new(&context),
|
||||||
context,
|
context,
|
||||||
|
resampler,
|
||||||
vello_renderer: vello_renderer.into(),
|
vello_renderer: vello_renderer.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
use crate::WgpuContext;
|
||||||
|
use glam::{DAffine2, UVec2, Vec2};
|
||||||
|
|
||||||
|
pub struct Resampler {
|
||||||
|
pipeline: wgpu::RenderPipeline,
|
||||||
|
bind_group_layout: wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resampler {
|
||||||
|
pub fn new(device: &wgpu::Device) -> Self {
|
||||||
|
let shader = device.create_shader_module(wgpu::include_wgsl!("resample_shader.wgsl"));
|
||||||
|
|
||||||
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
label: Some("resample_bind_group_layout"),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Buffer {
|
||||||
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: None,
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("resample_pipeline_layout"),
|
||||||
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
|
push_constant_ranges: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("resample_pipeline"),
|
||||||
|
layout: Some(&pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
blend: None,
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState::default(),
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Resampler { pipeline, bind_group_layout }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resample(&self, context: &WgpuContext, source: &wgpu::Texture, target_size: UVec2, transform: &DAffine2) -> wgpu::Texture {
|
||||||
|
let device = &context.device;
|
||||||
|
let queue = &context.queue;
|
||||||
|
|
||||||
|
let output_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("resample_output"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: target_size.x.max(1),
|
||||||
|
height: target_size.y.max(1),
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8Unorm,
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let source_view = source.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let params_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("resample_params"),
|
||||||
|
size: 32,
|
||||||
|
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let params_data = [transform.matrix2.x_axis.as_vec2(), transform.matrix2.y_axis.as_vec2(), transform.translation.as_vec2(), Vec2::ZERO];
|
||||||
|
queue.write_buffer(¶ms_buffer, 0, bytemuck::cast_slice(¶ms_data));
|
||||||
|
|
||||||
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("resample_bind_group"),
|
||||||
|
layout: &self.bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&source_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: params_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("resample_encoder") });
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("resample_pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &output_view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
depth_slice: None,
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&self.pipeline);
|
||||||
|
render_pass.set_bind_group(0, &bind_group, &[]);
|
||||||
|
render_pass.draw(0..3, 0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit([encoder.finish()]);
|
||||||
|
|
||||||
|
output_texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
// =============
|
||||||
|
// VERTEX SHADER
|
||||||
|
// =============
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
let pos = array(
|
||||||
|
vec2f(-1.0, -1.0),
|
||||||
|
vec2f(3.0, -1.0),
|
||||||
|
vec2f(-1.0, 3.0),
|
||||||
|
);
|
||||||
|
let xy = pos[vertex_index];
|
||||||
|
out.clip_position = vec4f(xy, 0.0, 1.0);
|
||||||
|
let coords = xy / 2. + 0.5;
|
||||||
|
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============
|
||||||
|
// FRAGMENT SHADER
|
||||||
|
// ===============
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var t_source: texture_2d<f32>;
|
||||||
|
|
||||||
|
struct Params {
|
||||||
|
matrix: mat2x2<f32>,
|
||||||
|
translation: vec2<f32>,
|
||||||
|
_pad: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We need to use a uniform buffer for the params because push constants are not supported on web
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var<uniform> params: Params;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let position = params.matrix * in.tex_coords + params.translation;
|
||||||
|
let texel = vec2<i32>(floor(position));
|
||||||
|
let texture_size = vec2<i32>(textureDimensions(t_source));
|
||||||
|
if (texel.x >= 0 && texel.x < texture_size.x && texel.y >= 0 && texel.y < texture_size.y) {
|
||||||
|
return textureLoad(t_source, texel, 0);
|
||||||
|
}
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod any;
|
pub mod any;
|
||||||
|
pub mod pixel_preview;
|
||||||
pub mod render_cache;
|
pub mod render_cache;
|
||||||
pub mod render_node;
|
pub mod render_node;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
use crate::render_node::RenderOutputType;
|
||||||
|
use core_types::transform::{Footprint, Transform};
|
||||||
|
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
|
use glam::{DAffine2, DVec2, UVec2};
|
||||||
|
use graph_craft::document::value::RenderOutput;
|
||||||
|
use graph_craft::wasm_application_io::WasmEditorApi;
|
||||||
|
use graphene_application_io::{ApplicationIo, ImageTexture};
|
||||||
|
use rendering::{RenderOutputType as RenderOutputTypeRequest, RenderParams};
|
||||||
|
use vector_types::vector::style::RenderMode;
|
||||||
|
|
||||||
|
#[node_macro::node(category(""))]
|
||||||
|
pub async fn pixel_preview<'a: 'n>(
|
||||||
|
ctx: impl Ctx + ExtractAll + CloneVarArgs + Sync,
|
||||||
|
editor_api: &'a WasmEditorApi,
|
||||||
|
data: impl Node<Context<'static>, Output = RenderOutput> + Send + Sync,
|
||||||
|
) -> RenderOutput {
|
||||||
|
let Some(render_params) = ctx.vararg(0).ok().and_then(|v| v.downcast_ref::<RenderParams>()).cloned() else {
|
||||||
|
log::error!("invalid render params for pixel preview");
|
||||||
|
let context = OwnedContextImpl::from(ctx).into_context();
|
||||||
|
return data.eval(context).await;
|
||||||
|
};
|
||||||
|
let physical_scale = render_params.scale;
|
||||||
|
|
||||||
|
let footprint = *ctx.footprint();
|
||||||
|
let viewport_zoom = footprint.decompose_scale().x;
|
||||||
|
|
||||||
|
if render_params.render_mode != RenderMode::PixelPreview || !matches!(render_params.render_output_type, RenderOutputTypeRequest::Vello) || viewport_zoom <= 1. {
|
||||||
|
let context = OwnedContextImpl::from(ctx).into_context();
|
||||||
|
return data.eval(context).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let physical_resolution = footprint.resolution;
|
||||||
|
let logical_resolution = physical_resolution.as_dvec2() / physical_scale;
|
||||||
|
|
||||||
|
let logical_footprint = Footprint {
|
||||||
|
resolution: logical_resolution.as_uvec2().max(UVec2::ONE),
|
||||||
|
..footprint
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds = logical_footprint.viewport_bounds_in_local_space();
|
||||||
|
|
||||||
|
let upstream_min = bounds.start.floor();
|
||||||
|
let upstream_max = bounds.end.ceil();
|
||||||
|
|
||||||
|
let upstream_size = (upstream_max - upstream_min).max(DVec2::ONE);
|
||||||
|
let upstream_resolution = upstream_size.as_uvec2().max(UVec2::ONE);
|
||||||
|
|
||||||
|
let upstream_footprint = Footprint {
|
||||||
|
transform: DAffine2::from_scale(DVec2::splat(1.0 / physical_scale)) * DAffine2::from_translation(-upstream_min),
|
||||||
|
resolution: upstream_resolution,
|
||||||
|
quality: footprint.quality,
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(upstream_footprint).with_vararg(Box::new(render_params)).into_context();
|
||||||
|
let mut result = data.eval(new_ctx).await;
|
||||||
|
|
||||||
|
let RenderOutputType::Texture(ref source_texture) = result.data else { return result };
|
||||||
|
|
||||||
|
let transform = DAffine2::from_translation(-upstream_min) * footprint.transform.inverse() * DAffine2::from_scale(logical_resolution);
|
||||||
|
|
||||||
|
let exec = editor_api.application_io.as_ref().unwrap().gpu_executor().unwrap();
|
||||||
|
let resampled = exec.resample_texture(&source_texture.texture, physical_resolution, &transform);
|
||||||
|
|
||||||
|
result.data = RenderOutputType::Texture(ImageTexture { texture: resampled });
|
||||||
|
|
||||||
|
result
|
||||||
|
.metadata
|
||||||
|
.apply_transform(footprint.transform * DAffine2::from_translation(upstream_min) * DAffine2::from_scale(DVec2::splat(physical_scale)));
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
@ -184,8 +184,7 @@ async fn render<'a: 'n>(ctx: impl Ctx + ExtractFootprint + ExtractVarArgs, edito
|
||||||
// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport
|
// We now replace all transforms which are supposed to be infinite with a transform which covers the entire viewport
|
||||||
// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail
|
// See <https://xi.zulipchat.com/#narrow/channel/197075-vello/topic/Full.20screen.20color.2Fgradients/near/538435044> for more detail
|
||||||
let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(physical_resolution.x as f64, physical_resolution.y as f64);
|
let scaled_infinite_transform = vello::kurbo::Affine::scale_non_uniform(physical_resolution.x as f64, physical_resolution.y as f64);
|
||||||
let encoding = scene.encoding_mut();
|
for transform in scene.encoding_mut().transforms.iter_mut() {
|
||||||
for transform in encoding.transforms.iter_mut() {
|
|
||||||
if transform.matrix[0] == f32::INFINITY {
|
if transform.matrix[0] == f32::INFINITY {
|
||||||
*transform = vello_encoding::Transform::from_kurbo(&scaled_infinite_transform);
|
*transform = vello_encoding::Transform::from_kurbo(&scaled_infinite_transform);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue