From 35c9f229e74669524ac83cb2fb0cb6a4117f2d06 Mon Sep 17 00:00:00 2001 From: Samuel Selleck Date: Tue, 29 Oct 2024 13:12:47 -0700 Subject: [PATCH 1/3] image transform working --- .../web/src/classes/native-element-pool.ts | 1 - .../files/interfaces/web/src/index.ts | 4 +- pax-pixels/src/render_backend/data.rs | 6 +- pax-pixels/src/render_backend/geometry.wgsl | 29 +++--- pax-pixels/src/render_backend/mod.rs | 66 +++++++------ pax-pixels/src/render_backend/texture.rs | 36 +++++-- pax-pixels/src/render_backend/textures.wgsl | 2 +- pax-pixels/src/render_context.rs | 96 +++++++++---------- pax-runtime/src/engine/mod.rs | 1 + .../src/engine/pax_pixels_render_context.rs | 4 +- 10 files changed, 135 insertions(+), 110 deletions(-) diff --git a/pax-compiler/files/interfaces/web/src/classes/native-element-pool.ts b/pax-compiler/files/interfaces/web/src/classes/native-element-pool.ts index 094425543..aa4561ed5 100644 --- a/pax-compiler/files/interfaces/web/src/classes/native-element-pool.ts +++ b/pax-compiler/files/interfaces/web/src/classes/native-element-pool.ts @@ -1092,7 +1092,6 @@ export class NativeElementPool { } async imageLoad(patch: ImageLoadPatch, chassis: PaxChassisWeb) { - if (chassis.image_loaded(patch.path ?? "")) { return } diff --git a/pax-compiler/files/interfaces/web/src/index.ts b/pax-compiler/files/interfaces/web/src/index.ts index c97010c5f..696e08450 100644 --- a/pax-compiler/files/interfaces/web/src/index.ts +++ b/pax-compiler/files/interfaces/web/src/index.ts @@ -291,7 +291,9 @@ export function processMessages(messages: any[], chassis: PaxChassisWeb, objectM let msg = unwrapped_msg["ImageLoad"]; let patch: ImageLoadPatch = objectManager.getFromPool(IMAGE_LOAD_PATCH); patch.fromPatch(msg); - nativePool.imageLoad(patch, chassis) + queueMicrotask(async () => { + await nativePool.imageLoad(patch, chassis); + }); }else if(unwrapped_msg["ScrollerCreate"]) { let msg = unwrapped_msg["ScrollerCreate"] let patch: AnyCreatePatch = objectManager.getFromPool(ANY_CREATE_PATCH); diff --git a/pax-pixels/src/render_backend/data.rs b/pax-pixels/src/render_backend/data.rs index b302a62ac..736c98361 100644 --- a/pax-pixels/src/render_backend/data.rs +++ b/pax-pixels/src/render_backend/data.rs @@ -16,8 +16,8 @@ pub(crate) struct GpuPrimitive { pub fill_id: u16, pub fill_type_flag: u16, pub z_index: i32, - pub clipping_id: u32, - pub transform_id: u32, //not used atm + pub clipping_id: u32, //not used atm + pub transform_id: u32, } #[repr(C)] @@ -54,7 +54,7 @@ pub(crate) struct GpuTransform { impl Default for GpuTransform { fn default() -> Self { Self { - transform: Transform2D::scale(1000.0, 1000.0).to_arrays(), + transform: Transform2D::identity().to_arrays(), _pad: 0, _pad2: 0, } diff --git a/pax-pixels/src/render_backend/geometry.wgsl b/pax-pixels/src/render_backend/geometry.wgsl index 96a23e312..35c1b4290 100644 --- a/pax-pixels/src/render_backend/geometry.wgsl +++ b/pax-pixels/src/render_backend/geometry.wgsl @@ -7,8 +7,8 @@ struct Globals { struct Primitive { fill_id_and_type: u32, z_index: i32, - clipping_id: u32, - transform_id: u32, //not used atm + clipping_id: u32, //not used atm + transform_id: u32, }; struct Primitives { @@ -57,7 +57,7 @@ struct Gradients { @group(0) @binding(0) var globals: Globals; @group(0) @binding(1) var u_primitives: Primitives; -@group(0) @binding(2) var clipping: Transforms; +@group(0) @binding(2) var transforms: Transforms; @group(0) @binding(3) var colors: Colors; @group(0) @binding(4) var gradients: Gradients; @@ -77,7 +77,17 @@ fn vs_main( model: GpuVertex, ) -> VertexOutput { var out: VertexOutput; - var pos = model.position; + var p = model.position; + + // apply transform + let primitive = u_primitives.primitives[model.prim_id]; + let m = transforms.transforms[primitive.transform_id]; + + let t_p_x = p.x * m.xx + p.y * m.yx + m.zx; + let t_p_y = p.x * m.xy + p.y * m.yy + m.zy; + + var pos = vec2(t_p_x, t_p_y); + pos /= globals.resolution; pos *= 2.0; pos -= 1.0; @@ -87,22 +97,13 @@ fn vs_main( out.clip_position = vec4(pos, 0.0, 1.0); return out; } + // Fragment shader @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let primitive = u_primitives.primitives[in.prim_id]; - // let p = in.clip_position.xy; - // let m = clipping.transforms[primitive.clipping_id]; - // let t_p_x = p.x * m.xx + p.y * m.yx + m.zx; - // let t_p_y = p.x * m.xy + p.y * m.yy + m.zy; - // if t_p_x > 1.0 || t_p_x < 0.0 || t_p_y > 1.0 || t_p_y < 0.0 { - // discard; - // } - - - //color/gradient let fill_id_and_type = primitive.fill_id_and_type; //clipping rectangle diff --git a/pax-pixels/src/render_backend/mod.rs b/pax-pixels/src/render_backend/mod.rs index c20377f9d..98b344d86 100644 --- a/pax-pixels/src/render_backend/mod.rs +++ b/pax-pixels/src/render_backend/mod.rs @@ -13,7 +13,7 @@ mod gpu_resources; mod texture; use data::{GpuGlobals, GpuPrimitive, GpuVertex}; -use crate::{render_backend::texture::TextureRenderer, Box2D}; +use crate::{render_backend::texture::TextureRenderer, Box2D, Transform2D}; use self::data::{GpuColor, GpuGradient, GpuTransform}; @@ -24,15 +24,14 @@ pub struct RenderConfig { primitive_buffer_size: u64, colors_buffer_size: u64, gradients_buffer_size: u64, - clipping_buffer_size: u64, - transform_buffer_size: u64, + transforms_buffer_size: u64, pub initial_width: u32, pub initial_height: u32, pub initial_dpr: u32, } impl RenderConfig { - pub fn new(debug: bool, width: u32, height: u32, dpr: u32) -> Self { + pub fn new(_debug: bool, width: u32, height: u32, dpr: u32) -> Self { Self { debug: false, index_buffer_size: 2 << 12, @@ -40,8 +39,7 @@ impl RenderConfig { primitive_buffer_size: 512, colors_buffer_size: 512, gradients_buffer_size: 64, - clipping_buffer_size: 64, - transform_buffer_size: 64, + transforms_buffer_size: 64, initial_width: width, initial_height: height, initial_dpr: dpr, @@ -68,7 +66,7 @@ pub struct RenderBackend<'w> { vertex_buffer: wgpu::Buffer, index_buffer: wgpu::Buffer, primitive_buffer: wgpu::Buffer, - clipping_buffer: wgpu::Buffer, + transforms_buffer: wgpu::Buffer, colors_buffer: wgpu::Buffer, gradients_buffer: wgpu::Buffer, @@ -182,21 +180,21 @@ impl<'w> RenderBackend<'w> { config.primitive_buffer_size, BufferUsages::UNIFORM | BufferUsages::COPY_DST, ); - let (_, clipping_buffer) = create_buffer::( + let (_, transforms_buffer) = create_buffer::( &device, - "Clipping Buffer", - config.clipping_buffer_size, + "Transform Buffer", + config.transforms_buffer_size, BufferUsages::UNIFORM | BufferUsages::COPY_DST, ); let (_, colors_buffer) = create_buffer::( &device, - "Clipping Buffer", + "Colors Buffer", config.colors_buffer_size, BufferUsages::UNIFORM | BufferUsages::COPY_DST, ); let (_, gradients_buffer) = create_buffer::( &device, - "Clipping Buffer", + "Gradients Buffer", config.gradients_buffer_size, BufferUsages::UNIFORM | BufferUsages::COPY_DST, ); @@ -216,7 +214,7 @@ impl<'w> RenderBackend<'w> { }, wgpu::BindGroupLayoutEntry { binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, @@ -226,7 +224,7 @@ impl<'w> RenderBackend<'w> { }, wgpu::BindGroupLayoutEntry { binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, @@ -270,7 +268,7 @@ impl<'w> RenderBackend<'w> { }, wgpu::BindGroupEntry { binding: 2, - resource: clipping_buffer.as_entire_binding(), + resource: transforms_buffer.as_entire_binding(), }, wgpu::BindGroupEntry { binding: 3, @@ -304,7 +302,7 @@ impl<'w> RenderBackend<'w> { vertex_buffer, index_buffer, primitive_buffer, - clipping_buffer, + transforms_buffer, globals_buffer, colors_buffer, gradients_buffer, @@ -387,9 +385,9 @@ impl<'w> RenderBackend<'w> { let CpuBuffers { geometry: ref mut geom, ref mut primitives, - ref mut stencils, ref mut colors, ref mut gradients, + ref mut transforms, } = buffers; //Add ghost triangles to follow COPY_BUFFER_ALIGNMENT requirement const ALIGNMENT: usize = 16; @@ -404,8 +402,8 @@ impl<'w> RenderBackend<'w> { while primitives.len() * std::mem::size_of::() % ALIGNMENT != 0 { primitives.push(GpuPrimitive::default()); } - while stencils.len() * std::mem::size_of::() % ALIGNMENT != 0 { - stencils.push(GpuTransform::default()); + while transforms.len() * std::mem::size_of::() % ALIGNMENT != 0 { + transforms.push(GpuTransform::default()); } while colors.len() * std::mem::size_of::() % ALIGNMENT != 0 { colors.push(GpuColor::default()); @@ -417,7 +415,7 @@ impl<'w> RenderBackend<'w> { if geom.indices.len() >= self.config.index_buffer_size as usize || geom.vertices.len() >= self.config.vertex_buffer_size as usize || primitives.len() >= self.config.primitive_buffer_size as usize - || stencils.len() >= self.config.clipping_buffer_size as usize + || transforms.len() >= self.config.transforms_buffer_size as usize || colors.len() >= self.config.colors_buffer_size as usize || gradients.len() >= self.config.gradients_buffer_size as usize { @@ -436,7 +434,7 @@ impl<'w> RenderBackend<'w> { self.queue .write_buffer(&self.gradients_buffer, 0, bytemuck::cast_slice(gradients)); self.queue - .write_buffer(&self.clipping_buffer, 0, bytemuck::cast_slice(stencils)); + .write_buffer(&self.transforms_buffer, 0, bytemuck::cast_slice(transforms)); self.index_count = geom.indices.len() as u64; } @@ -486,7 +484,7 @@ impl<'w> RenderBackend<'w> { (screen_surface, screen_texture) } - pub(crate) fn render_image(&mut self, image: &Image, rect: Box2D) { + pub(crate) fn render_image(&mut self, image: &Image, transform: Transform2D, rect: Box2D) { let (screen_surface, screen_texture) = self.get_screen_texture(); self.texture_renderer.render_image( &self.device, @@ -495,6 +493,7 @@ impl<'w> RenderBackend<'w> { &self.globals_buffer, &image.rgba, image.pixel_width, + transform, rect, ); screen_surface.present(); @@ -538,20 +537,27 @@ impl<'w> RenderBackend<'w> { pub(crate) struct CpuBuffers { pub geometry: VertexBuffers, pub primitives: Vec, - pub stencils: Vec, + pub transforms: Vec, pub colors: Vec, pub gradients: Vec, } impl CpuBuffers { pub(crate) fn reset(&mut self) { - self.geometry.vertices.clear(); - self.geometry.indices.clear(); - self.primitives.clear(); - self.stencils.clear(); - self.stencils.push(GpuTransform::default()); - self.colors.clear(); - self.gradients.clear(); + let CpuBuffers { + geometry, + primitives, + transforms, + colors, + gradients, + } = self; + geometry.vertices.clear(); + geometry.indices.clear(); + primitives.clear(); + colors.clear(); + gradients.clear(); + // leave the identity transform at the start + transforms.truncate(1); } } diff --git a/pax-pixels/src/render_backend/texture.rs b/pax-pixels/src/render_backend/texture.rs index 8b23bf15a..a33b8bfee 100644 --- a/pax-pixels/src/render_backend/texture.rs +++ b/pax-pixels/src/render_backend/texture.rs @@ -1,15 +1,14 @@ use bytemuck::Pod; use bytemuck::Zeroable; +use lyon::geom::Point; use wgpu::BufferUsages; use wgpu::IndexFormat; -use wgpu::{util::RenderEncoder, BindGroupLayout, RenderPass}; use crate::Box2D; +use crate::Transform2D; use wgpu::util::DeviceExt; use wgpu::TextureFormat; -use super::data::GpuGlobals; - pub struct TextureRenderer { vertices_buffer: wgpu::Buffer, indices_buffer: wgpu::Buffer, @@ -142,27 +141,29 @@ impl TextureRenderer { globals: &wgpu::Buffer, rgba: &[u8], rgba_width: u32, + transform: Transform2D, location: Box2D, ) { let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Texture Encoder"), }); + let points = get_transformed_corners(&location, &transform); let height = rgba.len() as u32 / (rgba_width * 4); let verts = [ TextureVertex { - position: [location.min.x, location.min.y], + position: points[0].to_array(), texture_coord: [0.0, 0.0], }, TextureVertex { - position: [location.max.x, location.min.y], + position: points[1].to_array(), texture_coord: [1.0, 0.0], }, TextureVertex { - position: [location.min.x, location.max.y], + position: points[2].to_array(), texture_coord: [0.0, 1.0], }, TextureVertex { - position: [location.max.x, location.max.y], + position: points[3].to_array(), texture_coord: [1.0, 1.0], }, ]; @@ -265,3 +266,24 @@ impl TextureVertex { } } } + +fn get_transformed_corners(box2d: &Box2D, transform: &Transform2D) -> [Point; 4] { + let min = box2d.min; + let max = box2d.max; + + // Get all 4 corners + let corners = [ + Point::new(min.x, min.y), // Top-left + Point::new(max.x, min.y), // Top-right + Point::new(min.x, max.y), // Bottom-left + Point::new(max.x, max.y), // Bottom-right + ]; + + // Transform each corner + [ + transform.transform_point(corners[0]), + transform.transform_point(corners[1]), + transform.transform_point(corners[2]), + transform.transform_point(corners[3]), + ] +} diff --git a/pax-pixels/src/render_backend/textures.wgsl b/pax-pixels/src/render_backend/textures.wgsl index 5175afbcb..14e1a7a2a 100644 --- a/pax-pixels/src/render_backend/textures.wgsl +++ b/pax-pixels/src/render_backend/textures.wgsl @@ -37,5 +37,5 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { let t = textureSample(texture, texture_sampler, in.texture_coord); - return vec4(t.x + in.texture_coord.x/1000.0, t.y + in.texture_coord.y/1000.0, t.z, 1.0); + return vec4(t.x + in.texture_coord.x/1000.0, t.y + in.texture_coord.y/1000.0, t.z, t.w); } diff --git a/pax-pixels/src/render_context.rs b/pax-pixels/src/render_context.rs index 8bef3a64a..1aa7cdce9 100644 --- a/pax-pixels/src/render_context.rs +++ b/pax-pixels/src/render_context.rs @@ -24,65 +24,41 @@ use crate::render_backend::RenderBackend; pub struct WgpuRenderer<'w> { buffers: CpuBuffers, render_backend: RenderBackend<'w>, - transform_stack: Vec, - clipping_stack: Vec, - // used for save/restore of transform/clipping stack - saves: Vec<(usize, usize)>, + // these reference indicies in the CpuBuffer "transforms" + transform_index_stack: Vec, + saves: Vec, tolerance: f32, } -const IDENTITY: Transform2D = Transform2D::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); - impl<'w> WgpuRenderer<'w> { pub fn new(render_backend: RenderBackend<'w>) -> Self { let geometry: VertexBuffers = VertexBuffers::new(); - let default_clipp = GpuTransform { - transform: Transform2D::scale(1000.0, 1000.0).to_arrays(), - _pad: 0, - _pad2: 0, - }; Self { render_backend, - transform_stack: Vec::new(), buffers: CpuBuffers { geometry, primitives: Vec::new(), colors: Vec::new(), gradients: Vec::new(), - stencils: vec![default_clipp], + transforms: vec![GpuTransform::default()], }, - clipping_stack: vec![0], tolerance: 0.5, //TODO expose as option + transform_index_stack: vec![], saves: vec![], } } - fn current_transform(&self) -> &Transform2D { - self.transform_stack.last().unwrap_or(&IDENTITY) - } - - fn current_clipping_id(&self) -> u32 { - *self.clipping_stack.last().expect("clipper stack empty???") - } - - pub fn push_clipping_bounds(&mut self, bounds: Box2D) { - let point_to_unit_rect = Transform2D::translation(-bounds.min.x, -bounds.min.y) - .then_scale(1.0 / bounds.width(), 1.0 / bounds.height()); - let clipping_bounds = self - .current_transform() - .inverse() - .expect("non-invertible transform was pushed to the stack") //TODO how to handle this better? - .then(&point_to_unit_rect); - self.clipping_stack.push(self.buffers.stencils.len() as u32); - self.buffers.stencils.push(GpuTransform { - transform: clipping_bounds.to_arrays(), - _pad: 0, - _pad2: 0, - }); + fn current_transform(&self) -> Transform2D { + Transform2D::from_arrays( + self.buffers + .transforms + .get(self.transform_index_stack.last().cloned().unwrap_or(0)) + .expect("at least one identity transform should exist on the transform stack") + .transform, + ) } pub fn stroke_path(&mut self, path: Path, stroke_fill: Fill, stroke_width: f32) { - let path = path.transformed(self.current_transform()); let prim_id = self.push_primitive_def(stroke_fill); let options = StrokeOptions::tolerance(self.tolerance).with_line_width(stroke_width); let mut geometry_builder = @@ -100,7 +76,6 @@ impl<'w> WgpuRenderer<'w> { } pub fn fill_path(&mut self, path: Path, fill: Fill) { - let path = path.transformed(self.current_transform()); let prim_id = self.push_primitive_def(fill); let options = FillOptions::tolerance(self.tolerance); let mut geometry_builder = @@ -162,8 +137,8 @@ impl<'w> WgpuRenderer<'w> { let primitive = GpuPrimitive { fill_id, fill_type_flag, - clipping_id: self.current_clipping_id(), - transform_id: 0, + clipping_id: 0, + transform_id: self.transform_index_stack.last().cloned().unwrap_or(0) as u32, z_index: 0, }; let prim_id = self.buffers.primitives.len() as u32; @@ -176,33 +151,52 @@ impl<'w> WgpuRenderer<'w> { } pub fn draw_image(&mut self, image: &Image, rect: Box2D) { - self.flush(); - self.render_backend.render_image(image, rect); + let transform = self.current_transform(); + if self.buffers.primitives.len() > 0 { + self.render_backend.render_primitives(&mut self.buffers); + let CpuBuffers { + geometry, + primitives, + transforms: _, + colors, + gradients, + } = &mut self.buffers; + geometry.vertices.clear(); + geometry.indices.clear(); + primitives.clear(); + colors.clear(); + gradients.clear(); + } + self.render_backend.render_image(image, transform, rect); } pub fn flush(&mut self) { if self.buffers.primitives.len() > 0 { self.render_backend.render_primitives(&mut self.buffers); - self.buffers.reset(); } + self.buffers.reset(); + self.transform_index_stack.clear(); } pub fn save(&mut self) { - let transform_len = self.transform_stack.len(); - let clipping_len = self.clipping_stack.len(); - self.saves.push((transform_len, clipping_len)); + let transform_len = self.transform_index_stack.len(); + self.saves.push(transform_len); } pub fn restore(&mut self) { - if let Some((t_pen, c_len)) = self.saves.pop() { - self.transform_stack.truncate(t_pen); - self.clipping_stack.truncate(c_len); + if let Some(t_pen) = self.saves.pop() { + self.transform_index_stack.truncate(t_pen); } } pub fn push_transform(&mut self, transform: Transform2D) { - let last = self.current_transform(); - self.transform_stack.push(transform.then(last)); + let new_ind = self.buffers.transforms.len(); + self.buffers.transforms.push(GpuTransform { + transform: transform.then(&self.current_transform()).to_arrays(), + _pad: 0, + _pad2: 0, + }); + self.transform_index_stack.push(new_ind); } pub fn resize(&mut self, width: f32, height: f32) { diff --git a/pax-runtime/src/engine/mod.rs b/pax-runtime/src/engine/mod.rs index e52fa05f8..6f622882f 100644 --- a/pax-runtime/src/engine/mod.rs +++ b/pax-runtime/src/engine/mod.rs @@ -271,6 +271,7 @@ impl PaxEngine { /// a. find lowest node (last child of last node) /// b. start rendering, from lowest node on-up, throughout tree pub fn tick(&mut self) -> Vec { + // self.runtime_context.set_all_canvases_dirty(); // // 1. UPDATE NODES (properties, etc.). This part we should be able to // completely remove once reactive properties dirty-dag is a thing. diff --git a/pax-runtime/src/engine/pax_pixels_render_context.rs b/pax-runtime/src/engine/pax_pixels_render_context.rs index 9679d0758..376bdc293 100644 --- a/pax-runtime/src/engine/pax_pixels_render_context.rs +++ b/pax-runtime/src/engine/pax_pixels_render_context.rs @@ -123,6 +123,7 @@ impl RenderContext for PaxPixelsRenderer { } }); } + fn get_image_size(&mut self, image_path: &str) -> Option<(usize, usize)> { self.image_map .get(image_path) @@ -130,8 +131,7 @@ impl RenderContext for PaxPixelsRenderer { } fn image_loaded(&self, image_path: &str) -> bool { - self.image_map.contains_key(image_path); - true + self.image_map.contains_key(image_path) } fn layers(&self) -> usize { From 1e5c308a68504b4bf805351c5a30bb1286881316 Mon Sep 17 00:00:00 2001 From: Samuel Selleck Date: Tue, 29 Oct 2024 15:23:11 -0700 Subject: [PATCH 2/3] gradient rendering working --- pax-pixels/src/render_backend/geometry.wgsl | 106 ++++++------------ .../src/engine/pax_pixels_render_context.rs | 81 +++++++++++-- 2 files changed, 105 insertions(+), 82 deletions(-) diff --git a/pax-pixels/src/render_backend/geometry.wgsl b/pax-pixels/src/render_backend/geometry.wgsl index 35c1b4290..8e8e9495e 100644 --- a/pax-pixels/src/render_backend/geometry.wgsl +++ b/pax-pixels/src/render_backend/geometry.wgsl @@ -120,85 +120,49 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { } - fn gradient(fill_id: u32, coord: vec2) -> vec4 { - let gradient = gradients.gradients[fill_id]; - - //color space for linear gradient: - let g_p = gradient.position*f32(globals.dpr); - let g_a = gradient.main_axis*f32(globals.dpr); + + // Calculate color space position + let g_p = gradient.position * f32(globals.dpr); + let g_a = gradient.main_axis * f32(globals.dpr); let p_t = coord - g_p; let m_a_l = length(g_a); - let n = g_a/m_a_l; - let t_m = dot(p_t, n); //this is the ammount that p_t points in the direction of n - let color_space = t_m/m_a_l; //This is 0.0..=1.0 and can be mapped to color space with coords + let n = g_a / m_a_l; + let color_space = dot(p_t, n); - //figure out surrounding stops in color space: let s1 = gradient.stops_set1; let s2 = gradient.stops_set2; let stops = array(s1[0], s1[1], s1[2], s1[3], s2[0], s2[1], s2[2], s2[3]); - let colors = gradient.colors; - - //assumed to be 2 or larger - let len = gradient.stop_count; - var color: vec4; - + + // Find the appropriate stop segment + var left_idx = 0u; + var right_idx = 1u; + + // Handle edge cases first + if color_space <= stops[0] { + return gradient.colors[0]; + } + if color_space >= stops[gradient.stop_count - 1u] { + return gradient.colors[gradient.stop_count - 1u]; + } - // This is horrible, but can't use dynamic indicies in loops in wgsl. One - // possible path forward would be to create 1d textures to sample from - // instead. - var left_stop: f32; - var right_stop: f32; - var left_col: vec4; - var right_col: vec4; - if stops[0] > color_space { - left_stop = stops[0] - 1.0; - right_stop = stops[0]; - left_col = colors[0]; - right_col = colors[0]; - } else if stops[1] > color_space || len <= 1u { - left_stop = stops[0]; - right_stop = stops[1]; - left_col = colors[0]; - right_col = colors[1]; - } else if stops[2] > color_space || len <= 2u { - left_stop = stops[1]; - right_stop = stops[2]; - left_col = colors[1]; - right_col = colors[2]; - } else if stops[3] > color_space || len <= 3u { - left_stop = stops[2]; - right_stop = stops[3]; - left_col = colors[2]; - right_col = colors[3]; - } else if stops[4] > color_space || len <= 4u { - left_stop = stops[3]; - right_stop = stops[4]; - left_col = colors[3]; - right_col = colors[4]; - } else if stops[5] > color_space || len <= 5u { - left_stop = stops[4]; - right_stop = stops[5]; - left_col = colors[4]; - right_col = colors[5]; - } else if stops[6] > color_space || len <= 6u { - left_stop = stops[5]; - right_stop = stops[6]; - left_col = colors[5]; - right_col = colors[6]; - } else if stops[7] > color_space || len <= 7u { - left_stop = stops[6]; - right_stop = stops[7]; - left_col = colors[6]; - right_col = colors[7]; - } else { - left_stop = stops[7] - 1.0; - right_stop = stops[7]; - left_col = colors[7]; - right_col = colors[7]; + // Find the segment using a fixed loop + for (var i = 0u; i < 7u; i++) { + if i >= gradient.stop_count - 1u { break; } + if stops[i + 1u] > color_space { + left_idx = i; + right_idx = i + 1u; + break; + } } - let space = (color_space - left_stop)/(right_stop - left_stop); - color = left_col*(1.0 - space) + right_col*space; - return color; + + let left_stop = stops[left_idx]; + let right_stop = stops[right_idx]; + let left_col = gradient.colors[left_idx]; + let right_col = gradient.colors[right_idx]; + + let t = (color_space - left_stop) / (right_stop - left_stop); + return mix(left_col, right_col, t); } + diff --git a/pax-runtime/src/engine/pax_pixels_render_context.rs b/pax-runtime/src/engine/pax_pixels_render_context.rs index 376bdc293..10378219b 100644 --- a/pax-runtime/src/engine/pax_pixels_render_context.rs +++ b/pax-runtime/src/engine/pax_pixels_render_context.rs @@ -1,6 +1,6 @@ -use kurbo::{BezPath, PathEl}; +use kurbo::{BezPath, PathEl, Shape}; use pax_pixels::{point, Box2D, Image, Path, Transform2D, WgpuRenderer}; -use pax_runtime_api::RenderContext; +use pax_runtime_api::{Axis, RenderContext}; use std::{cell::RefCell, collections::HashMap, future::Future, pin::Pin, rc::Rc}; pub struct PaxPixelsRenderer { @@ -52,8 +52,9 @@ impl PaxPixelsRenderer { impl RenderContext for PaxPixelsRenderer { fn fill(&mut self, layer: usize, path: kurbo::BezPath, fill: &pax_runtime_api::Fill) { self.with_layer_context(layer, |context| { + let bounds = path.bounding_box(); let path = convert_kurbo_to_lyon_path(&path); - let fill = to_pax_pixels_fill(fill); + let fill = to_pax_pixels_fill(fill, bounds); context.fill_path(path, fill); }); } @@ -66,9 +67,10 @@ impl RenderContext for PaxPixelsRenderer { width: f64, ) { self.with_layer_context(layer, |context| { + let bounds = path.bounding_box(); context.stroke_path( convert_kurbo_to_lyon_path(&path), - to_pax_pixels_fill(fill), + to_pax_pixels_fill(fill, bounds), width as f32, ); }); @@ -191,18 +193,75 @@ impl RenderContext for PaxPixelsRenderer { } } -fn to_pax_pixels_fill(fill: &pax_runtime_api::Fill) -> pax_pixels::Fill { +fn to_pax_pixels_fill(fill: &pax_runtime_api::Fill, rect: kurbo::Rect) -> pax_pixels::Fill { + let bounds = (rect.width(), rect.height()); + let orig = rect.origin(); match fill { - pax_runtime_api::Fill::Solid(color) => pax_pixels::Fill::Solid({ - let [r, g, b, a] = color.to_rgba_0_1(); - pax_pixels::Color::rgba(r as f32, g as f32, b as f32, a as f32) - }), + pax_runtime_api::Fill::Solid(color) => pax_pixels::Fill::Solid(to_pax_pixels_color(color)), // TODO fill in impls - pax_runtime_api::Fill::LinearGradient(_gradient) => todo!(), - pax_runtime_api::Fill::RadialGradient(_graident) => todo!(), + pax_runtime_api::Fill::LinearGradient(gradient) => { + let start_x = gradient.start.0.evaluate(bounds, Axis::X); + let start_y = gradient.start.1.evaluate(bounds, Axis::Y); + let end_x = gradient.end.0.evaluate(bounds, Axis::X); + let end_y = gradient.end.1.evaluate(bounds, Axis::Y); + let main_axis = + pax_pixels::Vector2D::new((end_x - start_x) as f32, (end_y - start_y) as f32); + pax_pixels::Fill::Gradient { + stops: gradient + .stops + .iter() + .map(|g| pax_pixels::GradientStop { + color: to_pax_pixels_color(&g.color), + stop: g + .position + .evaluate((main_axis.length() as f64, 0.0), Axis::X) + as f32, + }) + .collect(), + gradient_type: pax_pixels::GradientType::Linear, + pos: pax_pixels::Point2D::new((orig.x + start_x) as f32, (orig.y + start_y) as f32), + main_axis, + off_axis: pax_pixels::Vector2D::zero(), //not used for linear + } + } + pax_runtime_api::Fill::RadialGradient(gradient) => { + let start_x = gradient.start.0.evaluate(bounds, Axis::X); + let start_y = gradient.start.1.evaluate(bounds, Axis::Y); + let end_x = gradient.end.0.evaluate(bounds, Axis::X); + let end_y = gradient.end.1.evaluate(bounds, Axis::Y); + let r = gradient.radius as f32; + let main_axis = pax_pixels::Vector2D::new( + r * (end_x - start_x) as f32, + r * (end_y - start_y) as f32, + ); + // rotate 90 deg + let off_axis = pax_pixels::Vector2D::new(-main_axis.y, main_axis.x); + pax_pixels::Fill::Gradient { + gradient_type: pax_pixels::GradientType::Radial, + pos: pax_pixels::Point2D::new((orig.x + start_x) as f32, (orig.y + start_y) as f32), + main_axis, + off_axis, + stops: gradient + .stops + .iter() + .map(|g| pax_pixels::GradientStop { + color: to_pax_pixels_color(&g.color), + stop: g + .position + .evaluate((main_axis.length() as f64, 0.0), Axis::X) + as f32, + }) + .collect(), + } + } } } +pub fn to_pax_pixels_color(color: &pax_runtime_api::Color) -> pax_pixels::Color { + let [r, g, b, a] = color.to_rgba_0_1(); + pax_pixels::Color::rgba(r as f32, g as f32, b as f32, a as f32) +} + pub fn convert_kurbo_to_lyon_path(kurbo_path: &BezPath) -> Path { let mut builder = Path::builder(); let mut closed = false; From 5a7d8252bda7c94ee948fc7ccda8ec94b05b75d9 Mon Sep 17 00:00:00 2001 From: Samuel Selleck Date: Tue, 29 Oct 2024 16:42:32 -0700 Subject: [PATCH 3/3] keep track of failed context borrows --- pax-runtime-api/src/lib.rs | 2 +- pax-runtime/src/engine/mod.rs | 8 +-- .../src/engine/pax_pixels_render_context.rs | 56 +++++++++++++++---- pax-runtime/src/engine/piet_render_context.rs | 2 +- pax-runtime/src/properties.rs | 4 +- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/pax-runtime-api/src/lib.rs b/pax-runtime-api/src/lib.rs index f2038ea5b..72880b4aa 100644 --- a/pax-runtime-api/src/lib.rs +++ b/pax-runtime-api/src/lib.rs @@ -71,7 +71,7 @@ pub trait RenderContext { fn layers(&self) -> usize; fn resize_layers_to(&mut self, layer_count: usize, dirty_canvases: Rc>>); fn clear(&mut self, layer: usize); - fn flush(&mut self, layer: usize); + fn flush(&mut self, layer: usize, dirty_canvases: Rc>>); fn resize(&mut self, width: usize, height: usize); } diff --git a/pax-runtime/src/engine/mod.rs b/pax-runtime/src/engine/mod.rs index 6f622882f..5923a693a 100644 --- a/pax-runtime/src/engine/mod.rs +++ b/pax-runtime/src/engine/mod.rs @@ -271,7 +271,6 @@ impl PaxEngine { /// a. find lowest node (last child of last node) /// b. start rendering, from lowest node on-up, throughout tree pub fn tick(&mut self) -> Vec { - // self.runtime_context.set_all_canvases_dirty(); // // 1. UPDATE NODES (properties, etc.). This part we should be able to // completely remove once reactive properties dirty-dag is a thing. @@ -299,7 +298,7 @@ impl PaxEngine { .borrow() .get(i) .cloned() - .unwrap_or(true) + .unwrap_or(false) { rcs.clear(i); } @@ -310,10 +309,11 @@ impl PaxEngine { .recurse_render_queue(&mut self.runtime_context, rcs); self.runtime_context.recurse_flush_queued_renders(rcs); + self.runtime_context.clear_all_dirty_canvases(); + for i in 0..rcs.layers() { - rcs.flush(i); + rcs.flush(i, Rc::clone(&self.runtime_context.dirty_canvases)); } - self.runtime_context.clear_all_dirty_canvases(); //dirtify the canvases that where created this frame for i in new_range { diff --git a/pax-runtime/src/engine/pax_pixels_render_context.rs b/pax-runtime/src/engine/pax_pixels_render_context.rs index 10378219b..0e98a33d1 100644 --- a/pax-runtime/src/engine/pax_pixels_render_context.rs +++ b/pax-runtime/src/engine/pax_pixels_render_context.rs @@ -8,6 +8,7 @@ pub struct PaxPixelsRenderer { layer_factory: Rc Pin>>>>>, image_map: HashMap, + failed_context_gets: RefCell>, } pub enum RenderLayerState { @@ -24,6 +25,7 @@ impl PaxPixelsRenderer { backends: Default::default(), layer_factory: Rc::new(layer_factory), image_map: Default::default(), + failed_context_gets: RefCell::new(vec![]), } } } @@ -34,10 +36,17 @@ impl PaxPixelsRenderer { match backends.get_mut(layer) { Some(layer_state) => match layer_state { RenderLayerState::Pending => { - log::warn!( - "tried to retrieve layer {} context that wasn't ready", - layer - ); + let mut failed_context_gets = self.failed_context_gets.borrow_mut(); + if failed_context_gets.len() <= layer { + failed_context_gets.resize(layer + 1, false); + } + failed_context_gets[layer] = true; + // this happens to often to be useful right now - how to handle asyncness + // better here feels important + // log::warn!( + // "tried to retrieve layer {} context that wasn't ready", + // layer + // ); } RenderLayerState::Ready(renderer) => f(renderer), }, @@ -155,13 +164,20 @@ impl RenderContext for PaxPixelsRenderer { let dirty_canvases = Rc::clone(&dirty_canvases); wasm_bindgen_futures::spawn_local(async move { let backend = (factory)(i).await; - if let (Some(change), Some(backend)) = - (backends.borrow_mut().get_mut(i), backend) - { - *change = RenderLayerState::Ready(backend); - if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(i) { - *dirty_bit = true; + match (backends.borrow_mut().get_mut(i), backend) { + (Some(change), Some(backend)) => { + *change = RenderLayerState::Ready(backend); + if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(i) { + *dirty_bit = true; + } } + (Some(_), None) => log::warn!( + "failed to set poll state to ready: backend failed to initialize" + ), + (None, Some(_)) => { + log::warn!("failed to set poll state to ready: layer doesn't exist anymore") + } + (None, None) => log::warn!("failed to set poll state to ready: layer doesn't exist AND backend failed to initialize") } }); } @@ -175,8 +191,26 @@ impl RenderContext for PaxPixelsRenderer { }); } - fn flush(&mut self, layer: usize) { + fn flush(&mut self, layer: usize, dirty_canvases: Rc>>) { + // HACK: GPU rendering currently doesn't correctly handle + // re-dirtyfying canvases that where created but not ready before + // being drawn to - maybe encapsulate canvas dirtifiation inside + // rendercontext? + if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(layer) { + *dirty_bit = true; + } + self.with_layer_context(layer, |context| { + if let Some(failed) = self.failed_context_gets.borrow_mut().get_mut(layer) { + if *failed { + if let Some(dirty_bit) = dirty_canvases.borrow_mut().get_mut(layer) { + // if we failed to draw to this layer last frame because the context wasn't + // vailable yet, set this canvas to dirty for next frame + *dirty_bit = true; + } + *failed = false; + } + } context.flush(); }); } diff --git a/pax-runtime/src/engine/piet_render_context.rs b/pax-runtime/src/engine/piet_render_context.rs index da029181f..ad6689354 100644 --- a/pax-runtime/src/engine/piet_render_context.rs +++ b/pax-runtime/src/engine/piet_render_context.rs @@ -131,7 +131,7 @@ impl api::RenderContext for PietRenderer { } } - fn flush(&mut self, _layer: usize) { + fn flush(&mut self, _layer: usize, _dirty_canvases: Rc>>) { // NOTE: used for GPU rendering to flush changes to the screen, not needed // during CPU rendering } diff --git a/pax-runtime/src/properties.rs b/pax-runtime/src/properties.rs index e8e6a883d..9ac5bfc06 100644 --- a/pax-runtime/src/properties.rs +++ b/pax-runtime/src/properties.rs @@ -153,7 +153,7 @@ impl RuntimeContext { pub fn resize_canvas_layers_to(&self, id: usize) { let mut dirty_canvases = borrow_mut!(self.dirty_canvases); - dirty_canvases.resize(id, false); + dirty_canvases.resize(id, true); } pub fn clear_all_dirty_canvases(&self) { @@ -171,7 +171,7 @@ impl RuntimeContext { } pub fn is_canvas_dirty(&self, id: &usize) -> bool { - *borrow!(self.dirty_canvases).get(*id).unwrap_or(&false) + *borrow!(self.dirty_canvases).get(*id).unwrap_or(&true) } pub fn set_all_canvases_dirty(&self) {