@@ -50,6 +50,49 @@ impl Default for BlurConfig { |
| 50 | } | 50 | } |
| 51 | } | 51 | } |
| 52 | | 52 | |
| | 53 | +/// Intermediate render target for multi-pass rendering. |
| | 54 | +struct IntermediateTarget { |
| | 55 | + texture: wgpu::Texture, |
| | 56 | + view: wgpu::TextureView, |
| | 57 | + width: u32, |
| | 58 | + height: u32, |
| | 59 | +} |
| | 60 | + |
| | 61 | +impl IntermediateTarget { |
| | 62 | + fn new(device: &wgpu::Device, width: u32, height: u32, format: wgpu::TextureFormat) -> Self { |
| | 63 | + let texture = device.create_texture(&wgpu::TextureDescriptor { |
| | 64 | + label: Some("intermediate_render_target"), |
| | 65 | + size: wgpu::Extent3d { |
| | 66 | + width, |
| | 67 | + height, |
| | 68 | + depth_or_array_layers: 1, |
| | 69 | + }, |
| | 70 | + mip_level_count: 1, |
| | 71 | + sample_count: 1, |
| | 72 | + dimension: wgpu::TextureDimension::D2, |
| | 73 | + format, |
| | 74 | + usage: wgpu::TextureUsages::RENDER_ATTACHMENT |
| | 75 | + | wgpu::TextureUsages::TEXTURE_BINDING |
| | 76 | + | wgpu::TextureUsages::COPY_SRC, |
| | 77 | + view_formats: &[], |
| | 78 | + }); |
| | 79 | + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); |
| | 80 | + |
| | 81 | + Self { |
| | 82 | + texture, |
| | 83 | + view, |
| | 84 | + width, |
| | 85 | + height, |
| | 86 | + } |
| | 87 | + } |
| | 88 | + |
| | 89 | + fn resize(&mut self, device: &wgpu::Device, width: u32, height: u32, format: wgpu::TextureFormat) { |
| | 90 | + if self.width != width || self.height != height { |
| | 91 | + *self = Self::new(device, width, height, format); |
| | 92 | + } |
| | 93 | + } |
| | 94 | +} |
| | 95 | + |
| 53 | /// The main renderer for the compositor. | 96 | /// The main renderer for the compositor. |
| 54 | pub struct Renderer { | 97 | pub struct Renderer { |
| 55 | pub gpu: GpuContext, | 98 | pub gpu: GpuContext, |
@@ -64,6 +107,8 @@ pub struct Renderer { |
| 64 | test_texture: Option<WindowRenderData>, | 107 | test_texture: Option<WindowRenderData>, |
| 65 | // Bind groups for windows (keyed by window ID) | 108 | // Bind groups for windows (keyed by window ID) |
| 66 | window_bind_groups: HashMap<u32, wgpu::BindGroup>, | 109 | window_bind_groups: HashMap<u32, wgpu::BindGroup>, |
| | 110 | + // Intermediate render target for multi-pass rendering (blur support) |
| | 111 | + intermediate_texture: Option<IntermediateTarget>, |
| 67 | } | 112 | } |
| 68 | | 113 | |
| 69 | impl Renderer { | 114 | impl Renderer { |
@@ -102,6 +147,7 @@ impl Renderer { |
| 102 | }, | 147 | }, |
| 103 | test_texture: Some(test_texture), | 148 | test_texture: Some(test_texture), |
| 104 | window_bind_groups: HashMap::new(), | 149 | window_bind_groups: HashMap::new(), |
| | 150 | + intermediate_texture: None, |
| 105 | }) | 151 | }) |
| 106 | } | 152 | } |
| 107 | | 153 | |
@@ -184,6 +230,8 @@ impl Renderer { |
| 184 | /// Resize the render surface. | 230 | /// Resize the render surface. |
| 185 | pub fn resize(&mut self, width: u32, height: u32) { | 231 | pub fn resize(&mut self, width: u32, height: u32) { |
| 186 | self.gpu.resize(width, height); | 232 | self.gpu.resize(width, height); |
| | 233 | + // Invalidate intermediate texture so it gets recreated at new size |
| | 234 | + self.intermediate_texture = None; |
| 187 | } | 235 | } |
| 188 | | 236 | |
| 189 | /// Set the clear color (background). | 237 | /// Set the clear color (background). |
@@ -262,6 +310,19 @@ impl Renderer { |
| 262 | } | 310 | } |
| 263 | } | 311 | } |
| 264 | | 312 | |
| | 313 | + // Check if any window needs blur |
| | 314 | + let needs_blur = self.blur_config.enabled |
| | 315 | + && windows.iter().any(|w| w.blur_behind); |
| | 316 | + |
| | 317 | + if needs_blur { |
| | 318 | + self.render_windows_with_blur(windows) |
| | 319 | + } else { |
| | 320 | + self.render_windows_simple(windows) |
| | 321 | + } |
| | 322 | + } |
| | 323 | + |
| | 324 | + /// Simple render path when no blur is needed. |
| | 325 | + fn render_windows_simple(&mut self, windows: &[WindowRenderInfo]) -> Result<(), GpuError> { |
| 265 | let frame = self.gpu.begin_frame()?; | 326 | let frame = self.gpu.begin_frame()?; |
| 266 | let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); | 327 | let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); |
| 267 | | 328 | |
@@ -328,6 +389,205 @@ impl Renderer { |
| 328 | Ok(()) | 389 | Ok(()) |
| 329 | } | 390 | } |
| 330 | | 391 | |
| | 392 | + /// Render path with blur support for transparent windows. |
| | 393 | + fn render_windows_with_blur(&mut self, windows: &[WindowRenderInfo]) -> Result<(), GpuError> { |
| | 394 | + let (vw, vh) = self.gpu.dimensions(); |
| | 395 | + let format = self.gpu.format(); |
| | 396 | + |
| | 397 | + // Ensure intermediate texture exists and is correct size |
| | 398 | + if self.intermediate_texture.is_none() { |
| | 399 | + self.intermediate_texture = Some(IntermediateTarget::new( |
| | 400 | + &self.gpu.device, |
| | 401 | + vw, |
| | 402 | + vh, |
| | 403 | + format, |
| | 404 | + )); |
| | 405 | + } else if let Some(ref mut it) = self.intermediate_texture { |
| | 406 | + it.resize(&self.gpu.device, vw, vh, format); |
| | 407 | + } |
| | 408 | + |
| | 409 | + // Get frame surface |
| | 410 | + let frame = self.gpu.begin_frame()?; |
| | 411 | + let surface_view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); |
| | 412 | + |
| | 413 | + let mut encoder = self.gpu.create_encoder(); |
| | 414 | + |
| | 415 | + // Get intermediate target view (we need to reborrow after encoder creation) |
| | 416 | + let intermediate_view = &self.intermediate_texture.as_ref().unwrap().view; |
| | 417 | + |
| | 418 | + // Phase 1: Render shadows and non-blur windows to intermediate |
| | 419 | + { |
| | 420 | + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { |
| | 421 | + label: Some("composite_phase1"), |
| | 422 | + color_attachments: &[Some(wgpu::RenderPassColorAttachment { |
| | 423 | + view: intermediate_view, |
| | 424 | + resolve_target: None, |
| | 425 | + ops: wgpu::Operations { |
| | 426 | + load: wgpu::LoadOp::Clear(self.clear_color), |
| | 427 | + store: wgpu::StoreOp::Store, |
| | 428 | + }, |
| | 429 | + })], |
| | 430 | + depth_stencil_attachment: None, |
| | 431 | + timestamp_writes: None, |
| | 432 | + occlusion_query_set: None, |
| | 433 | + }); |
| | 434 | + |
| | 435 | + // Render shadows for all windows |
| | 436 | + for win in windows { |
| | 437 | + if win.shadow_enabled { |
| | 438 | + self.shadow_pipeline.update_uniforms( |
| | 439 | + &self.gpu.queue, |
| | 440 | + win.x as f32, |
| | 441 | + win.y as f32, |
| | 442 | + win.width as f32, |
| | 443 | + win.height as f32, |
| | 444 | + vw as f32, |
| | 445 | + vh as f32, |
| | 446 | + win.corner_radius, |
| | 447 | + &self.shadow_config, |
| | 448 | + ); |
| | 449 | + self.shadow_pipeline.render(&mut render_pass); |
| | 450 | + } |
| | 451 | + } |
| | 452 | + |
| | 453 | + // Render windows that don't need blur |
| | 454 | + for win in windows { |
| | 455 | + if !win.blur_behind { |
| | 456 | + if let Some(bind_group) = self.window_bind_groups.get(&win.id) { |
| | 457 | + self.pipeline.update_uniforms( |
| | 458 | + &self.gpu.queue, |
| | 459 | + win.x as f32, |
| | 460 | + win.y as f32, |
| | 461 | + win.width as f32, |
| | 462 | + win.height as f32, |
| | 463 | + vw as f32, |
| | 464 | + vh as f32, |
| | 465 | + win.opacity, |
| | 466 | + win.corner_radius, |
| | 467 | + ); |
| | 468 | + self.pipeline.render(&mut render_pass, bind_group); |
| | 469 | + } |
| | 470 | + } |
| | 471 | + } |
| | 472 | + } |
| | 473 | + // Render pass ends here, intermediate texture now contains background |
| | 474 | + |
| | 475 | + // Phase 2: Apply blur and render blur windows |
| | 476 | + // Run blur on the intermediate texture |
| | 477 | + let blur_iterations = self.blur_config.iterations; |
| | 478 | + let blur_strength = self.blur_config.strength; |
| | 479 | + |
| | 480 | + let blurred_view = self.blur_pipeline.blur( |
| | 481 | + &self.gpu.device, |
| | 482 | + &self.gpu.queue, |
| | 483 | + &mut encoder, |
| | 484 | + intermediate_view, |
| | 485 | + vw, |
| | 486 | + vh, |
| | 487 | + blur_iterations, |
| | 488 | + blur_strength, |
| | 489 | + ); |
| | 490 | + |
| | 491 | + // Create bind group for blurred texture (if available) before render pass |
| | 492 | + let blur_bind_group = blurred_view.map(|view| { |
| | 493 | + self.pipeline.create_bind_group(&self.gpu.device, view) |
| | 494 | + }); |
| | 495 | + |
| | 496 | + // Phase 3: Final composition to surface |
| | 497 | + // We need to composite: background + blurred regions + blur windows |
| | 498 | + { |
| | 499 | + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { |
| | 500 | + label: Some("composite_final"), |
| | 501 | + color_attachments: &[Some(wgpu::RenderPassColorAttachment { |
| | 502 | + view: &surface_view, |
| | 503 | + resolve_target: None, |
| | 504 | + ops: wgpu::Operations { |
| | 505 | + load: wgpu::LoadOp::Clear(self.clear_color), |
| | 506 | + store: wgpu::StoreOp::Store, |
| | 507 | + }, |
| | 508 | + })], |
| | 509 | + depth_stencil_attachment: None, |
| | 510 | + timestamp_writes: None, |
| | 511 | + occlusion_query_set: None, |
| | 512 | + }); |
| | 513 | + |
| | 514 | + // Re-render shadows |
| | 515 | + for win in windows { |
| | 516 | + if win.shadow_enabled { |
| | 517 | + self.shadow_pipeline.update_uniforms( |
| | 518 | + &self.gpu.queue, |
| | 519 | + win.x as f32, |
| | 520 | + win.y as f32, |
| | 521 | + win.width as f32, |
| | 522 | + win.height as f32, |
| | 523 | + vw as f32, |
| | 524 | + vh as f32, |
| | 525 | + win.corner_radius, |
| | 526 | + &self.shadow_config, |
| | 527 | + ); |
| | 528 | + self.shadow_pipeline.render(&mut render_pass); |
| | 529 | + } |
| | 530 | + } |
| | 531 | + |
| | 532 | + // Render all windows, using blurred background for blur_behind windows |
| | 533 | + for win in windows { |
| | 534 | + if win.blur_behind { |
| | 535 | + // First draw blurred background in window region |
| | 536 | + if let Some(ref bg) = blur_bind_group { |
| | 537 | + // Draw blurred background in window region |
| | 538 | + self.pipeline.update_uniforms( |
| | 539 | + &self.gpu.queue, |
| | 540 | + win.x as f32, |
| | 541 | + win.y as f32, |
| | 542 | + win.width as f32, |
| | 543 | + win.height as f32, |
| | 544 | + vw as f32, |
| | 545 | + vh as f32, |
| | 546 | + 1.0, // Opaque blur background |
| | 547 | + win.corner_radius, |
| | 548 | + ); |
| | 549 | + self.pipeline.render(&mut render_pass, bg); |
| | 550 | + } |
| | 551 | + |
| | 552 | + // Then draw the transparent window on top |
| | 553 | + if let Some(bind_group) = self.window_bind_groups.get(&win.id) { |
| | 554 | + self.pipeline.update_uniforms( |
| | 555 | + &self.gpu.queue, |
| | 556 | + win.x as f32, |
| | 557 | + win.y as f32, |
| | 558 | + win.width as f32, |
| | 559 | + win.height as f32, |
| | 560 | + vw as f32, |
| | 561 | + vh as f32, |
| | 562 | + win.opacity, |
| | 563 | + win.corner_radius, |
| | 564 | + ); |
| | 565 | + self.pipeline.render(&mut render_pass, bind_group); |
| | 566 | + } |
| | 567 | + } else { |
| | 568 | + // Non-blur window, render normally |
| | 569 | + if let Some(bind_group) = self.window_bind_groups.get(&win.id) { |
| | 570 | + self.pipeline.update_uniforms( |
| | 571 | + &self.gpu.queue, |
| | 572 | + win.x as f32, |
| | 573 | + win.y as f32, |
| | 574 | + win.width as f32, |
| | 575 | + win.height as f32, |
| | 576 | + vw as f32, |
| | 577 | + vh as f32, |
| | 578 | + win.opacity, |
| | 579 | + win.corner_radius, |
| | 580 | + ); |
| | 581 | + self.pipeline.render(&mut render_pass, bind_group); |
| | 582 | + } |
| | 583 | + } |
| | 584 | + } |
| | 585 | + } |
| | 586 | + |
| | 587 | + self.gpu.end_frame(encoder, frame); |
| | 588 | + Ok(()) |
| | 589 | + } |
| | 590 | + |
| 331 | /// Render a frame - currently renders the test pattern. | 591 | /// Render a frame - currently renders the test pattern. |
| 332 | pub fn render(&mut self) -> Result<(), GpuError> { | 592 | pub fn render(&mut self) -> Result<(), GpuError> { |
| 333 | // For now, just render the test pattern | 593 | // For now, just render the test pattern |