1use std::any::Any;
11use std::collections::HashMap;
12use std::ops::Range;
13use std::sync::Arc;
14
15use aetna_core::affine::Affine2;
16use aetna_core::paint::PhysicalScissor;
17use aetna_core::shader::stock_wgsl;
18use aetna_core::surface::{
19 AppTexture, AppTextureBackend, AppTextureId, SurfaceAlpha, SurfaceFormat, next_app_texture_id,
20};
21use aetna_core::tree::Rect;
22use bytemuck::{Pod, Zeroable};
23use vulkano::{
24 buffer::{
25 BufferUsage, Subbuffer,
26 allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo},
27 },
28 descriptor_set::{
29 DescriptorSet, WriteDescriptorSet, allocator::StandardDescriptorSetAllocator,
30 },
31 device::Device,
32 format::Format,
33 image::{
34 Image as VkImage, ImageAspects, ImageSubresourceRange, ImageUsage,
35 sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode},
36 view::{ImageView, ImageViewCreateInfo},
37 },
38 memory::allocator::{MemoryTypeFilter, StandardMemoryAllocator},
39 pipeline::{
40 DynamicState, GraphicsPipeline, Pipeline, PipelineShaderStageCreateInfo,
41 graphics::{
42 GraphicsPipelineCreateInfo,
43 color_blend::{
44 AttachmentBlend, BlendFactor, BlendOp, ColorBlendAttachmentState, ColorBlendState,
45 },
46 input_assembly::{InputAssemblyState, PrimitiveTopology},
47 rasterization::RasterizationState,
48 subpass::PipelineSubpassType,
49 vertex_input::{
50 VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate,
51 VertexInputState,
52 },
53 viewport::ViewportState,
54 },
55 },
56 render_pass::Subpass,
57 shader::{ShaderModule, ShaderModuleCreateInfo},
58};
59
60use crate::naga_compile::wgsl_to_spirv;
61use crate::pipeline::{build_shared_pipeline_layout, multisample_state};
62
63const INSTANCE_ARENA_SIZE: u64 = 32 * 1024;
64
65#[repr(C)]
66#[derive(Copy, Clone, Pod, Zeroable, Debug)]
67pub(crate) struct SurfaceInstance {
68 rect: [f32; 4],
69 matrix: [f32; 4],
70 translation: [f32; 2],
71}
72
73pub(crate) struct SurfaceRun {
74 pub texture_idx: usize,
75 pub scissor: Option<PhysicalScissor>,
76 pub alpha: SurfaceAlpha,
77 pub first: u32,
78 pub count: u32,
79}
80
81struct CachedDescriptor {
82 descriptor_set: Arc<DescriptorSet>,
83 last_used_frame: u64,
84}
85
86pub(crate) struct SurfacePaint {
87 instances: Vec<SurfaceInstance>,
88 instance_alloc: SubbufferAllocator,
89 instance_buf: Option<Subbuffer<[SurfaceInstance]>>,
90 runs: Vec<SurfaceRun>,
91
92 pipeline_premul: Arc<GraphicsPipeline>,
93 pipeline_straight: Arc<GraphicsPipeline>,
94 pipeline_opaque: Arc<GraphicsPipeline>,
95 sampler: Arc<Sampler>,
96
97 cache: HashMap<u64, CachedDescriptor>,
98 bind_group_lookup: Vec<u64>,
99 frame_counter: u64,
100
101 descriptor_alloc: Arc<StandardDescriptorSetAllocator>,
102}
103
104impl SurfacePaint {
105 pub(crate) fn new(
106 device: Arc<Device>,
107 memory_alloc: Arc<StandardMemoryAllocator>,
108 descriptor_alloc: Arc<StandardDescriptorSetAllocator>,
109 subpass: Subpass,
110 sample_count: u32,
111 ) -> Self {
112 let (pipeline_premul, pipeline_straight, pipeline_opaque) =
113 build_surface_pipelines(device.clone(), subpass, sample_count);
114 let sampler = Sampler::new(
115 device,
116 SamplerCreateInfo {
117 mag_filter: Filter::Linear,
118 min_filter: Filter::Linear,
119 mipmap_mode: SamplerMipmapMode::Linear,
120 address_mode: [SamplerAddressMode::ClampToEdge; 3],
121 ..Default::default()
122 },
123 )
124 .expect("aetna-vulkano: surface sampler");
125 let instance_alloc = SubbufferAllocator::new(
126 memory_alloc,
127 SubbufferAllocatorCreateInfo {
128 arena_size: INSTANCE_ARENA_SIZE,
129 buffer_usage: BufferUsage::VERTEX_BUFFER,
130 memory_type_filter: MemoryTypeFilter::PREFER_HOST
131 | MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
132 ..Default::default()
133 },
134 );
135
136 Self {
137 instances: Vec::new(),
138 instance_alloc,
139 instance_buf: None,
140 runs: Vec::new(),
141 pipeline_premul,
142 pipeline_straight,
143 pipeline_opaque,
144 sampler,
145 cache: HashMap::new(),
146 bind_group_lookup: Vec::new(),
147 frame_counter: 0,
148 descriptor_alloc,
149 }
150 }
151
152 pub(crate) fn frame_begin(&mut self) {
153 self.instances.clear();
154 self.runs.clear();
155 self.bind_group_lookup.clear();
156 self.frame_counter = self.frame_counter.wrapping_add(1);
157 }
158
159 pub(crate) fn record(
160 &mut self,
161 rect: Rect,
162 scissor: Option<PhysicalScissor>,
163 texture: &AppTexture,
164 alpha: SurfaceAlpha,
165 transform: Affine2,
166 ) -> Range<usize> {
167 if rect.w <= 0.0 || rect.h <= 0.0 {
168 let start = self.runs.len();
169 return start..start;
170 }
171 let start = self.runs.len();
172 let texture_idx = self.ensure_descriptor(texture);
173 let instance = SurfaceInstance {
174 rect: [rect.x, rect.y, rect.w, rect.h],
175 matrix: [transform.a, transform.b, transform.c, transform.d],
176 translation: [transform.tx, transform.ty],
177 };
178 let first = self.instances.len() as u32;
179 self.instances.push(instance);
180 self.runs.push(SurfaceRun {
181 texture_idx,
182 scissor,
183 alpha,
184 first,
185 count: 1,
186 });
187 start..self.runs.len()
188 }
189
190 fn ensure_descriptor(&mut self, texture: &AppTexture) -> usize {
191 let id = texture.id().0;
192 if !self.cache.contains_key(&id) {
193 let backend = texture.backend();
194 let vk_tex = backend
195 .as_any()
196 .downcast_ref::<VulkanoAppTexture>()
197 .unwrap_or_else(|| {
198 panic!(
199 "AppTexture passed to aetna-vulkano was not constructed by \
200 aetna_vulkano::app_texture (actual backend: {}); mixing \
201 backends in one runtime is unsupported",
202 texture.backend_name(),
203 )
204 });
205 let descriptor_set = DescriptorSet::new(
206 self.descriptor_alloc.clone(),
207 self.pipeline_premul.layout().set_layouts()[1].clone(),
208 [
209 WriteDescriptorSet::image_view(0, vk_tex.view.clone()),
210 WriteDescriptorSet::sampler(1, self.sampler.clone()),
211 ],
212 [],
213 )
214 .expect("aetna-vulkano: surface descriptor set");
215 self.cache.insert(
216 id,
217 CachedDescriptor {
218 descriptor_set,
219 last_used_frame: 0,
220 },
221 );
222 }
223 let entry = self.cache.get_mut(&id).expect("just inserted");
224 entry.last_used_frame = self.frame_counter;
225 if let Some(idx) = self.bind_group_lookup.iter().position(|&i| i == id) {
226 idx
227 } else {
228 self.bind_group_lookup.push(id);
229 self.bind_group_lookup.len() - 1
230 }
231 }
232
233 pub(crate) fn flush(&mut self) {
234 let frame = self.frame_counter;
235 self.cache.retain(|_, v| v.last_used_frame == frame);
236
237 if self.instances.is_empty() {
238 self.instance_buf = None;
239 return;
240 }
241 let buf = self
242 .instance_alloc
243 .allocate_slice::<SurfaceInstance>(self.instances.len() as u64)
244 .expect("aetna-vulkano: surface instance suballocate");
245 buf.write()
246 .expect("aetna-vulkano: surface instance suballocation write")
247 .copy_from_slice(&self.instances);
248 self.instance_buf = Some(buf);
249 }
250
251 pub(crate) fn run(&self, index: usize) -> &SurfaceRun {
252 &self.runs[index]
253 }
254
255 pub(crate) fn pipeline_for(&self, alpha: SurfaceAlpha) -> &Arc<GraphicsPipeline> {
256 match alpha {
257 SurfaceAlpha::Premultiplied => &self.pipeline_premul,
258 SurfaceAlpha::Straight => &self.pipeline_straight,
259 SurfaceAlpha::Opaque => &self.pipeline_opaque,
260 }
261 }
262
263 pub(crate) fn instance_buf(&self) -> &Subbuffer<[SurfaceInstance]> {
268 self.instance_buf
269 .as_ref()
270 .expect("aetna-vulkano: surface instance_buf accessed with no draws")
271 }
272
273 pub(crate) fn descriptor_for_run(&self, run: &SurfaceRun) -> &Arc<DescriptorSet> {
274 let id = self.bind_group_lookup[run.texture_idx];
275 &self
276 .cache
277 .get(&id)
278 .expect("cache entry alive for the frame")
279 .descriptor_set
280 }
281}
282
283fn build_surface_pipelines(
284 device: Arc<Device>,
285 subpass: Subpass,
286 sample_count: u32,
287) -> (
288 Arc<GraphicsPipeline>,
289 Arc<GraphicsPipeline>,
290 Arc<GraphicsPipeline>,
291) {
292 let words = wgsl_to_spirv("stock::surface", stock_wgsl::SURFACE)
293 .expect("aetna-vulkano: surface WGSL compile");
294 let module = unsafe {
295 ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&words))
296 .expect("aetna-vulkano: surface ShaderModule::new")
297 };
298 let premul = build_one(
299 device.clone(),
300 subpass.clone(),
301 sample_count,
302 &module,
303 "fs_premul",
304 Some(premultiplied_blend()),
305 );
306 let straight = build_one(
307 device.clone(),
308 subpass.clone(),
309 sample_count,
310 &module,
311 "fs_straight",
312 Some(premultiplied_blend()),
313 );
314 let opaque = build_one(
315 device,
316 subpass,
317 sample_count,
318 &module,
319 "fs_opaque",
320 Some(opaque_blend()),
321 );
322 (premul, straight, opaque)
323}
324
325fn build_one(
326 device: Arc<Device>,
327 subpass: Subpass,
328 sample_count: u32,
329 module: &Arc<ShaderModule>,
330 fs_entry: &'static str,
331 blend: Option<AttachmentBlend>,
332) -> Arc<GraphicsPipeline> {
333 let vs = module
334 .entry_point("vs_main")
335 .expect("surface.wgsl: missing vs_main");
336 let fs = module
337 .entry_point(fs_entry)
338 .unwrap_or_else(|| panic!("surface.wgsl: missing {fs_entry}"));
339 let stages = [
340 PipelineShaderStageCreateInfo::new(vs),
341 PipelineShaderStageCreateInfo::new(fs),
342 ];
343 let layout = build_shared_pipeline_layout(device.clone(), &stages);
344
345 let bind_vertex = VertexInputBindingDescription {
346 stride: (2 * std::mem::size_of::<f32>()) as u32,
347 input_rate: VertexInputRate::Vertex,
348 ..Default::default()
349 };
350 let bind_instance = VertexInputBindingDescription {
351 stride: std::mem::size_of::<SurfaceInstance>() as u32,
352 input_rate: VertexInputRate::Instance { divisor: 1 },
353 ..Default::default()
354 };
355 let attr = |binding: u32, offset: u32, format: Format| VertexInputAttributeDescription {
356 binding,
357 offset,
358 format,
359 ..Default::default()
360 };
361 let vertex_input_state = VertexInputState::new()
362 .binding(0, bind_vertex)
363 .binding(1, bind_instance)
364 .attribute(0, attr(0, 0, Format::R32G32_SFLOAT))
365 .attribute(1, attr(1, 0, Format::R32G32B32A32_SFLOAT))
367 .attribute(2, attr(1, 16, Format::R32G32B32A32_SFLOAT))
369 .attribute(3, attr(1, 32, Format::R32G32_SFLOAT));
371
372 GraphicsPipeline::new(
373 device,
374 None,
375 GraphicsPipelineCreateInfo {
376 stages: stages.into_iter().collect(),
377 vertex_input_state: Some(vertex_input_state),
378 input_assembly_state: Some(InputAssemblyState {
379 topology: PrimitiveTopology::TriangleStrip,
380 ..Default::default()
381 }),
382 viewport_state: Some(ViewportState::default()),
383 rasterization_state: Some(RasterizationState::default()),
384 multisample_state: Some(multisample_state(sample_count)),
385 color_blend_state: Some(ColorBlendState::with_attachment_states(
386 subpass.num_color_attachments(),
387 ColorBlendAttachmentState {
388 blend,
389 ..Default::default()
390 },
391 )),
392 dynamic_state: [DynamicState::Viewport, DynamicState::Scissor]
393 .into_iter()
394 .collect(),
395 subpass: Some(PipelineSubpassType::BeginRenderPass(subpass)),
396 ..GraphicsPipelineCreateInfo::layout(layout)
397 },
398 )
399 .expect("aetna-vulkano: surface GraphicsPipeline::new")
400}
401
402fn premultiplied_blend() -> AttachmentBlend {
403 AttachmentBlend {
404 src_color_blend_factor: BlendFactor::One,
405 dst_color_blend_factor: BlendFactor::OneMinusSrcAlpha,
406 color_blend_op: BlendOp::Add,
407 src_alpha_blend_factor: BlendFactor::One,
408 dst_alpha_blend_factor: BlendFactor::OneMinusSrcAlpha,
409 alpha_blend_op: BlendOp::Add,
410 }
411}
412
413fn opaque_blend() -> AttachmentBlend {
414 AttachmentBlend {
415 src_color_blend_factor: BlendFactor::One,
416 dst_color_blend_factor: BlendFactor::Zero,
417 color_blend_op: BlendOp::Add,
418 src_alpha_blend_factor: BlendFactor::One,
419 dst_alpha_blend_factor: BlendFactor::Zero,
420 alpha_blend_op: BlendOp::Add,
421 }
422}
423
424#[derive(Debug)]
430pub struct VulkanoAppTexture {
431 pub image: Arc<VkImage>,
435 pub view: Arc<ImageView>,
438 id: AppTextureId,
439 size: (u32, u32),
440 format: SurfaceFormat,
441}
442
443impl AppTextureBackend for VulkanoAppTexture {
444 fn id(&self) -> AppTextureId {
445 self.id
446 }
447 fn size_px(&self) -> (u32, u32) {
448 self.size
449 }
450 fn format(&self) -> SurfaceFormat {
451 self.format
452 }
453 fn as_any(&self) -> &dyn Any {
454 self
455 }
456}
457
458pub fn app_texture(image: Arc<VkImage>) -> AppTexture {
472 let format = match image.format() {
473 Format::R8G8B8A8_SRGB => SurfaceFormat::Rgba8UnormSrgb,
474 Format::B8G8R8A8_SRGB => SurfaceFormat::Bgra8UnormSrgb,
475 Format::R8G8B8A8_UNORM => SurfaceFormat::Rgba8Unorm,
476 f => panic!(
477 "aetna_vulkano::app_texture: unsupported image format {:?} \
478 (expected R8G8B8A8_SRGB / B8G8R8A8_SRGB / R8G8B8A8_UNORM)",
479 f
480 ),
481 };
482 assert!(
483 image.usage().intersects(ImageUsage::SAMPLED),
484 "aetna_vulkano::app_texture: source image must include SAMPLED usage (got {:?})",
485 image.usage(),
486 );
487 let samples = image.samples();
488 assert_eq!(
489 samples as u32, 1,
490 "aetna_vulkano::app_texture: source image must be single-sampled (got {:?})",
491 samples,
492 );
493 let extent = image.extent();
494 let size = (extent[0], extent[1]);
495 let view = ImageView::new(
496 image.clone(),
497 ImageViewCreateInfo {
498 subresource_range: ImageSubresourceRange {
499 aspects: ImageAspects::COLOR,
500 mip_levels: 0..1,
501 array_layers: 0..1,
502 },
503 ..ImageViewCreateInfo::from_image(&image)
504 },
505 )
506 .expect("aetna-vulkano: app_texture image view");
507 AppTexture::from_backend(Arc::new(VulkanoAppTexture {
508 image,
509 view,
510 id: next_app_texture_id(),
511 size,
512 format,
513 }))
514}