1use crate::context::GraphicsContext;
7use crate::Renderer;
8
9pub struct BlitRenderer {
23 pipeline: wgpu::RenderPipeline,
24 bind_group_layout: wgpu::BindGroupLayout,
25 sampler: wgpu::Sampler,
26 vertex_buffer: wgpu::Buffer,
27 context: &'static GraphicsContext,
28}
29
30impl BlitRenderer {
31 pub fn new(context: &'static GraphicsContext, target_format: wgpu::TextureFormat) -> Self {
38 Self::new_with_options(context, target_format, BlitOptions::default())
39 }
40
41 pub fn new_with_options(
43 context: &'static GraphicsContext,
44 target_format: wgpu::TextureFormat,
45 options: BlitOptions,
46 ) -> Self {
47 let renderer = Renderer::new(context);
48
49 let shader = renderer.create_shader(
51 Some("Blit Shader"),
52 include_str!("shaders/blit.wgsl"),
53 );
54
55 let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
57 label: Some("Blit Sampler"),
58 address_mode_u: wgpu::AddressMode::ClampToEdge,
59 address_mode_v: wgpu::AddressMode::ClampToEdge,
60 address_mode_w: wgpu::AddressMode::ClampToEdge,
61 mag_filter: options.filter_mode,
62 min_filter: options.filter_mode,
63 mipmap_filter: wgpu::FilterMode::Nearest,
64 ..Default::default()
65 });
66
67 let bind_group_layout =
69 context
70 .device
71 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
72 label: Some("Blit Bind Group Layout"),
73 entries: &[
74 wgpu::BindGroupLayoutEntry {
75 binding: 0,
76 visibility: wgpu::ShaderStages::FRAGMENT,
77 ty: wgpu::BindingType::Texture {
78 multisampled: false,
79 view_dimension: wgpu::TextureViewDimension::D2,
80 sample_type: wgpu::TextureSampleType::Float { filterable: true },
81 },
82 count: None,
83 },
84 wgpu::BindGroupLayoutEntry {
85 binding: 1,
86 visibility: wgpu::ShaderStages::FRAGMENT,
87 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
88 count: None,
89 },
90 ],
91 });
92
93 let pipeline_layout =
95 context
96 .device
97 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
98 label: Some("Blit Pipeline Layout"),
99 bind_group_layouts: &[&bind_group_layout],
100 push_constant_ranges: &[],
101 });
102
103 let pipeline = context
105 .device
106 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
107 label: Some("Blit Pipeline"),
108 layout: Some(&pipeline_layout),
109 vertex: wgpu::VertexState {
110 module: &shader,
111 entry_point: Some("vs_main"),
112 buffers: &[wgpu::VertexBufferLayout {
113 array_stride: 16,
114 step_mode: wgpu::VertexStepMode::Vertex,
115 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
116 }],
117 compilation_options: wgpu::PipelineCompilationOptions::default(),
118 },
119 fragment: Some(wgpu::FragmentState {
120 module: &shader,
121 entry_point: Some("fs_main"),
122 targets: &[Some(wgpu::ColorTargetState {
123 format: target_format,
124 blend: options.blend_state,
125 write_mask: wgpu::ColorWrites::ALL,
126 })],
127 compilation_options: wgpu::PipelineCompilationOptions::default(),
128 }),
129 primitive: wgpu::PrimitiveState {
130 topology: wgpu::PrimitiveTopology::TriangleList,
131 strip_index_format: None,
132 front_face: wgpu::FrontFace::Ccw,
133 cull_mode: None,
134 polygon_mode: wgpu::PolygonMode::Fill,
135 unclipped_depth: false,
136 conservative: false,
137 },
138 depth_stencil: None,
139 multisample: wgpu::MultisampleState {
140 count: 1,
141 mask: !0,
142 alpha_to_coverage_enabled: false,
143 },
144 multiview: None,
145 cache: None,
146 });
147
148 #[rustfmt::skip]
150 let vertices: [f32; 24] = [
151 -1.0, -1.0, 0.0, 1.0,
153 1.0, -1.0, 1.0, 1.0,
154 1.0, 1.0, 1.0, 0.0,
155 -1.0, -1.0, 0.0, 1.0,
156 1.0, 1.0, 1.0, 0.0,
157 -1.0, 1.0, 0.0, 0.0,
158 ];
159
160 let vertex_buffer = renderer.create_vertex_buffer(Some("Blit Vertex Buffer"), &vertices);
161
162 Self {
163 pipeline,
164 bind_group_layout,
165 sampler,
166 vertex_buffer,
167 context,
168 }
169 }
170
171 pub fn create_bind_group(&self, texture_view: &wgpu::TextureView) -> wgpu::BindGroup {
175 self.context
176 .device
177 .create_bind_group(&wgpu::BindGroupDescriptor {
178 label: Some("Blit Bind Group"),
179 layout: &self.bind_group_layout,
180 entries: &[
181 wgpu::BindGroupEntry {
182 binding: 0,
183 resource: wgpu::BindingResource::TextureView(texture_view),
184 },
185 wgpu::BindGroupEntry {
186 binding: 1,
187 resource: wgpu::BindingResource::Sampler(&self.sampler),
188 },
189 ],
190 })
191 }
192
193 pub fn blit(&self, render_pass: &mut wgpu::RenderPass, texture_view: &wgpu::TextureView) {
203 let bind_group = self.create_bind_group(texture_view);
204 self.blit_with_bind_group(render_pass, &bind_group);
205 }
206
207 pub fn blit_with_bind_group(
211 &self,
212 render_pass: &mut wgpu::RenderPass,
213 bind_group: &wgpu::BindGroup,
214 ) {
215 render_pass.set_pipeline(&self.pipeline);
216 render_pass.set_bind_group(0, bind_group, &[]);
217 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
218 render_pass.draw(0..6, 0..1);
219 }
220
221 pub fn bind_group_layout(&self) -> &wgpu::BindGroupLayout {
223 &self.bind_group_layout
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct BlitOptions {
230 pub filter_mode: wgpu::FilterMode,
232 pub blend_state: Option<wgpu::BlendState>,
234}
235
236impl Default for BlitOptions {
237 fn default() -> Self {
238 Self {
239 filter_mode: wgpu::FilterMode::Linear,
240 blend_state: Some(wgpu::BlendState::REPLACE),
241 }
242 }
243}
244
245impl BlitOptions {
246 pub fn opaque() -> Self {
248 Self {
249 filter_mode: wgpu::FilterMode::Linear,
250 blend_state: Some(wgpu::BlendState::REPLACE),
251 }
252 }
253
254 pub fn alpha_blend() -> Self {
256 Self {
257 filter_mode: wgpu::FilterMode::Linear,
258 blend_state: Some(wgpu::BlendState::ALPHA_BLENDING),
259 }
260 }
261
262 pub fn nearest() -> Self {
264 Self {
265 filter_mode: wgpu::FilterMode::Nearest,
266 blend_state: Some(wgpu::BlendState::REPLACE),
267 }
268 }
269
270 pub fn with_filter(mut self, filter: wgpu::FilterMode) -> Self {
272 self.filter_mode = filter;
273 self
274 }
275
276 pub fn with_blend(mut self, blend: Option<wgpu::BlendState>) -> Self {
278 self.blend_state = blend;
279 self
280 }
281}
282
283pub struct TextureUploader {
287 texture: wgpu::Texture,
288 view: wgpu::TextureView,
289 width: u32,
290 height: u32,
291 format: wgpu::TextureFormat,
292}
293
294impl TextureUploader {
295 pub fn new(
304 context: &GraphicsContext,
305 width: u32,
306 height: u32,
307 format: wgpu::TextureFormat,
308 ) -> Self {
309 let texture = context.device.create_texture(&wgpu::TextureDescriptor {
310 label: Some("Uploadable Texture"),
311 size: wgpu::Extent3d {
312 width,
313 height,
314 depth_or_array_layers: 1,
315 },
316 mip_level_count: 1,
317 sample_count: 1,
318 dimension: wgpu::TextureDimension::D2,
319 format,
320 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
321 view_formats: &[],
322 });
323
324 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
325
326 Self {
327 texture,
328 view,
329 width,
330 height,
331 format,
332 }
333 }
334
335 pub fn upload(&self, context: &GraphicsContext, data: &[u8]) {
342 let bytes_per_pixel = self.format.block_copy_size(None).unwrap_or(4);
343 let bytes_per_row = self.width * bytes_per_pixel;
344
345 context.queue.write_texture(
346 wgpu::TexelCopyTextureInfo {
347 texture: &self.texture,
348 mip_level: 0,
349 origin: wgpu::Origin3d::ZERO,
350 aspect: wgpu::TextureAspect::All,
351 },
352 data,
353 wgpu::TexelCopyBufferLayout {
354 offset: 0,
355 bytes_per_row: Some(bytes_per_row),
356 rows_per_image: Some(self.height),
357 },
358 wgpu::Extent3d {
359 width: self.width,
360 height: self.height,
361 depth_or_array_layers: 1,
362 },
363 );
364 }
365
366 pub fn upload_region(
375 &self,
376 context: &GraphicsContext,
377 data: &[u8],
378 x: u32,
379 y: u32,
380 width: u32,
381 height: u32,
382 ) {
383 let bytes_per_pixel = self.format.block_copy_size(None).unwrap_or(4);
384 let bytes_per_row = width * bytes_per_pixel;
385
386 context.queue.write_texture(
387 wgpu::TexelCopyTextureInfo {
388 texture: &self.texture,
389 mip_level: 0,
390 origin: wgpu::Origin3d { x, y, z: 0 },
391 aspect: wgpu::TextureAspect::All,
392 },
393 data,
394 wgpu::TexelCopyBufferLayout {
395 offset: 0,
396 bytes_per_row: Some(bytes_per_row),
397 rows_per_image: Some(height),
398 },
399 wgpu::Extent3d {
400 width,
401 height,
402 depth_or_array_layers: 1,
403 },
404 );
405 }
406
407 pub fn resize(&mut self, context: &GraphicsContext, width: u32, height: u32) {
409 if self.width == width && self.height == height {
410 return;
411 }
412
413 *self = Self::new(context, width, height, self.format);
414 }
415
416 pub fn view(&self) -> &wgpu::TextureView {
418 &self.view
419 }
420
421 pub fn texture(&self) -> &wgpu::Texture {
423 &self.texture
424 }
425
426 pub fn size(&self) -> (u32, u32) {
428 (self.width, self.height)
429 }
430
431 pub fn format(&self) -> wgpu::TextureFormat {
433 self.format
434 }
435}