Krolyxon
Commit 92928dd

Branches

Tags

Refs →

remagesrcrenderimage.rs

383 lines · 13.0 KB · Rust

1//! Image textures: incremental row uploads, GPU mipmaps, linear/nearest sampling.
2
3use bytemuck::{Pod, Zeroable};
4use wgpu::util::DeviceExt;
5
6use crate::decode::DecodedImage;
7use crate::state::view::Affine;
8
9const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb;
10
11#[repr(C)]
12#[derive(Clone, Copy, Pod, Zeroable)]
13struct Uniforms {
14 m: [f32; 4],
15 t: [f32; 2],
16 viewport: [f32; 2],
17 bg: [f32; 4],
18 dot: [f32; 4],
19 params: [f32; 4],
20}
21
22/// How the area behind transparent pixels is painted.
23#[derive(Clone, Copy, Debug)]
24pub struct AlphaStyle {
25 /// 0 solid, 1 dots, 2 checker.
26 pub mode: u32,
27 pub bg: [f32; 4],
28 pub dot: [f32; 4],
29 pub cell: f32,
30 pub radius: f32,
31}
32
33pub struct GpuImage {
34 texture: wgpu::Texture,
35 bind_group: wgpu::BindGroup,
36 uniforms: wgpu::Buffer,
37 size: [u32; 2],
38 rows_done: u32,
39 ready: bool,
40}
41
42impl GpuImage {
43 pub fn is_ready(&self) -> bool {
44 self.ready
45 }
46}
47
48pub struct ImagePipeline {
49 pipeline: wgpu::RenderPipeline,
50 layout: wgpu::BindGroupLayout,
51 linear: wgpu::BindGroup,
52 nearest: wgpu::BindGroup,
53 mip_pipeline: wgpu::RenderPipeline,
54 mip_layout: wgpu::BindGroupLayout,
55 mip_sampler: wgpu::Sampler,
56}
57
58fn sampler_group(
59 device: &wgpu::Device,
60 layout: &wgpu::BindGroupLayout,
61 filter: wgpu::FilterMode,
62) -> wgpu::BindGroup {
63 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
64 label: Some("image sampler"),
65 mag_filter: filter,
66 min_filter: filter,
67 mipmap_filter: wgpu::MipmapFilterMode::Linear,
68 ..Default::default()
69 });
70 device.create_bind_group(&wgpu::BindGroupDescriptor {
71 label: Some("image sampler"),
72 layout,
73 entries: &[wgpu::BindGroupEntry {
74 binding: 0,
75 resource: wgpu::BindingResource::Sampler(&sampler),
76 }],
77 })
78}
79
80fn texture_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
81 wgpu::BindGroupLayoutEntry {
82 binding,
83 visibility: wgpu::ShaderStages::FRAGMENT,
84 ty: wgpu::BindingType::Texture {
85 sample_type: wgpu::TextureSampleType::Float { filterable: true },
86 view_dimension: wgpu::TextureViewDimension::D2,
87 multisampled: false,
88 },
89 count: None,
90 }
91}
92
93fn sampler_entry(binding: u32) -> wgpu::BindGroupLayoutEntry {
94 wgpu::BindGroupLayoutEntry {
95 binding,
96 visibility: wgpu::ShaderStages::FRAGMENT,
97 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
98 count: None,
99 }
100}
101
102impl ImagePipeline {
103 pub fn new(device: &wgpu::Device, target: wgpu::TextureFormat) -> Self {
104 let layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
105 label: Some("image"),
106 entries: &[
107 wgpu::BindGroupLayoutEntry {
108 binding: 0,
109 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
110 ty: wgpu::BindingType::Buffer {
111 ty: wgpu::BufferBindingType::Uniform,
112 has_dynamic_offset: false,
113 min_binding_size: None,
114 },
115 count: None,
116 },
117 texture_entry(1),
118 ],
119 });
120 let sampler_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
121 label: Some("image sampler"),
122 entries: &[sampler_entry(0)],
123 });
124 let shader = device.create_shader_module(wgpu::include_wgsl!("shaders/image.wgsl"));
125 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
126 label: Some("image"),
127 bind_group_layouts: &[Some(&layout), Some(&sampler_layout)],
128 immediate_size: 0,
129 });
130 let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
131 label: Some("image"),
132 layout: Some(&pipeline_layout),
133 vertex: wgpu::VertexState {
134 module: &shader,
135 entry_point: Some("vs"),
136 compilation_options: Default::default(),
137 buffers: &[],
138 },
139 primitive: wgpu::PrimitiveState::default(),
140 depth_stencil: None,
141 multisample: wgpu::MultisampleState::default(),
142 fragment: Some(wgpu::FragmentState {
143 module: &shader,
144 entry_point: Some("fs"),
145 compilation_options: Default::default(),
146 targets: &[Some(wgpu::ColorTargetState {
147 format: target,
148 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
149 write_mask: wgpu::ColorWrites::ALL,
150 })],
151 }),
152 multiview_mask: None,
153 cache: None,
154 });
155
156 let mip_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
157 label: Some("mip"),
158 entries: &[texture_entry(0), sampler_entry(1)],
159 });
160 let blit = device.create_shader_module(wgpu::include_wgsl!("shaders/blit.wgsl"));
161 let mip_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
162 label: Some("mip"),
163 bind_group_layouts: &[Some(&mip_layout)],
164 immediate_size: 0,
165 });
166 let mip_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
167 label: Some("mip"),
168 layout: Some(&mip_pipeline_layout),
169 vertex: wgpu::VertexState {
170 module: &blit,
171 entry_point: Some("vs"),
172 compilation_options: Default::default(),
173 buffers: &[],
174 },
175 primitive: wgpu::PrimitiveState::default(),
176 depth_stencil: None,
177 multisample: wgpu::MultisampleState::default(),
178 fragment: Some(wgpu::FragmentState {
179 module: &blit,
180 entry_point: Some("fs"),
181 compilation_options: Default::default(),
182 targets: &[Some(FORMAT.into())],
183 }),
184 multiview_mask: None,
185 cache: None,
186 });
187 let mip_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
188 label: Some("mip"),
189 mag_filter: wgpu::FilterMode::Linear,
190 min_filter: wgpu::FilterMode::Linear,
191 ..Default::default()
192 });
193
194 Self {
195 pipeline,
196 linear: sampler_group(device, &sampler_layout, wgpu::FilterMode::Linear),
197 nearest: sampler_group(device, &sampler_layout, wgpu::FilterMode::Nearest),
198 layout,
199 mip_pipeline,
200 mip_layout,
201 mip_sampler,
202 }
203 }
204
205 /// Allocate a texture for `img`; fill it with [`ImagePipeline::upload`].
206 pub fn create(&self, device: &wgpu::Device, img: &DecodedImage) -> GpuImage {
207 let [w, h] = img.size;
208 let mips = 32 - w.max(h).max(1).leading_zeros();
209 let texture = device.create_texture(&wgpu::TextureDescriptor {
210 label: Some("image"),
211 size: wgpu::Extent3d {
212 width: w,
213 height: h,
214 depth_or_array_layers: 1,
215 },
216 mip_level_count: mips,
217 sample_count: 1,
218 dimension: wgpu::TextureDimension::D2,
219 format: FORMAT,
220 usage: wgpu::TextureUsages::TEXTURE_BINDING
221 | wgpu::TextureUsages::COPY_DST
222 | wgpu::TextureUsages::RENDER_ATTACHMENT,
223 view_formats: &[],
224 });
225 let uniforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
226 label: Some("image uniforms"),
227 contents: bytemuck::bytes_of(&Uniforms::zeroed()),
228 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
229 });
230 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
231 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
232 label: Some("image"),
233 layout: &self.layout,
234 entries: &[
235 wgpu::BindGroupEntry {
236 binding: 0,
237 resource: uniforms.as_entire_binding(),
238 },
239 wgpu::BindGroupEntry {
240 binding: 1,
241 resource: wgpu::BindingResource::TextureView(&view),
242 },
243 ],
244 });
245 GpuImage {
246 texture,
247 bind_group,
248 uniforms,
249 size: [w, h],
250 rows_done: 0,
251 ready: false,
252 }
253 }
254
255 /// Upload up to `budget` bytes of rows. Returns true once the image is complete.
256 pub fn upload(
257 &self,
258 device: &wgpu::Device,
259 queue: &wgpu::Queue,
260 gpu: &mut GpuImage,
261 img: &DecodedImage,
262 budget: usize,
263 ) -> bool {
264 if gpu.ready {
265 return true;
266 }
267 let [w, h] = gpu.size;
268 let row_bytes = w as usize * 4;
269 let rows = ((budget / row_bytes.max(1)) as u32).clamp(1, h - gpu.rows_done);
270 let start = gpu.rows_done as usize * row_bytes;
271 let end = start + rows as usize * row_bytes;
272 queue.write_texture(
273 wgpu::TexelCopyTextureInfo {
274 texture: &gpu.texture,
275 mip_level: 0,
276 origin: wgpu::Origin3d {
277 x: 0,
278 y: gpu.rows_done,
279 z: 0,
280 },
281 aspect: wgpu::TextureAspect::All,
282 },
283 &img.pixels[start..end],
284 wgpu::TexelCopyBufferLayout {
285 offset: 0,
286 bytes_per_row: Some(row_bytes as u32),
287 rows_per_image: Some(rows),
288 },
289 wgpu::Extent3d {
290 width: w,
291 height: rows,
292 depth_or_array_layers: 1,
293 },
294 );
295 gpu.rows_done += rows;
296 if gpu.rows_done == h {
297 self.generate_mips(device, queue, &gpu.texture);
298 gpu.ready = true;
299 } else {
300 queue.submit([]);
301 }
302 gpu.ready
303 }
304
305 fn generate_mips(&self, device: &wgpu::Device, queue: &wgpu::Queue, texture: &wgpu::Texture) {
306 let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
307 label: Some("mips"),
308 });
309 let level_view = |level| {
310 texture.create_view(&wgpu::TextureViewDescriptor {
311 base_mip_level: level,
312 mip_level_count: Some(1),
313 ..Default::default()
314 })
315 };
316 for level in 1..texture.mip_level_count() {
317 let src = level_view(level - 1);
318 let dst = level_view(level);
319 let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
320 label: Some("mip"),
321 layout: &self.mip_layout,
322 entries: &[
323 wgpu::BindGroupEntry {
324 binding: 0,
325 resource: wgpu::BindingResource::TextureView(&src),
326 },
327 wgpu::BindGroupEntry {
328 binding: 1,
329 resource: wgpu::BindingResource::Sampler(&self.mip_sampler),
330 },
331 ],
332 });
333 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
334 label: Some("mip"),
335 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
336 view: &dst,
337 depth_slice: None,
338 resolve_target: None,
339 ops: wgpu::Operations {
340 load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
341 store: wgpu::StoreOp::Store,
342 },
343 })],
344 depth_stencil_attachment: None,
345 timestamp_writes: None,
346 occlusion_query_set: None,
347 multiview_mask: None,
348 });
349 pass.set_pipeline(&self.mip_pipeline);
350 pass.set_bind_group(0, &bind_group, &[]);
351 pass.draw(0..3, 0..1);
352 }
353 queue.submit([encoder.finish()]);
354 }
355
356 /// Write this frame's placement; call before the render pass begins.
357 pub fn prepare(
358 &self,
359 queue: &wgpu::Queue,
360 gpu: &GpuImage,
361 affine: Affine,
362 viewport: [f32; 2],
363 alpha: f32,
364 style: &AlphaStyle,
365 ) {
366 let u = Uniforms {
367 m: affine.m,
368 t: affine.t,
369 viewport,
370 bg: style.bg,
371 dot: style.dot,
372 params: [alpha, style.mode as f32, style.cell, style.radius],
373 };
374 queue.write_buffer(&gpu.uniforms, 0, bytemuck::bytes_of(&u));
375 }
376
377 pub fn draw(&self, pass: &mut wgpu::RenderPass<'_>, gpu: &GpuImage, nearest: bool) {
378 pass.set_pipeline(&self.pipeline);
379 pass.set_bind_group(0, &gpu.bind_group, &[]);
380 pass.set_bind_group(1, if nearest { &self.nearest } else { &self.linear }, &[]);
381 pass.draw(0..6, 0..1);
382 }
383}