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