From 047a0830d1d6d815dd0f48b92d9fc2d80633d131 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Wed, 18 Nov 2020 15:54:11 -0800 Subject: [PATCH] Towards wiring up images to k4 This patch passes a dynamically sized array of textures to the fine rasterizer. A bunch of the low level Vulkan stuff is done, but only enough of the shaders and encoders to do minimal testing. We'll want to switch from storage images to sampled images, track the actual array of textures during encoding, use that to build the descriptor set (which will need to be more dynamic), and of course run image elements through the pipeline. Progress towards #38 --- piet-gpu-hal/src/hub.rs | 12 +++++ piet-gpu-hal/src/lib.rs | 7 +++ piet-gpu-hal/src/vulkan.rs | 102 +++++++++++++++++++++++++++++------ piet-gpu/shader/kernel4.comp | 6 +++ piet-gpu/shader/kernel4.spv | Bin 34772 -> 35568 bytes piet-gpu/src/lib.rs | 32 +++++++++++ 6 files changed, 144 insertions(+), 15 deletions(-) diff --git a/piet-gpu-hal/src/hub.rs b/piet-gpu-hal/src/hub.rs index a16d86f..5855578 100644 --- a/piet-gpu-hal/src/hub.rs +++ b/piet-gpu-hal/src/hub.rs @@ -324,6 +324,12 @@ impl PipelineBuilder { self } + /// Add a binding with a variable-size array of textures. + pub fn add_textures(mut self, max_textures: u32) -> Self { + self.0.add_textures(max_textures); + self + } + pub unsafe fn create_compute_pipeline( self, session: &Session, @@ -349,6 +355,12 @@ impl DescriptorSetBuilder { self } + pub fn add_textures<'a>(mut self, images: impl IntoRefs<'a, Image>) -> Self { + let vk_images = images.into_refs().map(|i| i.vk_image()).collect::>(); + self.0.add_textures(&vk_images); + self + } + pub unsafe fn build( self, session: &Session, diff --git a/piet-gpu-hal/src/lib.rs b/piet-gpu-hal/src/lib.rs index 6ac7766..49c68eb 100644 --- a/piet-gpu-hal/src/lib.rs +++ b/piet-gpu-hal/src/lib.rs @@ -212,11 +212,18 @@ pub trait PipelineBuilder { fn add_buffers(&mut self, n_buffers: u32); /// Add storage images to the pipeline. Each has its own binding. fn add_images(&mut self, n_images: u32); + /// Add a binding with a variable-size array of textures. + fn add_textures(&mut self, max_textures: u32); unsafe fn create_compute_pipeline(self, device: &D, code: &[u8]) -> Result; } +/// A builder for descriptor sets with more complex layouts. +/// +/// Note: the order needs to match the pipeline building, and it also needs to +/// be buffers, then images, then textures. pub trait DescriptorSetBuilder { fn add_buffers(&mut self, buffers: &[&D::Buffer]); fn add_images(&mut self, images: &[&D::Image]); + fn add_textures(&mut self, images: &[&D::Image]); unsafe fn build(self, device: &D, pipeline: &D::Pipeline) -> Result; } diff --git a/piet-gpu-hal/src/vulkan.rs b/piet-gpu-hal/src/vulkan.rs index a269b6b..5400af7 100644 --- a/piet-gpu-hal/src/vulkan.rs +++ b/piet-gpu-hal/src/vulkan.rs @@ -71,6 +71,7 @@ pub struct Pipeline { pipeline: vk::Pipeline, descriptor_set_layout: vk::DescriptorSetLayout, pipeline_layout: vk::PipelineLayout, + max_textures: u32, } pub struct DescriptorSet { @@ -92,11 +93,14 @@ pub struct MemFlags(vk::MemoryPropertyFlags); pub struct PipelineBuilder { bindings: Vec, + binding_flags: Vec, + max_textures: u32, } pub struct DescriptorSetBuilder { buffers: Vec, images: Vec, + textures: Vec, } unsafe extern "system" fn vulkan_debug_callback( @@ -141,6 +145,8 @@ static EXTS: Lazy> = Lazy::new(|| { if cfg!(debug_assertions) { exts.push(DebugUtils::name()); } + // We'll need this to do runtime query of descriptor indexing. + //exts.push(vk::KhrGetPhysicalDeviceProperties2Fn::name()); exts }); @@ -270,13 +276,22 @@ impl VkInstance { .queue_family_index(qfi) .queue_priorities(&queue_priorities) .build()]; + + // support for descriptor indexing (maybe should be optional for compatibility) + let descriptor_indexing = vk::PhysicalDeviceDescriptorIndexingFeatures::builder() + .descriptor_binding_variable_descriptor_count(true) + .runtime_descriptor_array(true); + let extensions = match surface { Some(_) => vec![khr::Swapchain::name().as_ptr()], None => vec![], }; + //extensions.push(vk::KhrMaintenance3Fn::name().as_ptr()); + //extensions.push(vk::ExtDescriptorIndexingFn::name().as_ptr()); let create_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queue_create_infos) .enabled_extension_names(&extensions) + .push_next(&mut descriptor_indexing.build()) .build(); let device = self.instance.create_device(pdevice, &create_info, None)?; @@ -541,6 +556,8 @@ impl crate::Device for VkDevice { unsafe fn pipeline_builder(&self) -> PipelineBuilder { PipelineBuilder { bindings: Vec::new(), + binding_flags: Vec::new(), + max_textures: 0, } } @@ -548,6 +565,7 @@ impl crate::Device for VkDevice { DescriptorSetBuilder { buffers: Vec::new(), images: Vec::new(), + textures: Vec::new(), } } @@ -922,6 +940,8 @@ impl crate::PipelineBuilder for PipelineBuilder { .stage_flags(vk::ShaderStageFlags::COMPUTE) .build(), ); + self.binding_flags + .push(vk::DescriptorBindingFlags::default()); } } @@ -936,9 +956,27 @@ impl crate::PipelineBuilder for PipelineBuilder { .stage_flags(vk::ShaderStageFlags::COMPUTE) .build(), ); + self.binding_flags + .push(vk::DescriptorBindingFlags::default()); } } + fn add_textures(&mut self, max_textures: u32) { + let start = self.bindings.len() as u32; + self.bindings.push( + vk::DescriptorSetLayoutBinding::builder() + .binding(start) + // TODO: we do want these to be sampled images + .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) + .descriptor_count(max_textures) + .stage_flags(vk::ShaderStageFlags::COMPUTE) + .build(), + ); + self.binding_flags + .push(vk::DescriptorBindingFlags::VARIABLE_DESCRIPTOR_COUNT); + self.max_textures += max_textures; + } + unsafe fn create_compute_pipeline( self, device: &VkDevice, @@ -946,7 +984,14 @@ impl crate::PipelineBuilder for PipelineBuilder { ) -> Result { let device = &device.device.device; let descriptor_set_layout = device.create_descriptor_set_layout( - &vk::DescriptorSetLayoutCreateInfo::builder().bindings(&self.bindings), + &vk::DescriptorSetLayoutCreateInfo::builder() + .bindings(&self.bindings) + // It might be a slight optimization not to push this if max_textures = 0 + .push_next( + &mut vk::DescriptorSetLayoutBindingFlagsCreateInfo::builder() + .binding_flags(&self.binding_flags) + .build(), + ), None, )?; let descriptor_set_layouts = [descriptor_set_layout]; @@ -981,6 +1026,7 @@ impl crate::PipelineBuilder for PipelineBuilder { pipeline, pipeline_layout, descriptor_set_layout, + max_textures: self.max_textures, }) } } @@ -994,6 +1040,10 @@ impl crate::DescriptorSetBuilder for DescriptorSetBuilder { self.images.extend(images.iter().map(|i| i.image_view)); } + fn add_textures(&mut self, images: &[&Image]) { + self.textures.extend(images.iter().map(|i| i.image_view)); + } + unsafe fn build(self, device: &VkDevice, pipeline: &Pipeline) -> Result { let device = &device.device.device; let mut descriptor_pool_sizes = Vec::new(); @@ -1005,11 +1055,12 @@ impl crate::DescriptorSetBuilder for DescriptorSetBuilder { .build(), ); } - if !self.images.is_empty() { + let n_images_total = self.images.len() + pipeline.max_textures as usize; + if n_images_total > 0 { descriptor_pool_sizes.push( vk::DescriptorPoolSize::builder() .ty(vk::DescriptorType::STORAGE_IMAGE) - .descriptor_count(self.images.len() as u32) + .descriptor_count(n_images_total as u32) .build(), ); } @@ -1030,39 +1081,60 @@ impl crate::DescriptorSetBuilder for DescriptorSetBuilder { let mut binding = 0; // Maybe one call to update_descriptor_sets with an array of descriptor_writes? for buf in &self.buffers { - let buf_info = vk::DescriptorBufferInfo::builder() - .buffer(*buf) - .offset(0) - .range(vk::WHOLE_SIZE) - .build(); device.update_descriptor_sets( &[vk::WriteDescriptorSet::builder() .dst_set(descriptor_sets[0]) .dst_binding(binding) .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .buffer_info(&[buf_info]) + .buffer_info(&[vk::DescriptorBufferInfo::builder() + .buffer(*buf) + .offset(0) + .range(vk::WHOLE_SIZE) + .build()]) .build()], &[], ); binding += 1; } for image in &self.images { - let image_info = vk::DescriptorImageInfo::builder() - .sampler(vk::Sampler::null()) - .image_view(*image) - .image_layout(vk::ImageLayout::GENERAL) - .build(); device.update_descriptor_sets( &[vk::WriteDescriptorSet::builder() .dst_set(descriptor_sets[0]) .dst_binding(binding) .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) - .image_info(&[image_info]) + .image_info(&[vk::DescriptorImageInfo::builder() + .sampler(vk::Sampler::null()) + .image_view(*image) + .image_layout(vk::ImageLayout::GENERAL) + .build()]) .build()], &[], ); binding += 1; } + if !self.textures.is_empty() { + let infos = self + .textures + .iter() + .map(|texture| { + vk::DescriptorImageInfo::builder() + .sampler(vk::Sampler::null()) + .image_view(*texture) + .image_layout(vk::ImageLayout::GENERAL) + .build() + }) + .collect::>(); + device.update_descriptor_sets( + &[vk::WriteDescriptorSet::builder() + .dst_set(descriptor_sets[0]) + .dst_binding(binding) + .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) + .image_info(&infos) + .build()], + &[], + ); + //binding += 1; + } Ok(DescriptorSet { descriptor_set: descriptor_sets[0], }) diff --git a/piet-gpu/shader/kernel4.comp b/piet-gpu/shader/kernel4.comp index 6c98c4b..2d7f579 100644 --- a/piet-gpu/shader/kernel4.comp +++ b/piet-gpu/shader/kernel4.comp @@ -6,6 +6,7 @@ #version 450 #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_nonuniform_qualifier : enable #include "setup.h" @@ -28,6 +29,8 @@ layout(set = 0, binding = 2) buffer ClipScratchBuf { layout(rgba8, set = 0, binding = 3) uniform writeonly image2D image; +layout(rgba8, set = 0, binding = 4) uniform readonly image2D textures[]; + #include "ptcl.h" #include "tile.h" @@ -103,6 +106,9 @@ void main() { uint clip_tos = 0; for (uint i = 0; i < CHUNK; i++) { rgb[i] = vec3(0.5); + if (xy_uint.x < 256 && xy_uint.y < 256) { + rgb[i] = imageLoad(textures[gl_WorkGroupID.x / 16], ivec2(xy_uint.x, xy_uint.y + CHUNK_DY * i)).rgb; + } mask[i] = 1.0; } diff --git a/piet-gpu/shader/kernel4.spv b/piet-gpu/shader/kernel4.spv index 538e783526e6b89294d561a56016d15196b149da..de38ef057706c8b72cae514ee2bf505bc80cc8cc 100644 GIT binary patch literal 35568 zcmai-2b^71xy3Kc3;_~4NQZwl+XzT=`c)YCSk}V6DAW9dMKec5m7*4M2dnS z2!e`$2E|7a6crQ!QBe`Gp@IU+`~UAfYi8%F@139f?X}id_P6)hr`?3SmW9_IQL8Of zTfDYt?e}f9`ms!HVU$|U{(J zWNneU|M&wY9=+=QGz*=p=P%mS8-}G72dnWhwPisGUR!48o*>gn(99OyZvdnixk*E7AdcUD*F&_<@R&Ngaek!pKP>)NBI zzq7ad(C*1l)V8`V?wdS$X7@l1rH%C{d@DaSkIwGt1C&~iRda3nJGy#i%~V^Y9_xJj zr%dW-@9gXC!?&$AinT0qw)b~;bZszmpnq29z$Rr?o6Oo`i=FUqsV!j+%CvUZ6R(K_ z{e4r*+SNVE+Lwh7`GT!U&10a4#WX2Lbn8E{qjz%q?4GWH6NmNxG=~-N9m?H2hoS0* z=fKvh=FnDKu@Sp>Pj7F1^<8^4kKy`TYpcK+L%({Ba*yWP)vbL}N9WY8{=OOdI4AXg zuYF`~b@VE4bLCY{!*#EQZ`OXz;kW}kW=cr}K;^?KAin(J5p_xc+- ztbdvbINI0=>(Cc@|5N={ZNqa|o0!aDy+-`F?kPRf_vr1J;Urvtb&ugWEK=J5&R9df z+Fe(5kLG!(H_xMu-98q*%+p--&;S(EsM@)<)HWinQTL*?O^O^7`+9r2Qarl#Yxfwg ze?)CFc*Y%yLDOHGM@R3B6FcbJxU#mS|2O~tSnN=?p^B|{3*sB~E>hdNk!!E%MWK$X z?lD|vTWwo7<5s?n`qVsz>sz9>1AN%8xv=4($~D}cm_|))wVfLA_2MbKz7A?0Wj!B; zGfv%4o9A53qxoL88(MsuE6PA+J-eEtx|fYBVsk;cM(Q5JH7!!x6P|JE7ZhAyb&ug| zWXalicssI{Y3#SXiOX8;+sLustZDU2q&}9K$8cS(wf*6YrC;4g?lHZ4Hp5P) z!jZLu;FV8vDOLXAYxO{U8*#0*35~czNmI`)_ZS|3vD#s9a<>n3Oer2Bgj9>}zMP0p zYB{pL>WUaS~udb^98fct1=d7;}AJ0eQ5%^!j*cUCez9McQ zao*ehdssb=*NbYrQMEZ`-2bz{jyZcMc4Y00D%MT-e+*e4^Yo#(w%S?X!+3v~*gb^} z)7YD89>aUHwKf-C`PKKY++*fIN5Av+EpaZr&y4QrT|+)T^G%sjUvp1)!^H0%F80hRBH>+`PmV3-Vvp4=m0f+Io(i87vr_S$ea35oJ zes6{LlvqFOp50I$!}`mcXHu3tI6;5!tdO|?}8@3%ZB_~Yd3;tbo6&j zV{P3dw>9y*oA?7w{E;U9coTo7i9g@OUuojMUVyjM-hk))`~h5^=Rd){ezX7O9$rKC z?%TI2&AGkPh#%S)?>Bh0FIsC0aU&uBR*l%!+9D0Ub@<2z-zK@2g*W!mqV@0DDZT9n z^mTUh?mPVyx`Cd)>HF@P*d6e#-$xMqF53z2E9u0}{*Hmp6P*I;h`Me}{cgkOw#G(| zmfDAjY2Za_dpP!qjhoVN-j>=y=-!Z=?-Ae*ZbXSW9$vq{%8Dr&{FGxC*R57X{WY(3#gRZ%&n<-3Qri)&r^0)Id3Ub+9t9rYSvIS` zyYg+V^`Q;F!?x5;)2E|x4o?RUpTpMLmGI8`j~9*kUe#p320ob`!Osr$Id&l}+O6RJdVQ_6`@qBB8m+Zw;QT1Tj|`15o`nzB&{BI2-Z(EUwHLu%>^{%QtKk2% zCtpX;p8PF%I9FTkO?drxom09yvvzNx<-ETO?wT>Yt|M9*qgvPX!SUM+T+ZPD_+(F7 zHOB+t!{hRPt^d&V)LJ{F$v&}(AHD!@sT~h*jNMi{1MFDs)k$<8p1FWO?>bHV+$KJ6 z0p3!(7@oP^w1BO(c5@TIrHS9Z0B@iOj6r>k?H4d(YZKu^ZB>>1V9J5hiw1u(4{|{5>#Z`oSRAN1HEu{HEsT$W=`wegYTEXs&v<)*<@#vb zmp*a(fsIqswtr=FyaT{;eY8!eY@Xvoz{aWRduU}-mmf~={t7=D?D-8p9_;xFp9J| zbLi*N^mh&B(HmzT?H5#7UHc{U+RY=swBqi&v2f!qqj?Rk2EUx%@zlm(y@FoNJjR;W zIO9J~^9cVWcwyS=%j z^@;sHu=gR zeIq}r;_e&yNfmdW$WN=d`$T?e#oZ^F&kbm;aMv}@HrLf>nB2HO)4K+0uB+VVSh+4s zK-#KtmH|8OxwNdyT4=7@@&9|v{~%&WiL?@+b)?+v$*@crP6R`G{}ov(XYyXX5- z{W-!ftMj?LhCc*34f;Hzia00bDQ)-BdxFiUzA+45&>$v4% zovy9t@CbbLGyX_=4>jwS`~Kmf{kRJ2Z{0og-Yd)}p9*(8b8kp)_t~jv#(s?6Iq0)7 zJJoyEUEtK8^Y9Yd22-aWsDY3v3_$Bp1c)3j$)WO=F$88@3`{%{lDOzd%yEz_ulGve7Ny` z%a`2m_>%9?#QlDc-M!#9e7JkxZ}xEW`^_G1eSWuxd(ZT{JzTrr?cts$zuUvjcU{5f z!~J%TUGDdL$^C{8_nzxFe7NKJ4IgfPzv083kKgg(J_}zhxc4@{;Y+*U@!_tw-|^w* z_ZvQ3yWjEQ*6(+Gxc+|2himtHKHO)9-}B+l$M5-Y>-T#;-0}RL5BE9i_k6hH`8{89 zzvsh!CizVt?s$IBhns(H!Oeee!OidYeEj8p&xf1OZ~Aca-CS_<`Ar|Y@qW{n+;94l z&u`*>)0cL?=}Yc6eYn@O-}T`>gZ-utw_d;J!|Ug>;MVK+eC*ci_k79yo)7n#?l*n7 z`TdR$_x$@EA3hK6H+;D70Di}Z8}B!Kxc+{_m)vjolKUMW?)5jj;J#D%Eg!r2{gw}( z2lrb(+;u-T-qiuXWt37 zqFIABYxa4e=A5-}LsOdpU!3`ETWR|GERl5|G_hCC$P^l`?tyc zVftNY9@=-U?Dkpz-N5F!huVFf?+#Y;nfxv}sgTdx5&+!DfdU78E zRx`Kjkv#T)dh&>Zu0dif63 zo;eeI7(VLSPor1M9L@r(-GRUR`fRY;ATjRebHEv7Qm5=y3@E~^A@_MjyuyZQ%?q0C5>Yhu-{1Vu6IgX~?`QA#ep1Ir)Hn-P!YX34^J+(go_Ip-6J`aM;<@k>E z6|jElquAAIUj;k<*vjs27v6uY<34QK&F3}oHL&L`{Gp0lr}x6I)4Ug|>*M|NVVb(! zJ^BdEeVF^}qm`!aVJ!?lG{!g39W!h3O>iGg-JH&0K3F~cF|f7ItLFVJus-U^^KGzo z_?uqh9*3(Z?mJ*()hDn5&gXw<>S?Z{`V%y3_xEz=?7Z~N+B^w%ZCpdwR&I>fm)Dxt zh`qJA)=z<*ckc5~gVl1M*QQopzg}x{?{(&zM{hpw*WUw=p*i-m^m6Y>{vP>#@C~ed0&-&(l1#zfjrjGmjsG&5`@WPrz!qPso2rGncu=iTx?q z*qpPM!7tL(txf(C&9SY+c>Bb^0yaKt_%pCt)=-;TIXCNYym`#&=U~?&*R<#57jSjo z|NZU!mtZyPd6iynzSroj$KJL2HT`dB9@>9Z+3l^@TyKEQVXoiO%RNVPvHcEw9=*Eu z*Xh-))%(xy!D_ijc>nnWTs=O2tb8&beg1@|o_o}v!N#dO&YSdV;{Y|-lbPdjSN?>G0&!j;A-WwX<@j!>pCBE3)np6v!)eI-Fm%8wSm=) zP23{j#9516zsz}2u*j(E3Tv!U6 z=Ysa~x!`YB+OuX$gFUa|%Tzq`To&$itFDjhu^d=EbuSN2-P%`xJI)o<==uH-SReJo ztO$1fa&A|G>!+UQ>&jr~F^;Bv6mNCrU!~I51zU@LsdrVd^*S%li(G&AwR_yXX`lJ7 zhRylsKC?PpE%zC1YUOp~9(TO68Gj9I?%TC^@R`$l!J265j{hn)x$)jZ9N*q}?^C0( z>63l$eQFGv`knarj=dIGZ4jI9)$-Sf@xEj}`{Y|2oA>P8ybWd!7rhX1B z*j(zlzP1DB`qExrU;gH+J!5VUcFgb{DsBz?F!qjc-!0Vj@qJ_`u)2IId}pw0pZBg0 z!_{ma*5bL!7`vdC^V}8QN4&Z|&T}`gdid^OYtQ@BIIuqIS+707)=|E9?TMzIxV^x} zs^`6HZ?Jk=-n+)5S-bCD&e?hCo3+^o?Ao}7?m4+J?kV?|`@%l+-WTk=b1m-&R?D@l zO|9Is?l1WjjAOnF=*{PQ*Fo6w-gO{Y?lav#_dOVVEWNt59YC*UtoOV_!9Me}9Rij+ zFMnq~46J|lufL~!1g@_CM0&aY-Xo3x>tEg@j)bf0e>lB-2kwuT<9`(RYI?^tmv;Ro z&}&QHqrv7a?-j?u)pM^n7My#9w%jX@18cJ}O;)bqM^_)qeWH)!9#21r?mD-FU1xjm z6&>`GXdc=-E4zKpYZurYxmR?9)pDjA6F zz5Y%Dm)GA^xSGwQ`TFZccTC5ak9`{0>rdUBUVqcU>Zz*_T*l0Rd%n~Yb23;x*IGZ= zJn9*1Cb;<;9Y9l0+$^xM>bXWw0jsCw8l8=nYt(gd?)ql^=73#4*V%I|H^y`6dG&nQ zXP&2mooBAm)4*!EMzyJxd&ToA&oz1>E-%+ja~rOzr02-gsba6k6xZ@^dhj=sNz7lMXTsK#N)pFg)FO}2GEl%u5 z!N!JPUGeO*kHOv3>iW2+uK}ya=i^}4B=;A6u7#^7?`BKor#{JX1K8S}gX<_a#&vSdoU46m_!QV0vL`o^)JuY1hy5p-p@z{UAQh{R?2{ZttGFoBoS55AF9PksrkmOUxIi)Jo!ixYbv*x2yC9|mWiT8HE4m%NXFt=D+qcX80@_5y?$P#|0&Hw`%9JGK6SqgHb<_XSHNnye&j!)nakYb z#QqFyE#a?%&6Tl3}hxSD)yM5MpF|awZ z?-vKFW#7x&Xy!7vII$za<=z|xH&^USRNVRdO}-@DcUpCQ{3cimte%*q!R9b#K6Nhx zcE0NR`0QC0te)SqmIM1dz^!h}b7J{QAB(O}eor`r+~!#U&G9{=eW=pR7j4B#~k6eSR zfz@&iX;UlDzvoKs^w&Gq-hH_N{f0CT?Hg5g`^fQh_-d5m3wEKN-Ouy zwrIxduif0)JKKS?cl68N*&giPac<7XHCEH#HFP~)2m8!(2e9+b-q{hXmc64*tvpAr zr#ySdKKXXS=6g!=?F?5-K5c5o=e_6>G~?ZSyJ5@T^Y7%z-FyCh{oTR6^y<#dzr&}N zvB!bUe=h!BV|&2We22(;)}Cb$B@a5i}3&M^<+G%)h_84$^ z9UcoeSM0}utu_4k%AUEl!_~_7rW4@m<$F^H++5o7zB37&_Z{sSqZ6zx*HstT>&jZK z&oxui&oyy)hb`H4QSEs_wm2-L;*mJ6%w#1zd)|TI3 z&j5R`-HoQrdCjI*Pt2KMbIbkB@+`1E`7?vF!D==S^KsgS;^(3_YHK`e&Vl=^Q8%a0 znsdSGdCr~(-i1~^XU~VL$7decI?6vYxB#x6-y<&s8>gP%BQFB0r{(v^i_xszKQnMn z)}(Kqk(Yq2+x2jrb@J=dNi`>flgVCS7Zei>LTdr6y`@!3C~Yk8g>molFDe0E%g zEzgcC!E&D+{Y6N*(itV z8h&*3vHVR~AIH6xeh}R`d;;v6*n3S}M}IxdL;DSt-9BsfDX=+mO?(=xmTN-(Nt(IL zEl%u>U}M8?s(8-dAl&n(u8;ffGhlVOd;4av^U2=+EL_dz(Y&{BL3hmT$y>qx?xJo^ z-gOqdk0uOE&KLPwCr2wW6k|1SW@inkHvX>qLt7R|AAE24b+~UN39c*mQ z*~4IK%k|@VeFUx^pGU!-dtb|Rc{XY&?_sD;@@Ey3i^_xrXc(cLQ@3_u)9x+d0(OZgjP-M{`h;5J@3Q{_?D*=*{Y$X9t;g@a z*TCKz)b;V6_$#n_zUh7qR`ZDVn@aP(6Ycd%^ZAh92!4xZy#CrR!`R6me;qtJ=JlM( zWBV%Dwegzr9{)9(TI>&jon!1@2djC_`CIA3G-K2~@0ZX&LUS*8J{(i7Uvho}?EI4R zn_xBL=hHt%Gd}xXu215>1$G|UXWs_vpFJekM}Mz}$7$OC#r_xPT3Hsn0?lJNn%BxQ z^vlz{Rz5_ZYvl=Wd98f6i9grGUnuxX;Fl|&nxBL_j%$>8Jq1?Jwed7qE%UMlwfH{+ zR?D^VELd%jmOb%3aQ1}u#C{*F&GX}2fvc6*#*6Ue)t=a&g0-2~wUx*A3fMVhZGQ%KyX|NR`Smi_k& zu<_Y{a(xp2ORzO$yw||`=RC>v(ceAxE1GuqpE&z(74Ygbk5y^zzm@1$qq+aqpwIq$ z9bE3eH=FpMoA}!We-HeA#Z&Wd;f_=8zc=9O*?+$St7Tr+pq6#`Jy)Og=`zP2rWNrTicD=Lzo5wemi@N`SZ$EJ*?&vIb3U~v zb}6tn&!=;d$F>Z(%)2aHt=xah!IM{eVwVSNGp}nRkL^R?GVh9TwQ~Qh1W#V=iCr12 z&AhIyJhs)q&LL~NI@tBj{*&ty|24qYmi@OTSS|Z+G}!p;Ke;}M9|N|A+&|X>s~PX} zdu_1sId5`(j30x29k6!yqd5C<18iey9vjl!kL%NKL~}oGOrQO@K5^M!^U%jO@y!eF zzhAIz#j{2mz|C9kzYWpUv;Q^%t7RUJsg`xv7_654w+UEnki6M{o5FKGwI_Bnur|-9 zwaa7M0$k?Z60TP6zpdcOt39z>gSDC0HIT=)9oVt52HS&Om+TX{KJniHY>w=c9l>hZ zCp&?S&pwgsW4z~b=gRJR7Uw)~hHXol$L2K8^QQD$&^*su(dRt-Z!wqWd5?nc1Kzjd znbU4?^OWaxcQo~!*KuIAoL9$C%e?mltL41z1y&m*Z_ex9@T|Y~#Eu7RbN#JV9@~Ck zwOlv*gVn6v>*fHk^U3*;>tnoYc3@?9J;hniZL#e@^VqK5TI&Dq-1cDCb4U8D=LF)) z^*pTLM}d#7c-Hd}xOvL;JQPhm>p2mumi2TDwaoh?V7090;b65v@@74cfM-3mC-z9N zHrLZy<*^+DR?B)G3s%c|9tUn>#=B<6S9aG^ob~)Lw%uqRyU^U9JJatvuA7S+9BMvkHD1`1FdWrhd42%JrLxrk?d10IOyF978ShJO!+l^_vY=8zgVmZw@@` zr#-Q!g0;DR)+&$f46s`EtnoYc6McVJ;hni{jnWH^EiO!dhSPm zAkFnWm_F-yE^+00UQqB$!IxD$>v(Bbh_0iuoy0Wsne&Vd(BG?Y2c^pb}{SKj@ zNOS!@LZ30NCQiLvkB_0LXFaX~tGOPT-^byZpZ3h*TCg^25~rq-#2!iWIGmQ6jsRQJ zQS_eEMd@!O&U2a?uY>DX)_6Uddd|V8z-l+p{H@#gPlMH6Yjeu|z1&)E0xwCgZmsgv z>Nmu(G>@Zcsr4AJwH`-rtt-*rMqF9z&2at7T0e`Xo?34KtEJWz@xK+WZms5&TdTF) z4qm+)%Ub2Bbqx9mG>_wHskI$!tsV5%I-34&;>ud@fa_P*dMBECYW)IOEw!$R|6Oo( zYc;3bTE}4fB6yu@ENhjgR^O4jXdaVjskIYqt=;t2x<35_#Fe#v39esR>wReIsr7!a zT54Sn|1ZPUt<{`zYqgdK!DFkjtW};`eg8O-<}sO;TBm@mwTIqXeUE#DxU$x-!SyR^ zeF#lGwSFC}mRdK#|6#bgwVG3It=94=*mp>EYn7)~pY6RgkCSMrbt>3er_o#MHuR4Z zSJpZou3uT}V`%EB^;=-I)VekP--fGOt2yP?YAxTX3QPHTAzdKSJwIiH1*W_L$F$E-3k96!PTwRoN{ZmmKVUg(W_gl zJhkqLK8NOU3N5wH23zZ?^w#Qo$t%Q_wY~_~udMZ_XzHo;C9qm*9f$wRaCK`nr`%et zxf(bQAxYhbn1x-b5} zf~#ArIpx-BEx!T#zM*cd^3*yP{XCk-IW%iMoBmvywVqFJt-i~>L7cUQ|DlP$)5PCz z;tMUjVEiIYd}P7>H!1!9*;1~}@5py9eZ_yk_dt%=PYcsENm&f*ZaGCcXaJAcuyzj!3S9@aL18Xy{YbcNHpWrg@ zzu;}I+|zakYuDzvaNX20k3GR^+V-HA$F^5xORn*7wY>|UeZUuzJ2mdBjb?rNn^#Rg z*Cq3^f0g-q@3Q&tjc45b!Pesb5NCf}j^EWZk1J^I53jK+D|{*aRTZ8`|52K2_%ZtI zn}f(VqT+{xk0`i%_xOVQ-03d3_Q?fzjGls9%gIgr^d>&HiO*}|pJ?K@6nsAU?{DG{ zH}S`s_~T9di6;JZ!JSWQOJjXJZ?4rKn&<6cuxn}W-%mP({!p5S_KB6&&(V$odv5#Si_sqqSIa%?7_jHkIBS>dV{O)H ze*5G<7Ht0P?c=~|xo2uqE9Y*ViEjtHzj6;e0j}oQ?jgCc9rTWE@49u;chNkwPpa(p z<~|F33fLUxa=*!af18W#M6mB@>e}6(YGU`|AU5-!1a@3|$L*z`M)S}r%~HyiU&myLR5E9D8y4GvVstXMv5&zrA!e zSRZwBdTpyE=Q&_=x`&eET)2ApdEheV`EY&I%{iA|Eo*iG*m24=yAZA(pNqi8l|C24 z)w3R#fQ?g+b~*Ucsztj3ep%K0JGrjQm2h5B>c1bD$2k40*ZY_D)N&PAZLnGc&(BBU z>hZa{@+s^67@GQnoSzNpub~;I?lbaQdJpd#+CENGb03PczMlj;e#W{E+`N9*qp4@k zH-Ob_IoF>8n@d}AeHz@n&Nrf|$LA(+^LT@3>iKWrKLa*SJvH16R=4?Ymbvbq1zU$U z*V<=;TE@B+>{x^NI)B&tHn@6xZUGL_bdVD?)Hh1ZB2V6b>?d6?d_kh0vIfgm( zNscdon`^lXO+7w$gPUvlBAR-9?g3j%xi{{GtEZMPfm4fPm_whe<$Yl5)z5YF8LE~s zz6@3?=lcL$Jw6XsK4l$WK~vAaul7~2b*xJ>UVCg`D{T6CP38Q59jyH!n)TgJua>bN z0XNt8D4KeFzESy<^?egfJvGb+Tc5xG8L!>(9;VlpJ@YNFTH?M9&b+i)yK#@vYfIdB zz-o#6A8^Lg=Do$Z$LX~t?n$t7PTW)A#A!?16JTwL`!3k=!k-1}6aKx5=f6AnK3pI5 z_&f);p70-3JTX6n>!Y6Y^&_ykw8igv@G~@R=hPa{p%=i`s4e+_40f)`^%Jmu>hXCI z?D~iQ6kNu<1lLDB|DNH?U~_46ji07hGl$;QdHxY>oVx#wk!$)Vu>02a)}DNS z2A@nT*X=F1dVKx@?xy*To%Q%DTs>p{4Q!nHIn^F}8?1gAE%(EBz}7LArrqmDd(Ow- z!Pcr@#{UP{b#=Wm{<~oHjP)K^&G^K<4^EutL$06aV;a5Z!#?NZpJ2~N-n0J&R?B;~ zHnsAdwHNMqes6sMcAeIunbY@UhOO(4e>}R}_%5*H+Z#U*(?Zzv$$OsXYGE|>{JW7Y zV6{PPzF){Yi0Q2I**jKi<>OdwV8^oe{dxrbqBIZfi&b{}ti|GBbKFC18+c+$uXWm|uI0eine)9oSS{yUn_9Vs&ewRq?^ggjp5Ojrzy1Fgi?RM_ literal 34772 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 dR|2c$d}~uH*U@U+lO4{{xlVXbb=V diff --git a/piet-gpu/src/lib.rs b/piet-gpu/src/lib.rs index cd7bfbe..cbe95c9 100644 --- a/piet-gpu/src/lib.rs +++ b/piet-gpu/src/lib.rs @@ -199,6 +199,8 @@ pub struct Renderer { n_elements: usize, n_paths: usize, n_pathseg: usize, + + bg_image: hub::Image, } impl Renderer { @@ -301,16 +303,27 @@ impl Renderer { ], )?; + let bg_image = Self::make_test_bg_image(&session); + let k4_code = 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. + let max_textures = 256; let k4_pipeline = session .pipeline_builder() .add_buffers(3) .add_images(1) + .add_textures(max_textures) .create_compute_pipeline(&session, k4_code)?; let k4_ds = session .descriptor_set_builder() .add_buffers(&[&ptcl_buf, &tile_buf, &clip_scratch_buf]) .add_images(&[&image_dev]) + .add_textures(&[&bg_image]) .build(&session, &k4_pipeline)?; Ok(Renderer { @@ -347,6 +360,7 @@ impl Renderer { n_elements, n_paths, n_pathseg, + bg_image, }) } @@ -469,4 +483,22 @@ impl Renderer { Ok(image) } } + + /// Make a test image. + fn make_test_bg_image(session: &hub::Session) -> hub::Image { + const WIDTH: usize = 256; + const HEIGHT: usize = 256; + let mut buf = vec![255u8; WIDTH * HEIGHT * 4]; + for y in 0..HEIGHT { + for x in 0..WIDTH { + let r = x as u8; + let g = y as u8; + let b = r ^ g; + buf[(y * WIDTH + x) * 4] = r; + buf[(y * WIDTH + x) * 4 + 1] = g; + buf[(y * WIDTH + x) * 4 + 2] = b; + } + } + Self::make_image(session, WIDTH, HEIGHT, &buf, ImageFormat::RgbaPremul).unwrap() + } }