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