@@ -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 | 96 | /// The main renderer for the compositor. |
| 54 | 97 | pub struct Renderer { |
| 55 | 98 | pub gpu: GpuContext, |
@@ -64,6 +107,8 @@ pub struct Renderer { |
| 64 | 107 | test_texture: Option<WindowRenderData>, |
| 65 | 108 | // Bind groups for windows (keyed by window ID) |
| 66 | 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 | 114 | impl Renderer { |
@@ -102,6 +147,7 @@ impl Renderer { |
| 102 | 147 | }, |
| 103 | 148 | test_texture: Some(test_texture), |
| 104 | 149 | window_bind_groups: HashMap::new(), |
| 150 | + intermediate_texture: None, |
| 105 | 151 | }) |
| 106 | 152 | } |
| 107 | 153 | |
@@ -184,6 +230,8 @@ impl Renderer { |
| 184 | 230 | /// Resize the render surface. |
| 185 | 231 | pub fn resize(&mut self, width: u32, height: u32) { |
| 186 | 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 | 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 | 326 | let frame = self.gpu.begin_frame()?; |
| 266 | 327 | let view = frame.texture.create_view(&wgpu::TextureViewDescriptor::default()); |
| 267 | 328 | |
@@ -328,6 +389,205 @@ impl Renderer { |
| 328 | 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 | 591 | /// Render a frame - currently renders the test pattern. |
| 332 | 592 | pub fn render(&mut self) -> Result<(), GpuError> { |
| 333 | 593 | // For now, just render the test pattern |