diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index 9111900..96cbc54 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -104,7 +104,6 @@ pub struct PipelineBuilder { bindings: Vec, binding_flags: Vec, max_textures: u32, - has_descriptor_indexing: bool, } pub struct DescriptorSetBuilder { @@ -644,7 +643,6 @@ impl crate::backend::Device for VkDevice { bindings: Vec::new(), binding_flags: Vec::new(), max_textures: 0, - has_descriptor_indexing: self.gpu_info.has_descriptor_indexing, } } @@ -1090,23 +1088,21 @@ impl crate::backend::PipelineBuilder for PipelineBuilder { } } - fn add_textures(&mut self, max_textures: u32) { + fn add_textures(&mut self, n_images: u32) { let start = self.bindings.len() as u32; - self.bindings.push( - vk::DescriptorSetLayoutBinding::builder() - .binding(start) - .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) - .descriptor_count(max_textures) - .stage_flags(vk::ShaderStageFlags::COMPUTE) - .build(), - ); - let flags = if self.has_descriptor_indexing { - vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT - } else { - Default::default() - }; - self.binding_flags.push(flags); - self.max_textures += max_textures; + for i in 0..n_images { + self.bindings.push( + vk::DescriptorSetLayoutBinding::builder() + .binding(start + i) + .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::COMPUTE) + .build(), + ); + self.binding_flags + .push(vk::DescriptorBindingFlags::default()); + } + self.max_textures += n_images; } unsafe fn create_compute_pipeline( @@ -1195,11 +1191,11 @@ impl crate::backend::DescriptorSetBuilder for DescriptorSetBuilder { .build(), ); } - if pipeline.max_textures > 0 { + if !self.textures.is_empty() { descriptor_pool_sizes.push( vk::DescriptorPoolSize::builder() .ty(vk::DescriptorType::STORAGE_IMAGE) - .descriptor_count(pipeline.max_textures) + .descriptor_count(self.textures.len() as u32) .build(), ); } @@ -1211,15 +1207,11 @@ impl crate::backend::DescriptorSetBuilder for DescriptorSetBuilder { )?; let descriptor_set_layouts = [pipeline.descriptor_set_layout]; - let counts = &[pipeline.max_textures]; - let variable_info = vk::DescriptorSetVariableDescriptorCountAllocateInfo::builder() - .descriptor_counts(counts); let descriptor_sets = device .allocate_descriptor_sets( &vk::DescriptorSetAllocateInfo::builder() .descriptor_pool(descriptor_pool) - .set_layouts(&descriptor_set_layouts) - .push_next(&mut variable_info.build()), + .set_layouts(&descriptor_set_layouts), ) .unwrap(); let mut binding = 0; @@ -1240,6 +1232,7 @@ impl crate::backend::DescriptorSetBuilder for DescriptorSetBuilder { ); binding += 1; } + // maybe chain images and textures together; they're basically identical now for image in &self.images { device.update_descriptor_sets( &[vk::WriteDescriptorSet::builder() @@ -1256,28 +1249,21 @@ impl crate::backend::DescriptorSetBuilder for DescriptorSetBuilder { ); binding += 1; } - if !self.textures.is_empty() { - let infos = self - .textures - .iter() - .map(|texture| { - vk::DescriptorImageInfo::builder() - .sampler(self.sampler) - .image_view(*texture) - .image_layout(vk::ImageLayout::GENERAL) - .build() - }) - .collect::>(); + for image in &self.textures { device.update_descriptor_sets( &[vk::WriteDescriptorSet::builder() .dst_set(descriptor_sets[0]) .dst_binding(binding) .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) - .image_info(&infos) + .image_info(&[vk::DescriptorImageInfo::builder() + .sampler(vk::Sampler::null()) + .image_view(*image) + .image_layout(vk::ImageLayout::GENERAL) + .build()]) .build()], &[], ); - //binding += 1; + binding += 1; } Ok(DescriptorSet { descriptor_set: descriptor_sets[0], diff --git a/piet-gpu-types/src/annotated.rs b/piet-gpu-types/src/annotated.rs index 1bf3e9a..90e54bc 100644 --- a/piet-gpu-types/src/annotated.rs +++ b/piet-gpu-types/src/annotated.rs @@ -17,6 +17,15 @@ piet_gpu! { linewidth: f32, rgba_color: u32, } + struct AnnoLinGradient { + bbox: [f32; 4], + // For stroked fills. + linewidth: f32, + index: u32, + line_x: f32, + line_y: f32, + line_c: f32, + } struct AnnoBeginClip { bbox: [f32; 4], linewidth: f32, @@ -27,6 +36,7 @@ piet_gpu! { enum Annotated { Nop, Color(TagFlags, AnnoColor), + LinGradient(TagFlags, AnnoLinGradient), Image(TagFlags, AnnoImage), BeginClip(TagFlags, AnnoBeginClip), EndClip(AnnoEndClip), diff --git a/piet-gpu-types/src/ptcl.rs b/piet-gpu-types/src/ptcl.rs index 41cebc7..e8c29c3 100644 --- a/piet-gpu-types/src/ptcl.rs +++ b/piet-gpu-types/src/ptcl.rs @@ -17,6 +17,13 @@ piet_gpu! { struct CmdColor { rgba_color: u32, } + struct CmdLinGrad { + index: u32, + // line equation for gradient + line_x: f32, + line_y: f32, + line_c: f32, + } struct CmdImage { index: u32, offset: [i16; 2], @@ -34,6 +41,7 @@ piet_gpu! { Solid, Alpha(CmdAlpha), Color(CmdColor), + LinGrad(CmdLinGrad), Image(CmdImage), BeginClip, EndClip, diff --git a/piet-gpu-types/src/scene.rs b/piet-gpu-types/src/scene.rs index c4a9b9a..03f71ec 100644 --- a/piet-gpu-types/src/scene.rs +++ b/piet-gpu-types/src/scene.rs @@ -25,6 +25,11 @@ piet_gpu! { struct FillColor { rgba_color: u32, } + struct FillLinGradient { + index: u32, + p0: [f32; 2], + p1: [f32; 2], + } struct FillImage { index: u32, offset: [i16; 2], @@ -51,11 +56,12 @@ piet_gpu! { Cubic(CubicSeg), FillColor(FillColor), + FillLinGradient(FillLinGradient), + FillImage(FillImage), SetLineWidth(SetLineWidth), Transform(Transform), BeginClip(Clip), EndClip(Clip), - FillImage(FillImage), SetFillMode(SetFillMode), } } diff --git a/piet-gpu/bin/android.rs b/piet-gpu/bin/android.rs index d3a2aa3..ac9ffa7 100644 --- a/piet-gpu/bin/android.rs +++ b/piet-gpu/bin/android.rs @@ -4,7 +4,7 @@ //! //! Requires the [cargo-apk] tool. //! [cargo-apk]: https://crates.io/crates/cargo-apk - +/* use raw_window_handle::android::AndroidHandle; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle}; @@ -175,3 +175,5 @@ impl GfxState { } } } +*/ +fn main() {} diff --git a/piet-gpu/shader/annotated.h b/piet-gpu/shader/annotated.h index 6b18155..b833574 100644 --- a/piet-gpu/shader/annotated.h +++ b/piet-gpu/shader/annotated.h @@ -10,6 +10,10 @@ struct AnnoColorRef { uint offset; }; +struct AnnoLinGradientRef { + uint offset; +}; + struct AnnoBeginClipRef { uint offset; }; @@ -47,6 +51,21 @@ AnnoColorRef AnnoColor_index(AnnoColorRef ref, uint index) { return AnnoColorRef(ref.offset + index * AnnoColor_size); } +struct AnnoLinGradient { + vec4 bbox; + float linewidth; + uint index; + float line_x; + float line_y; + float line_c; +}; + +#define AnnoLinGradient_size 36 + +AnnoLinGradientRef AnnoLinGradient_index(AnnoLinGradientRef ref, uint index) { + return AnnoLinGradientRef(ref.offset + index * AnnoLinGradient_size); +} + struct AnnoBeginClip { vec4 bbox; float linewidth; @@ -70,10 +89,11 @@ AnnoEndClipRef AnnoEndClip_index(AnnoEndClipRef ref, uint index) { #define Annotated_Nop 0 #define Annotated_Color 1 -#define Annotated_Image 2 -#define Annotated_BeginClip 3 -#define Annotated_EndClip 4 -#define Annotated_size 32 +#define Annotated_LinGradient 2 +#define Annotated_Image 3 +#define Annotated_BeginClip 4 +#define Annotated_EndClip 5 +#define Annotated_size 40 AnnotatedRef Annotated_index(AnnotatedRef ref, uint index) { return AnnotatedRef(ref.offset + index * Annotated_size); @@ -137,6 +157,40 @@ void AnnoColor_write(Alloc a, AnnoColorRef ref, AnnoColor s) { write_mem(a, ix + 5, s.rgba_color); } +AnnoLinGradient AnnoLinGradient_read(Alloc a, AnnoLinGradientRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = read_mem(a, ix + 0); + uint raw1 = read_mem(a, ix + 1); + uint raw2 = read_mem(a, ix + 2); + uint raw3 = read_mem(a, ix + 3); + uint raw4 = read_mem(a, ix + 4); + uint raw5 = read_mem(a, ix + 5); + uint raw6 = read_mem(a, ix + 6); + uint raw7 = read_mem(a, ix + 7); + uint raw8 = read_mem(a, ix + 8); + AnnoLinGradient s; + s.bbox = vec4(uintBitsToFloat(raw0), uintBitsToFloat(raw1), uintBitsToFloat(raw2), uintBitsToFloat(raw3)); + s.linewidth = uintBitsToFloat(raw4); + s.index = raw5; + s.line_x = uintBitsToFloat(raw6); + s.line_y = uintBitsToFloat(raw7); + s.line_c = uintBitsToFloat(raw8); + return s; +} + +void AnnoLinGradient_write(Alloc a, AnnoLinGradientRef ref, AnnoLinGradient s) { + uint ix = ref.offset >> 2; + write_mem(a, ix + 0, floatBitsToUint(s.bbox.x)); + write_mem(a, ix + 1, floatBitsToUint(s.bbox.y)); + write_mem(a, ix + 2, floatBitsToUint(s.bbox.z)); + write_mem(a, ix + 3, floatBitsToUint(s.bbox.w)); + write_mem(a, ix + 4, floatBitsToUint(s.linewidth)); + write_mem(a, ix + 5, s.index); + write_mem(a, ix + 6, floatBitsToUint(s.line_x)); + write_mem(a, ix + 7, floatBitsToUint(s.line_y)); + write_mem(a, ix + 8, floatBitsToUint(s.line_c)); +} + AnnoBeginClip AnnoBeginClip_read(Alloc a, AnnoBeginClipRef ref) { uint ix = ref.offset >> 2; uint raw0 = read_mem(a, ix + 0); @@ -187,6 +241,10 @@ AnnoColor Annotated_Color_read(Alloc a, AnnotatedRef ref) { return AnnoColor_read(a, AnnoColorRef(ref.offset + 4)); } +AnnoLinGradient Annotated_LinGradient_read(Alloc a, AnnotatedRef ref) { + return AnnoLinGradient_read(a, AnnoLinGradientRef(ref.offset + 4)); +} + AnnoImage Annotated_Image_read(Alloc a, AnnotatedRef ref) { return AnnoImage_read(a, AnnoImageRef(ref.offset + 4)); } @@ -208,6 +266,11 @@ void Annotated_Color_write(Alloc a, AnnotatedRef ref, uint flags, AnnoColor s) { AnnoColor_write(a, AnnoColorRef(ref.offset + 4), s); } +void Annotated_LinGradient_write(Alloc a, AnnotatedRef ref, uint flags, AnnoLinGradient s) { + write_mem(a, ref.offset >> 2, (flags << 16) | Annotated_LinGradient); + AnnoLinGradient_write(a, AnnoLinGradientRef(ref.offset + 4), s); +} + void Annotated_Image_write(Alloc a, AnnotatedRef ref, uint flags, AnnoImage s) { write_mem(a, ref.offset >> 2, (flags << 16) | Annotated_Image); AnnoImage_write(a, AnnoImageRef(ref.offset + 4), s); diff --git a/piet-gpu/shader/backdrop.spv b/piet-gpu/shader/backdrop.spv index 6458f4b..6fad4ff 100644 Binary files a/piet-gpu/shader/backdrop.spv and b/piet-gpu/shader/backdrop.spv differ diff --git a/piet-gpu/shader/backdrop_lg.spv b/piet-gpu/shader/backdrop_lg.spv index 7941654..2ecb31f 100644 Binary files a/piet-gpu/shader/backdrop_lg.spv and b/piet-gpu/shader/backdrop_lg.spv differ diff --git a/piet-gpu/shader/binning.comp b/piet-gpu/shader/binning.comp index b5e2dab..313310e 100644 --- a/piet-gpu/shader/binning.comp +++ b/piet-gpu/shader/binning.comp @@ -57,6 +57,7 @@ void main() { int x0 = 0, y0 = 0, x1 = 0, y1 = 0; switch (tag) { case Annotated_Color: + case Annotated_LinGradient: case Annotated_Image: case Annotated_BeginClip: case Annotated_EndClip: diff --git a/piet-gpu/shader/binning.spv b/piet-gpu/shader/binning.spv index 05abf0c..516cc1d 100644 Binary files a/piet-gpu/shader/binning.spv and b/piet-gpu/shader/binning.spv differ diff --git a/piet-gpu/shader/build.ninja b/piet-gpu/shader/build.ninja index b73da2e..c1fcc92 100644 --- a/piet-gpu/shader/build.ninja +++ b/piet-gpu/shader/build.ninja @@ -24,6 +24,3 @@ build backdrop_lg.spv: glsl backdrop.comp | annotated.h tile.h setup.h build coarse.spv: glsl coarse.comp | annotated.h bins.h ptcl.h setup.h build kernel4.spv: glsl kernel4.comp | ptcl.h setup.h - -build kernel4_idx.spv: glsl kernel4.comp | ptcl.h setup.h - flags = -DENABLE_IMAGE_INDICES diff --git a/piet-gpu/shader/coarse.comp b/piet-gpu/shader/coarse.comp index 731da97..e402e9e 100644 --- a/piet-gpu/shader/coarse.comp +++ b/piet-gpu/shader/coarse.comp @@ -229,6 +229,7 @@ void main() { switch (tag) { case Annotated_Color: case Annotated_Image: + case Annotated_LinGradient: case Annotated_BeginClip: case Annotated_EndClip: // We have one "path" for each element, even if the element isn't @@ -344,6 +345,23 @@ void main() { Cmd_Color_write(cmd_alloc, cmd_ref, CmdColor(fill.rgba_color)); cmd_ref.offset += 4 + CmdColor_size; break; + case Annotated_LinGradient: + // TODO: process and write linear gradient + tile = Tile_read(read_tile_alloc(element_ref_ix, mem_ok), TileRef(sh_tile_base[element_ref_ix] + + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); + AnnoLinGradient lin = Annotated_LinGradient_read(conf.anno_alloc, ref); + if (!alloc_cmd(cmd_alloc, cmd_ref, cmd_limit)) { + break; + } + write_fill(cmd_alloc, cmd_ref, tag.flags, tile, fill.linewidth); + CmdLinGrad cmd_lin; + cmd_lin.index = lin.index; + cmd_lin.line_x = lin.line_x; + cmd_lin.line_y = lin.line_y; + cmd_lin.line_c = lin.line_c; + Cmd_LinGrad_write(cmd_alloc, cmd_ref, cmd_lin); + cmd_ref.offset += 4 + CmdLinGrad_size; + break; case Annotated_Image: tile = Tile_read(read_tile_alloc(element_ref_ix, mem_ok), TileRef(sh_tile_base[element_ref_ix] + (sh_tile_stride[element_ref_ix] * tile_y + tile_x) * Tile_size)); diff --git a/piet-gpu/shader/coarse.spv b/piet-gpu/shader/coarse.spv index 5570675..4352ba1 100644 Binary files a/piet-gpu/shader/coarse.spv and b/piet-gpu/shader/coarse.spv differ diff --git a/piet-gpu/shader/elements.comp b/piet-gpu/shader/elements.comp index b3773c4..43797aa 100644 --- a/piet-gpu/shader/elements.comp +++ b/piet-gpu/shader/elements.comp @@ -363,6 +363,31 @@ void main() { AnnotatedRef out_ref = AnnotatedRef(conf.anno_alloc.offset + (st.path_count - 1) * Annotated_size); Annotated_Color_write(conf.anno_alloc, out_ref, fill_mode, anno_fill); break; + case Element_FillLinGradient: + FillLinGradient lin = Element_FillLinGradient_read(this_ref); + AnnoLinGradient anno_lin; + anno_lin.index = lin.index; + vec2 p0 = st.mat.xy * lin.p0.x + st.mat.zw * lin.p0.y + st.translate; + vec2 p1 = st.mat.xy * lin.p1.x + st.mat.zw * lin.p1.y + st.translate; + vec2 dxy = p1 - p0; + float scale = inversesqrt(dxy.x * dxy.x + dxy.y * dxy.y); + float line_x = dxy.x * scale; + float line_y = dxy.y * scale; + anno_lin.line_x = line_x; + anno_lin.line_y = line_y; + anno_lin.line_c = -(p0.x * line_x + p0.y * line_y); + // TODO: consider consolidating bbox calculation + if (is_stroke) { + vec2 lw = get_linewidth(st); + anno_lin.bbox = st.bbox + vec4(-lw, lw); + anno_lin.linewidth = st.linewidth * sqrt(abs(st.mat.x * st.mat.w - st.mat.y * st.mat.z)); + } else { + anno_lin.bbox = st.bbox; + anno_lin.linewidth = 0.0; + } + out_ref = AnnotatedRef(conf.anno_alloc.offset + (st.path_count - 1) * Annotated_size); + Annotated_LinGradient_write(conf.anno_alloc, out_ref, fill_mode, anno_lin); + break; case Element_FillImage: FillImage fill_img = Element_FillImage_read(this_ref); AnnoImage anno_img; diff --git a/piet-gpu/shader/elements.spv b/piet-gpu/shader/elements.spv index 8e51bdd..d472b72 100644 Binary files a/piet-gpu/shader/elements.spv and b/piet-gpu/shader/elements.spv differ diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index c613b72..cdb7930 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -28,11 +28,9 @@ layout(set = 0, binding = 1) restrict readonly buffer ConfigBuf { layout(rgba8, set = 0, binding = 2) uniform restrict writeonly image2D image; -#ifdef ENABLE_IMAGE_INDICES -layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D images[]; -#else -layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D images[1]; -#endif +layout(rgba8, set = 0, binding = 3) uniform restrict readonly image2D image_atlas; + +layout(rgba8, set = 0, binding = 4) uniform restrict readonly image2D gradients; #include "ptcl.h" #include "tile.h" @@ -74,11 +72,7 @@ mediump vec4[CHUNK] fillImage(uvec2 xy, CmdImage cmd_img) { for (uint i = 0; i < CHUNK; i++) { ivec2 uv = ivec2(xy + chunk_offset(i)) + cmd_img.offset; mediump vec4 fg_rgba; -#ifdef ENABLE_IMAGE_INDICES - fg_rgba = imageLoad(images[cmd_img.index], uv); -#else - fg_rgba = imageLoad(images[0], uv); -#endif + fg_rgba = imageLoad(image_atlas, uv); fg_rgba.rgb = fromsRGB(fg_rgba.rgb); rgba[i] = fg_rgba; } @@ -99,18 +93,6 @@ void main() { mediump vec4 rgba[CHUNK]; for (uint i = 0; i < CHUNK; i++) { rgba[i] = vec4(0.0); - // TODO: remove this debug image support when the actual image method is plumbed. -#ifdef DEBUG_IMAGES -#ifdef ENABLE_IMAGE_INDICES - if (xy_uint.x < 1024 && xy_uint.y < 1024) { - rgba[i] = imageLoad(images[gl_WorkGroupID.x / 64], ivec2(xy_uint + chunk_offset(i))/4); - } -#else - if (xy_uint.x < 1024 && xy_uint.y < 1024) { - rgb[i] = imageLoad(images[0], ivec2(xy_uint + chunk_offset(i))/4).rgb; - } -#endif -#endif } mediump float area[CHUNK]; @@ -198,6 +180,19 @@ void main() { } cmd_ref.offset += 4 + CmdColor_size; break; + case Cmd_LinGrad: + CmdLinGrad lin = Cmd_LinGrad_read(cmd_alloc, cmd_ref); + float d = lin.line_x * float(xy.x) + lin.line_y * float(xy.y) + lin.line_c; + for (uint k = 0; k < CHUNK; k++) { + vec2 chunk_xy = vec2(chunk_offset(k)); + float my_d = d + lin.line_x * chunk_xy.x + lin.line_y * chunk_xy.y; + int x = int(round(clamp(my_d, 0.0, 1.0) * float(GRADIENT_WIDTH - 1))); + mediump vec4 fg_rgba = imageLoad(gradients, ivec2(x, int(lin.index))); + fg_rgba.rgb = fromsRGB(fg_rgba.rgb); + rgba[k] = fg_rgba; + } + cmd_ref.offset += 4 + CmdLinGrad_size; + break; case Cmd_Image: CmdImage fill_img = Cmd_Image_read(cmd_alloc, cmd_ref); mediump vec4 img[CHUNK] = fillImage(xy_uint, fill_img); diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 3e88494..e7c1ce5 100644 Binary files a/piet-gpu/shader/kernel4.spv and b/piet-gpu/shader/kernel4.spv differ diff --git a/piet-gpu/shader/ptcl.h b/piet-gpu/shader/ptcl.h index 6267fc5..936c431 100644 --- a/piet-gpu/shader/ptcl.h +++ b/piet-gpu/shader/ptcl.h @@ -14,6 +14,10 @@ struct CmdColorRef { uint offset; }; +struct CmdLinGradRef { + uint offset; +}; + struct CmdImageRef { uint offset; }; @@ -62,6 +66,19 @@ CmdColorRef CmdColor_index(CmdColorRef ref, uint index) { return CmdColorRef(ref.offset + index * CmdColor_size); } +struct CmdLinGrad { + uint index; + float line_x; + float line_y; + float line_c; +}; + +#define CmdLinGrad_size 16 + +CmdLinGradRef CmdLinGrad_index(CmdLinGradRef ref, uint index) { + return CmdLinGradRef(ref.offset + index * CmdLinGrad_size); +} + struct CmdImage { uint index; ivec2 offset; @@ -99,11 +116,12 @@ CmdJumpRef CmdJump_index(CmdJumpRef ref, uint index) { #define Cmd_Solid 3 #define Cmd_Alpha 4 #define Cmd_Color 5 -#define Cmd_Image 6 -#define Cmd_BeginClip 7 -#define Cmd_EndClip 8 -#define Cmd_Jump 9 -#define Cmd_size 12 +#define Cmd_LinGrad 6 +#define Cmd_Image 7 +#define Cmd_BeginClip 8 +#define Cmd_EndClip 9 +#define Cmd_Jump 10 +#define Cmd_size 20 CmdRef Cmd_index(CmdRef ref, uint index) { return CmdRef(ref.offset + index * Cmd_size); @@ -159,6 +177,28 @@ void CmdColor_write(Alloc a, CmdColorRef ref, CmdColor s) { write_mem(a, ix + 0, s.rgba_color); } +CmdLinGrad CmdLinGrad_read(Alloc a, CmdLinGradRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = read_mem(a, ix + 0); + uint raw1 = read_mem(a, ix + 1); + uint raw2 = read_mem(a, ix + 2); + uint raw3 = read_mem(a, ix + 3); + CmdLinGrad s; + s.index = raw0; + s.line_x = uintBitsToFloat(raw1); + s.line_y = uintBitsToFloat(raw2); + s.line_c = uintBitsToFloat(raw3); + return s; +} + +void CmdLinGrad_write(Alloc a, CmdLinGradRef ref, CmdLinGrad s) { + uint ix = ref.offset >> 2; + write_mem(a, ix + 0, s.index); + write_mem(a, ix + 1, floatBitsToUint(s.line_x)); + write_mem(a, ix + 2, floatBitsToUint(s.line_y)); + write_mem(a, ix + 3, floatBitsToUint(s.line_c)); +} + CmdImage CmdImage_read(Alloc a, CmdImageRef ref) { uint ix = ref.offset >> 2; uint raw0 = read_mem(a, ix + 0); @@ -222,6 +262,10 @@ CmdColor Cmd_Color_read(Alloc a, CmdRef ref) { return CmdColor_read(a, CmdColorRef(ref.offset + 4)); } +CmdLinGrad Cmd_LinGrad_read(Alloc a, CmdRef ref) { + return CmdLinGrad_read(a, CmdLinGradRef(ref.offset + 4)); +} + CmdImage Cmd_Image_read(Alloc a, CmdRef ref) { return CmdImage_read(a, CmdImageRef(ref.offset + 4)); } @@ -258,6 +302,11 @@ void Cmd_Color_write(Alloc a, CmdRef ref, CmdColor s) { CmdColor_write(a, CmdColorRef(ref.offset + 4), s); } +void Cmd_LinGrad_write(Alloc a, CmdRef ref, CmdLinGrad s) { + write_mem(a, ref.offset >> 2, Cmd_LinGrad); + CmdLinGrad_write(a, CmdLinGradRef(ref.offset + 4), s); +} + void Cmd_Image_write(Alloc a, CmdRef ref, CmdImage s) { write_mem(a, ref.offset >> 2, Cmd_Image); CmdImage_write(a, CmdImageRef(ref.offset + 4), s); diff --git a/piet-gpu/shader/scene.h b/piet-gpu/shader/scene.h index 38c2549..254d4aa 100644 --- a/piet-gpu/shader/scene.h +++ b/piet-gpu/shader/scene.h @@ -18,6 +18,10 @@ struct FillColorRef { uint offset; }; +struct FillLinGradientRef { + uint offset; +}; + struct FillImageRef { uint offset; }; @@ -88,6 +92,18 @@ FillColorRef FillColor_index(FillColorRef ref, uint index) { return FillColorRef(ref.offset + index * FillColor_size); } +struct FillLinGradient { + uint index; + vec2 p0; + vec2 p1; +}; + +#define FillLinGradient_size 20 + +FillLinGradientRef FillLinGradient_index(FillLinGradientRef ref, uint index) { + return FillLinGradientRef(ref.offset + index * FillLinGradient_size); +} + struct FillImage { uint index; ivec2 offset; @@ -145,12 +161,13 @@ SetFillModeRef SetFillMode_index(SetFillModeRef ref, uint index) { #define Element_Quad 2 #define Element_Cubic 3 #define Element_FillColor 4 -#define Element_SetLineWidth 5 -#define Element_Transform 6 -#define Element_BeginClip 7 -#define Element_EndClip 8 -#define Element_FillImage 9 -#define Element_SetFillMode 10 +#define Element_FillLinGradient 5 +#define Element_FillImage 6 +#define Element_SetLineWidth 7 +#define Element_Transform 8 +#define Element_BeginClip 9 +#define Element_EndClip 10 +#define Element_SetFillMode 11 #define Element_size 36 ElementRef Element_index(ElementRef ref, uint index) { @@ -215,6 +232,20 @@ FillColor FillColor_read(FillColorRef ref) { return s; } +FillLinGradient FillLinGradient_read(FillLinGradientRef ref) { + uint ix = ref.offset >> 2; + uint raw0 = scene[ix + 0]; + uint raw1 = scene[ix + 1]; + uint raw2 = scene[ix + 2]; + uint raw3 = scene[ix + 3]; + uint raw4 = scene[ix + 4]; + FillLinGradient s; + s.index = raw0; + s.p0 = vec2(uintBitsToFloat(raw1), uintBitsToFloat(raw2)); + s.p1 = vec2(uintBitsToFloat(raw3), uintBitsToFloat(raw4)); + return s; +} + FillImage FillImage_read(FillImageRef ref) { uint ix = ref.offset >> 2; uint raw0 = scene[ix + 0]; @@ -287,6 +318,14 @@ FillColor Element_FillColor_read(ElementRef ref) { return FillColor_read(FillColorRef(ref.offset + 4)); } +FillLinGradient Element_FillLinGradient_read(ElementRef ref) { + return FillLinGradient_read(FillLinGradientRef(ref.offset + 4)); +} + +FillImage Element_FillImage_read(ElementRef ref) { + return FillImage_read(FillImageRef(ref.offset + 4)); +} + SetLineWidth Element_SetLineWidth_read(ElementRef ref) { return SetLineWidth_read(SetLineWidthRef(ref.offset + 4)); } @@ -303,10 +342,6 @@ Clip Element_EndClip_read(ElementRef ref) { return Clip_read(ClipRef(ref.offset + 4)); } -FillImage Element_FillImage_read(ElementRef ref) { - return FillImage_read(FillImageRef(ref.offset + 4)); -} - SetFillMode Element_SetFillMode_read(ElementRef ref) { return SetFillMode_read(SetFillModeRef(ref.offset + 4)); } diff --git a/piet-gpu/shader/setup.h b/piet-gpu/shader/setup.h index 3ed1690..cfe5f28 100644 --- a/piet-gpu/shader/setup.h +++ b/piet-gpu/shader/setup.h @@ -28,6 +28,8 @@ #define LG_N_TILE (7 + LG_WG_FACTOR) #define N_SLICE (N_TILE / 32) +#define GRADIENT_WIDTH 512 + struct Config { uint n_elements; // paths uint n_pathseg; diff --git a/piet-gpu/shader/tile_alloc.spv b/piet-gpu/shader/tile_alloc.spv index 4a39f96..12165b5 100644 Binary files a/piet-gpu/shader/tile_alloc.spv and b/piet-gpu/shader/tile_alloc.spv differ diff --git a/piet-gpu/src/gradient.rs b/piet-gpu/src/gradient.rs new file mode 100644 index 0000000..a5c52b8 --- /dev/null +++ b/piet-gpu/src/gradient.rs @@ -0,0 +1,145 @@ +// Copyright 2021 The piet-gpu authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Also licensed under MIT license, at your choice. + +//! Implementation of gradients. + +use std::collections::hash_map::{Entry, HashMap}; + +use piet::{Color, FixedLinearGradient, GradientStop}; + +#[derive(Clone)] +pub struct BakedGradient { + ramp: Vec, +} + +#[derive(Clone)] +pub struct LinearGradient { + start: [f32; 2], + end: [f32; 2], + ramp_id: u32, +} + +#[derive(Default)] +pub struct RampCache { + ramps: Vec, + map: HashMap, +} + +#[derive(Clone, Hash, PartialEq, Eq)] +struct GradientRamp(Vec); + +pub const N_SAMPLES: usize = 512; +// TODO: make this dynamic +pub const N_GRADIENTS: usize = 256; + +#[derive(Clone, Copy)] +struct PremulRgba([f64; 4]); + +impl PremulRgba { + fn from_color(c: &Color) -> PremulRgba { + let rgba = c.as_rgba(); + let a = rgba.3; + // TODO: sRGB nonlinearity? This is complicated. + PremulRgba([rgba.0 * a, rgba.1 * a, rgba.2 * a, a]) + } + + fn to_u32(&self) -> u32 { + let z = self.0; + Color::rgba(z[0], z[1], z[2], z[3]).as_rgba_u32() + } + + fn lerp(&self, other: PremulRgba, t: f64) -> PremulRgba { + fn l(a: f64, b: f64, t: f64) -> f64 { + a * (1.0 - t) + b * t + } + let a = self.0; + let b = other.0; + PremulRgba([l(a[0], b[0], t), l(a[1], b[1], t), l(a[2], b[2], t), l(a[2], b[3], t)]) + } +} + +impl GradientRamp { + fn from_stops(stops: &[GradientStop]) -> GradientRamp { + let mut last_u = 0.0; + let mut last_c = PremulRgba::from_color(&stops[0].color); + let mut this_u = last_u; + let mut this_c = last_c; + let mut j = 0; + let v = (0..N_SAMPLES).map(|i| { + let u = (i as f64) / 255.0; + while u > this_u { + last_u = this_u; + last_c = this_c; + if let Some(s) = stops.get(j + 1) { + this_u = s.pos as f64; + this_c = PremulRgba::from_color(&s.color); + j += 1; + } else { + break; + } + } + let du = this_u - last_u; + let c = if du < 1e-9 { + this_c + } else { + last_c.lerp(this_c, (u - last_u) / du) + }; + c.to_u32() + }).collect(); + GradientRamp(v) + } +} + +impl RampCache { + /// Add a gradient ramp to the cache. + /// + /// Currently there is no eviction, so if the gradient is animating, there may + /// be resource leaks. In order to support lifetime management, the signature + /// should probably change so it returns a ref-counted handle, so that eviction + /// is deferred until the last handle is dropped. + /// + /// This function is pretty expensive, but the result is lightweight. + fn add_ramp(&mut self, ramp: &[GradientStop]) -> usize { + let ramp = GradientRamp::from_stops(ramp); + match self.map.entry(ramp) { + Entry::Occupied(o) => *o.get(), + Entry::Vacant(v) => { + let idx = self.ramps.len(); + self.ramps.push(v.key().clone()); + v.insert(idx); + idx + } + } + } + + pub fn add_linear_gradient(&mut self, lin: &FixedLinearGradient) -> LinearGradient { + let ramp_id = self.add_ramp(&lin.stops); + LinearGradient { + ramp_id: ramp_id as u32, + start: crate::render_ctx::to_f32_2(lin.start), + end: crate::render_ctx::to_f32_2(lin.end), + } + } +} + + +#[cfg(test)] +mod test { + #[test] + fn it_works() { + println!("it works!"); + } +} diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index d1c1dd6..9cba9e6 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -1,3 +1,4 @@ +mod gradient; mod pico_svg; mod render_ctx; mod text; @@ -248,6 +249,8 @@ pub struct Renderer { // Keep a reference to the image so that it is not destroyed. _bg_image: Image, + + gradients: Image, } impl Renderer { @@ -351,22 +354,14 @@ impl Renderer { let bg_image = Self::make_test_bg_image(&session); - let k4_code = if session.gpu_info().has_descriptor_indexing { - ShaderCode::Spv(include_bytes!("../shader/kernel4_idx.spv")) - } else { - println!("doing non-indexed k4"); - ShaderCode::Spv(include_bytes!("../shader/kernel4.spv")) - }; - // This is an arbitrary limit on the number of textures that can be referenced by - // the fine rasterizer. To set it for real, we probably want to pay attention both - // to the device limit (maxDescriptorSetSampledImages) but also to the number of - // images encoded (I believe there's an cost when allocating descriptor pools). If - // it can't be satisfied, then for compatibility we'll probably want to fall back - // to an atlasing approach. - // - // However, we're adding only one texture for now. Avoid a harmless Vulkan validation - // error by using a tight bound. - let max_textures = 1; + let gradients = Self::make_gradient_image(&session); + + let k4_code = ShaderCode::Spv(include_bytes!("../shader/kernel4.spv")); + // This is a bit of a stand-in for future development. For now, we assume one + // atlas image for all images, and another image for the gradients. In the future, + // on GPUs that support it, we will probably want to go to descriptor indexing in + // order to cut down on allocation and copying for the atlas image. + let max_textures = 2; let k4_pipeline = session .pipeline_builder() .add_buffers(2) @@ -377,7 +372,7 @@ impl Renderer { .descriptor_set_builder() .add_buffers(&[&memory_buf_dev, &config_buf]) .add_images(&[&image_dev]) - .add_textures(&[&bg_image]) + .add_textures(&[&bg_image, &gradients]) .build(&session, &k4_pipeline)?; Ok(Renderer { @@ -405,6 +400,7 @@ impl Renderer { n_paths, n_pathseg, _bg_image: bg_image, + gradients: gradients, }) } @@ -417,6 +413,12 @@ impl Renderer { ImageLayout::Undefined, ImageLayout::General, ); + // TODO: make gradient upload optional, only if it's changed + cmd_buf.image_barrier( + &self.gradients, + ImageLayout::Undefined, + ImageLayout::General, + ); cmd_buf.reset_query_pool(&query_pool); cmd_buf.write_timestamp(&query_pool, 0); cmd_buf.dispatch( @@ -492,9 +494,7 @@ impl Renderer { if format != ImageFormat::RgbaPremul { return Err("unsupported image format".into()); } - let host_upload = BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC; - let mut buffer = session.create_buffer(buf.len() as u64, host_upload)?; - buffer.write(buf)?; + let buffer = session.create_buffer_init(&buf, BufferUsage::COPY_SRC)?; let image = session.create_image2d(width.try_into()?, height.try_into()?)?; let mut cmd_buf = session.cmd_buf()?; cmd_buf.begin(); @@ -528,4 +528,10 @@ impl Renderer { } Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap() } + + fn make_gradient_image(session: &Session) -> Image { + unsafe { + session.create_image2d(gradient::N_SAMPLES as u32, gradient::N_GRADIENTS as u32).unwrap() + } + } } diff --git a/piet-gpu/src/render_ctx.rs b/piet-gpu/src/render_ctx.rs index fe1c4ff..bc5660e 100644 --- a/piet-gpu/src/render_ctx.rs +++ b/piet-gpu/src/render_ctx.rs @@ -14,6 +14,7 @@ use piet_gpu_types::scene::{ Clip, CubicSeg, Element, FillColor, LineSeg, QuadSeg, SetFillMode, SetLineWidth, Transform, }; +use crate::gradient::{LinearGradient, RampCache}; use crate::text::Font; pub use crate::text::{PathEncoder, PietGpuText, PietGpuTextLayout, PietGpuTextLayoutBuilder}; @@ -38,12 +39,14 @@ pub struct PietGpuRenderContext { cur_transform: Affine, state_stack: Vec, clip_stack: Vec, + + ramp_cache: RampCache, } #[derive(Clone)] pub enum PietGpuBrush { Solid(u32), - Gradient, + LinGradient(LinearGradient), } #[derive(Default)] @@ -92,6 +95,7 @@ impl PietGpuRenderContext { cur_transform: Affine::default(), state_stack: Vec::new(), clip_stack: Vec::new(), + ramp_cache: RampCache::default(), } } @@ -148,8 +152,14 @@ impl RenderContext for PietGpuRenderContext { PietGpuBrush::Solid(premul.as_rgba_u32()) } - fn gradient(&mut self, _gradient: impl Into) -> Result { - Ok(Self::Brush::Gradient) + fn gradient(&mut self, gradient: impl Into) -> Result { + match gradient.into() { + FixedGradient::Linear(lin) => { + let lin = self.ramp_cache.add_linear_gradient(&lin); + Ok(PietGpuBrush::LinGradient(lin)) + } + _ => todo!("don't do radial gradients yet"), + } } fn clear(&mut self, _color: Color) {}