1use astrelis_core::profiling::profile_function;
7
8use crate::Renderer;
9use crate::capability::{GpuRequirements, RenderCapability};
10use crate::context::GraphicsContext;
11use crate::types::{GpuTexture, TypedBuffer};
12use std::sync::Arc;
13
14impl RenderCapability for BlitRenderer {
28 fn requirements() -> GpuRequirements {
29 GpuRequirements::none()
30 }
31
32 fn name() -> &'static str {
33 "BlitRenderer"
34 }
35}
36
37pub struct BlitRenderer {
38 pipeline: wgpu::RenderPipeline,
39 bind_group_layout: wgpu::BindGroupLayout,
40 sampler: wgpu::Sampler,
41 vertex_buffer: TypedBuffer<f32>,
42 context: Arc<GraphicsContext>,
43}
44
45impl BlitRenderer {
46 pub fn new(context: Arc<GraphicsContext>, target_format: wgpu::TextureFormat) -> Self {
53 Self::new_with_options(context, target_format, BlitOptions::default())
54 }
55
56 pub fn new_with_options(
58 context: Arc<GraphicsContext>,
59 target_format: wgpu::TextureFormat,
60 options: BlitOptions,
61 ) -> Self {
62 profile_function!();
63 let renderer = Renderer::new(context.clone());
64
65 let shader = renderer.create_shader(Some("Blit Shader"), include_str!("shaders/blit.wgsl"));
67
68 let sampler = context.device().create_sampler(&wgpu::SamplerDescriptor {
70 label: Some("Blit Sampler"),
71 address_mode_u: wgpu::AddressMode::ClampToEdge,
72 address_mode_v: wgpu::AddressMode::ClampToEdge,
73 address_mode_w: wgpu::AddressMode::ClampToEdge,
74 mag_filter: options.filter_mode,
75 min_filter: options.filter_mode,
76 mipmap_filter: wgpu::FilterMode::Nearest,
77 ..Default::default()
78 });
79
80 let bind_group_layout =
82 context
83 .device()
84 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
85 label: Some("Blit Bind Group Layout"),
86 entries: &[
87 wgpu::BindGroupLayoutEntry {
88 binding: 0,
89 visibility: wgpu::ShaderStages::FRAGMENT,
90 ty: wgpu::BindingType::Texture {
91 multisampled: false,
92 view_dimension: wgpu::TextureViewDimension::D2,
93 sample_type: wgpu::TextureSampleType::Float { filterable: true },
94 },
95 count: None,
96 },
97 wgpu::BindGroupLayoutEntry {
98 binding: 1,
99 visibility: wgpu::ShaderStages::FRAGMENT,
100 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
101 count: None,
102 },
103 ],
104 });
105
106 let pipeline_layout =
108 context
109 .device()
110 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
111 label: Some("Blit Pipeline Layout"),
112 bind_group_layouts: &[&bind_group_layout],
113 push_constant_ranges: &[],
114 });
115
116 let pipeline = context
118 .device()
119 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
120 label: Some("Blit Pipeline"),
121 layout: Some(&pipeline_layout),
122 vertex: wgpu::VertexState {
123 module: &shader,
124 entry_point: Some("vs_main"),
125 buffers: &[wgpu::VertexBufferLayout {
126 array_stride: 16,
127 step_mode: wgpu::VertexStepMode::Vertex,
128 attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2],
129 }],
130 compilation_options: wgpu::PipelineCompilationOptions::default(),
131 },
132 fragment: Some(wgpu::FragmentState {
133 module: &shader,
134 entry_point: Some("fs_main"),
135 targets: &[Some(wgpu::ColorTargetState {
136 format: target_format,
137 blend: options.blend_state,
138 write_mask: wgpu::ColorWrites::ALL,
139 })],
140 compilation_options: wgpu::PipelineCompilationOptions::default(),
141 }),
142 primitive: wgpu::PrimitiveState {
143 topology: wgpu::PrimitiveTopology::TriangleList,
144 strip_index_format: None,
145 front_face: wgpu::FrontFace::Ccw,
146 cull_mode: None,
147 polygon_mode: wgpu::PolygonMode::Fill,
148 unclipped_depth: false,
149 conservative: false,
150 },
151 depth_stencil: None,
152 multisample: wgpu::MultisampleState {
153 count: 1,
154 mask: !0,
155 alpha_to_coverage_enabled: false,
156 },
157 multiview: None,
158 cache: None,
159 });
160
161 #[rustfmt::skip]
163 let vertices: [f32; 24] = [
164 -1.0, -1.0, 0.0, 1.0,
166 1.0, -1.0, 1.0, 1.0,
167 1.0, 1.0, 1.0, 0.0,
168 -1.0, -1.0, 0.0, 1.0,
169 1.0, 1.0, 1.0, 0.0,
170 -1.0, 1.0, 0.0, 0.0,
171 ];
172
173 let vertex_buffer =
174 renderer.create_typed_vertex_buffer(Some("Blit Vertex Buffer"), &vertices);
175
176 Self {
177 pipeline,
178 bind_group_layout,
179 sampler,
180 vertex_buffer,
181 context,
182 }
183 }
184
185 pub fn create_bind_group(&self, texture_view: &wgpu::TextureView) -> wgpu::BindGroup {
189 self.context
190 .device()
191 .create_bind_group(&wgpu::BindGroupDescriptor {
192 label: Some("Blit Bind Group"),
193 layout: &self.bind_group_layout,
194 entries: &[
195 wgpu::BindGroupEntry {
196 binding: 0,
197 resource: wgpu::BindingResource::TextureView(texture_view),
198 },
199 wgpu::BindGroupEntry {
200 binding: 1,
201 resource: wgpu::BindingResource::Sampler(&self.sampler),
202 },
203 ],
204 })
205 }
206
207 pub fn blit(&self, render_pass: &mut wgpu::RenderPass, texture_view: &wgpu::TextureView) {
217 profile_function!();
218 let bind_group = self.create_bind_group(texture_view);
219 self.blit_with_bind_group(render_pass, &bind_group);
220 }
221
222 pub fn blit_with_bind_group(
226 &self,
227 render_pass: &mut wgpu::RenderPass,
228 bind_group: &wgpu::BindGroup,
229 ) {
230 render_pass.push_debug_group("BlitRenderer::blit");
231 render_pass.set_pipeline(&self.pipeline);
232 render_pass.set_bind_group(0, bind_group, &[]);
233 render_pass.set_vertex_buffer(0, self.vertex_buffer.slice());
234 render_pass.draw(0..6, 0..1);
235 render_pass.pop_debug_group();
236 }
237
238 pub fn bind_group_layout(&self) -> &wgpu::BindGroupLayout {
240 &self.bind_group_layout
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct BlitOptions {
247 pub filter_mode: wgpu::FilterMode,
249 pub blend_state: Option<wgpu::BlendState>,
251}
252
253impl Default for BlitOptions {
254 fn default() -> Self {
255 Self {
256 filter_mode: wgpu::FilterMode::Linear,
257 blend_state: Some(wgpu::BlendState::REPLACE),
258 }
259 }
260}
261
262impl BlitOptions {
263 pub fn opaque() -> Self {
265 Self {
266 filter_mode: wgpu::FilterMode::Linear,
267 blend_state: Some(wgpu::BlendState::REPLACE),
268 }
269 }
270
271 pub fn alpha_blend() -> Self {
273 Self {
274 filter_mode: wgpu::FilterMode::Linear,
275 blend_state: Some(wgpu::BlendState::ALPHA_BLENDING),
276 }
277 }
278
279 pub fn nearest() -> Self {
281 Self {
282 filter_mode: wgpu::FilterMode::Nearest,
283 blend_state: Some(wgpu::BlendState::REPLACE),
284 }
285 }
286
287 pub fn with_filter(mut self, filter: wgpu::FilterMode) -> Self {
289 self.filter_mode = filter;
290 self
291 }
292
293 pub fn with_blend(mut self, blend: Option<wgpu::BlendState>) -> Self {
295 self.blend_state = blend;
296 self
297 }
298}
299
300pub struct TextureUploader {
304 texture: GpuTexture,
306}
307
308impl TextureUploader {
309 pub fn new(
318 context: &GraphicsContext,
319 width: u32,
320 height: u32,
321 format: wgpu::TextureFormat,
322 ) -> Self {
323 let texture = GpuTexture::new_2d(
324 context.device(),
325 Some("Uploadable Texture"),
326 width,
327 height,
328 format,
329 wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
330 );
331
332 Self { texture }
333 }
334
335 pub fn upload(&self, context: &GraphicsContext, data: &[u8]) {
342 use crate::extension::AsWgpu;
343 let bytes_per_pixel = self.texture.format().block_copy_size(None).unwrap_or(4);
344 let bytes_per_row = self.texture.width() * bytes_per_pixel;
345
346 context.queue().write_texture(
347 wgpu::TexelCopyTextureInfo {
348 texture: self.texture.as_wgpu(),
349 mip_level: 0,
350 origin: wgpu::Origin3d::ZERO,
351 aspect: wgpu::TextureAspect::All,
352 },
353 data,
354 wgpu::TexelCopyBufferLayout {
355 offset: 0,
356 bytes_per_row: Some(bytes_per_row),
357 rows_per_image: Some(self.texture.height()),
358 },
359 wgpu::Extent3d {
360 width: self.texture.width(),
361 height: self.texture.height(),
362 depth_or_array_layers: 1,
363 },
364 );
365 }
366
367 pub fn upload_region(
376 &self,
377 context: &GraphicsContext,
378 data: &[u8],
379 x: u32,
380 y: u32,
381 width: u32,
382 height: u32,
383 ) {
384 use crate::extension::AsWgpu;
385 let bytes_per_pixel = self.texture.format().block_copy_size(None).unwrap_or(4);
386 let bytes_per_row = width * bytes_per_pixel;
387
388 context.queue().write_texture(
389 wgpu::TexelCopyTextureInfo {
390 texture: self.texture.as_wgpu(),
391 mip_level: 0,
392 origin: wgpu::Origin3d { x, y, z: 0 },
393 aspect: wgpu::TextureAspect::All,
394 },
395 data,
396 wgpu::TexelCopyBufferLayout {
397 offset: 0,
398 bytes_per_row: Some(bytes_per_row),
399 rows_per_image: Some(height),
400 },
401 wgpu::Extent3d {
402 width,
403 height,
404 depth_or_array_layers: 1,
405 },
406 );
407 }
408
409 pub fn resize(&mut self, context: &GraphicsContext, width: u32, height: u32) {
411 if self.texture.width() == width && self.texture.height() == height {
412 return;
413 }
414
415 *self = Self::new(context, width, height, self.texture.format());
416 }
417
418 pub fn view(&self) -> &wgpu::TextureView {
420 self.texture.view()
421 }
422
423 pub fn texture(&self) -> &wgpu::Texture {
425 use crate::extension::AsWgpu;
426 self.texture.as_wgpu()
427 }
428
429 pub fn size(&self) -> (u32, u32) {
431 (self.texture.width(), self.texture.height())
432 }
433
434 pub fn format(&self) -> wgpu::TextureFormat {
436 self.texture.format()
437 }
438}