1/// Carries frames to a window whose compositor blends premultiplied sRGB, as Core Animation's
2/// transparent layers do. The renderer blends in linear light into a texture of its own; on
3/// the way to the window each pixel is premultiplied again in sRGB, so a translucent colour
4/// over whatever shows through the window looks as it would over an opaque one.
5pub struct Translucent {
6 frame: Option<wgpu::Texture>,
7 format: wgpu::TextureFormat,
8 pipeline: wgpu::RenderPipeline,
9}
10
11impl Translucent {
12 /// Frames the renderer draws in `format` for a window view in `window`, which stores its
13 /// bytes as given.
14 pub fn new(
15 device: &wgpu::Device,
16 format: wgpu::TextureFormat,
17 window: wgpu::TextureFormat,
18 ) -> Self {
19 let shader = device.create_shader_module(wgpu::include_wgsl!("translucent.wgsl"));
20 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
21 label: Some("Translucent window"),
22 layout: None,
23 vertex: wgpu::VertexState {
24 module: &shader,
25 entry_point: Some("vertex"),
26 compilation_options: Default::default(),
27 buffers: &[],
28 },
29 fragment: Some(wgpu::FragmentState {
30 module: &shader,
31 entry_point: Some("fragment"),
32 compilation_options: Default::default(),
33 targets: &[Some(window.into())],
34 }),
35 primitive: Default::default(),
36 depth_stencil: None,
37 multisample: Default::default(),
38 multiview_mask: None,
39 cache: None,
40 });
41 Self {
42 frame: None,
43 format,
44 pipeline,
45 }
46 }
47
48 /// The view to draw a frame `size` pixels large into.
49 pub fn target(&mut self, device: &wgpu::Device, size: [u32; 2]) -> wgpu::TextureView {
50 let extent = wgpu::Extent3d {
51 width: size[0],
52 height: size[1],
53 depth_or_array_layers: 1,
54 };
55 let frame = match &self.frame {
56 Some(frame) if frame.size() == extent => frame,
57 _ => self
58 .frame
59 .insert(device.create_texture(&wgpu::TextureDescriptor {
60 label: Some("Translucent frame"),
61 size: extent,
62 mip_level_count: 1,
63 sample_count: 1,
64 dimension: wgpu::TextureDimension::D2,
65 format: self.format,
66 usage: wgpu::TextureUsages::RENDER_ATTACHMENT
67 | wgpu::TextureUsages::TEXTURE_BINDING,
68 view_formats: &[],
69 })),
70 };
71 frame.create_view(&Default::default())
72 }
73
74 /// Copies the frame last drawn into `target` to `window`.
75 pub fn present(&self, device: &wgpu::Device, queue: &wgpu::Queue, window: &wgpu::TextureView) {
76 let Some(frame) = &self.frame else {
77 return;
78 };
79 let binding = device.create_bind_group(&wgpu::BindGroupDescriptor {
80 label: Some("Translucent frame"),
81 layout: &self.pipeline.get_bind_group_layout(0),
82 entries: &[wgpu::BindGroupEntry {
83 binding: 0,
84 resource: wgpu::BindingResource::TextureView(
85 &frame.create_view(&Default::default()),
86 ),
87 }],
88 });
89 let mut encoder = device.create_command_encoder(&Default::default());
90 {
91 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
92 label: Some("Translucent window"),
93 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
94 view: window,
95 depth_slice: None,
96 resolve_target: None,
97 ops: wgpu::Operations {
98 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
99 store: wgpu::StoreOp::Store,
100 },
101 })],
102 ..Default::default()
103 });
104 pass.set_pipeline(&self.pipeline);
105 pass.set_bind_group(0, &binding, &[]);
106 pass.draw(0..3, 0..1);
107 }
108 queue.submit([encoder.finish()]);
109 }
110}