@@ -29,6 +29,10 @@ pub struct WindowRenderInfo { |
| 29 | 29 | pub blur_behind: bool, |
| 30 | 30 | /// Whether this window is currently focused. |
| 31 | 31 | pub focused: bool, |
| 32 | + /// Scale factor from Lua animation (x, y). Default: (1.0, 1.0). |
| 33 | + pub scale: (f32, f32), |
| 34 | + /// Position offset from Lua animation (x, y). Default: (0.0, 0.0). |
| 35 | + pub offset: (f32, f32), |
| 32 | 36 | } |
| 33 | 37 | |
| 34 | 38 | /// Blur configuration. |
@@ -111,6 +115,8 @@ pub struct Renderer { |
| 111 | 115 | test_uniform_buffer: wgpu::Buffer, |
| 112 | 116 | // Bind groups for windows (keyed by window ID) with uniform buffer and actual texture dimensions |
| 113 | 117 | window_bind_groups: HashMap<u32, (wgpu::BindGroup, wgpu::Buffer, u32, u32)>, |
| 118 | + // Shadow bind groups for windows (keyed by window ID) with uniform buffer |
| 119 | + shadow_bind_groups: HashMap<u32, (wgpu::BindGroup, wgpu::Buffer)>, |
| 114 | 120 | // Intermediate render target for multi-pass rendering (blur support) |
| 115 | 121 | intermediate_texture: Option<IntermediateTarget>, |
| 116 | 122 | // HDR render target and tonemapping (optional) |
@@ -173,6 +179,7 @@ impl Renderer { |
| 173 | 179 | test_texture: Some(test_texture), |
| 174 | 180 | test_uniform_buffer, |
| 175 | 181 | window_bind_groups: HashMap::new(), |
| 182 | + shadow_bind_groups: HashMap::new(), |
| 176 | 183 | intermediate_texture: None, |
| 177 | 184 | hdr_target: None, |
| 178 | 185 | tonemap_pipeline: None, |
@@ -344,16 +351,16 @@ impl Renderer { |
| 344 | 351 | Ok(tex) => { |
| 345 | 352 | tracing::trace!("Texture updated for window {:#x} (actual size {}x{})", win.id, tex.width, tex.height); |
| 346 | 353 | // Create or update bind group for this window with its own uniform buffer |
| 347 | | - // Only create new uniform buffer if window doesn't have one yet |
| 348 | | - let uniform_buffer = if let Some((_, existing_buffer, _, _)) = self.window_bind_groups.get(&win.id) { |
| 349 | | - // Reuse existing buffer (just update bind group with new texture view) |
| 350 | | - // This is a bit wasteful but simpler - we clone the buffer reference |
| 351 | | - self.pipeline.create_uniform_buffer(&self.gpu.device) |
| 352 | | - } else { |
| 353 | | - self.pipeline.create_uniform_buffer(&self.gpu.device) |
| 354 | | - }; |
| 354 | + let uniform_buffer = self.pipeline.create_uniform_buffer(&self.gpu.device); |
| 355 | 355 | let bind_group = self.pipeline.create_bind_group(&self.gpu.device, &tex.view, &uniform_buffer); |
| 356 | 356 | self.window_bind_groups.insert(win.id, (bind_group, uniform_buffer, tex.width, tex.height)); |
| 357 | + |
| 358 | + // Create shadow bind group for windows that need shadows |
| 359 | + if win.shadow_enabled && !self.shadow_bind_groups.contains_key(&win.id) { |
| 360 | + let shadow_buffer = self.shadow_pipeline.create_uniform_buffer(&self.gpu.device); |
| 361 | + let shadow_bind = self.shadow_pipeline.create_bind_group(&self.gpu.device, &shadow_buffer); |
| 362 | + self.shadow_bind_groups.insert(win.id, (shadow_bind, shadow_buffer)); |
| 363 | + } |
| 357 | 364 | } |
| 358 | 365 | Err(e) => { |
| 359 | 366 | tracing::warn!("Failed to update texture for window {:#x}: {}", win.id, e); |
@@ -409,41 +416,56 @@ impl Renderer { |
| 409 | 416 | // First pass: render shadows for all windows (back to front) |
| 410 | 417 | for win in windows { |
| 411 | 418 | if win.shadow_enabled { |
| 412 | | - // Use actual texture dimensions for shadow sizing |
| 413 | | - let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 414 | | - (*tex_w as f32, *tex_h as f32) |
| 415 | | - } else { |
| 416 | | - (win.width as f32, win.height as f32) |
| 417 | | - }; |
| 418 | | - self.shadow_pipeline.update_uniforms( |
| 419 | | - &self.gpu.queue, |
| 420 | | - win.x as f32, |
| 421 | | - win.y as f32, |
| 422 | | - w, |
| 423 | | - h, |
| 424 | | - vw as f32, |
| 425 | | - vh as f32, |
| 426 | | - win.corner_radius, |
| 427 | | - &self.shadow_config, |
| 428 | | - ); |
| 429 | | - self.shadow_pipeline.render(&mut render_pass); |
| 419 | + if let Some((shadow_bind, shadow_buffer)) = self.shadow_bind_groups.get(&win.id) { |
| 420 | + // Use actual texture dimensions for shadow sizing |
| 421 | + let (base_w, base_h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 422 | + (*tex_w as f32, *tex_h as f32) |
| 423 | + } else { |
| 424 | + (win.width as f32, win.height as f32) |
| 425 | + }; |
| 426 | + // Apply Lua animation transforms |
| 427 | + let x = win.x as f32 + win.offset.0; |
| 428 | + let y = win.y as f32 + win.offset.1; |
| 429 | + let w = base_w * win.scale.0; |
| 430 | + let h = base_h * win.scale.1; |
| 431 | + |
| 432 | + self.shadow_pipeline.update_uniforms( |
| 433 | + &self.gpu.queue, |
| 434 | + shadow_buffer, |
| 435 | + x, |
| 436 | + y, |
| 437 | + w, |
| 438 | + h, |
| 439 | + vw as f32, |
| 440 | + vh as f32, |
| 441 | + win.corner_radius, |
| 442 | + &self.shadow_config, |
| 443 | + ); |
| 444 | + self.shadow_pipeline.render(&mut render_pass, shadow_bind); |
| 445 | + } |
| 430 | 446 | } |
| 431 | 447 | } |
| 432 | 448 | |
| 433 | 449 | // Second pass: render windows (back to front) |
| 434 | 450 | for win in windows.iter() { |
| 435 | 451 | if let Some((bind_group, uniform_buffer, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 452 | + // Apply Lua animation transforms: offset position and scale size |
| 453 | + let x = win.x as f32 + win.offset.0; |
| 454 | + let y = win.y as f32 + win.offset.1; |
| 455 | + let w = *tex_w as f32 * win.scale.0; |
| 456 | + let h = *tex_h as f32 * win.scale.1; |
| 457 | + |
| 436 | 458 | tracing::debug!( |
| 437 | | - "Rendering window {:#x} at ({},{}) size {}x{} opacity={}", |
| 438 | | - win.id, win.x, win.y, tex_w, tex_h, win.opacity |
| 459 | + "Rendering window {:#x} at ({},{}) size {}x{} opacity={} scale=({:.2},{:.2})", |
| 460 | + win.id, x, y, w, h, win.opacity, win.scale.0, win.scale.1 |
| 439 | 461 | ); |
| 440 | 462 | self.pipeline.update_uniforms( |
| 441 | 463 | &self.gpu.queue, |
| 442 | 464 | uniform_buffer, |
| 443 | | - win.x as f32, |
| 444 | | - win.y as f32, |
| 445 | | - *tex_w as f32, |
| 446 | | - *tex_h as f32, |
| 465 | + x, |
| 466 | + y, |
| 467 | + w, |
| 468 | + h, |
| 447 | 469 | vw as f32, |
| 448 | 470 | vh as f32, |
| 449 | 471 | win.opacity, |
@@ -507,23 +529,26 @@ impl Renderer { |
| 507 | 529 | // Render shadows for all windows |
| 508 | 530 | for win in windows { |
| 509 | 531 | if win.shadow_enabled { |
| 510 | | - let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 511 | | - (*tex_w as f32, *tex_h as f32) |
| 512 | | - } else { |
| 513 | | - (win.width as f32, win.height as f32) |
| 514 | | - }; |
| 515 | | - self.shadow_pipeline.update_uniforms( |
| 516 | | - &self.gpu.queue, |
| 517 | | - win.x as f32, |
| 518 | | - win.y as f32, |
| 519 | | - w, |
| 520 | | - h, |
| 521 | | - vw as f32, |
| 522 | | - vh as f32, |
| 523 | | - win.corner_radius, |
| 524 | | - &self.shadow_config, |
| 525 | | - ); |
| 526 | | - self.shadow_pipeline.render(&mut render_pass); |
| 532 | + if let Some((shadow_bind, shadow_buffer)) = self.shadow_bind_groups.get(&win.id) { |
| 533 | + let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 534 | + (*tex_w as f32, *tex_h as f32) |
| 535 | + } else { |
| 536 | + (win.width as f32, win.height as f32) |
| 537 | + }; |
| 538 | + self.shadow_pipeline.update_uniforms( |
| 539 | + &self.gpu.queue, |
| 540 | + shadow_buffer, |
| 541 | + win.x as f32, |
| 542 | + win.y as f32, |
| 543 | + w, |
| 544 | + h, |
| 545 | + vw as f32, |
| 546 | + vh as f32, |
| 547 | + win.corner_radius, |
| 548 | + &self.shadow_config, |
| 549 | + ); |
| 550 | + self.shadow_pipeline.render(&mut render_pass, shadow_bind); |
| 551 | + } |
| 527 | 552 | } |
| 528 | 553 | } |
| 529 | 554 | |
@@ -595,23 +620,26 @@ impl Renderer { |
| 595 | 620 | // Re-render shadows |
| 596 | 621 | for win in windows { |
| 597 | 622 | if win.shadow_enabled { |
| 598 | | - let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 599 | | - (*tex_w as f32, *tex_h as f32) |
| 600 | | - } else { |
| 601 | | - (win.width as f32, win.height as f32) |
| 602 | | - }; |
| 603 | | - self.shadow_pipeline.update_uniforms( |
| 604 | | - &self.gpu.queue, |
| 605 | | - win.x as f32, |
| 606 | | - win.y as f32, |
| 607 | | - w, |
| 608 | | - h, |
| 609 | | - vw as f32, |
| 610 | | - vh as f32, |
| 611 | | - win.corner_radius, |
| 612 | | - &self.shadow_config, |
| 613 | | - ); |
| 614 | | - self.shadow_pipeline.render(&mut render_pass); |
| 623 | + if let Some((shadow_bind, shadow_buffer)) = self.shadow_bind_groups.get(&win.id) { |
| 624 | + let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 625 | + (*tex_w as f32, *tex_h as f32) |
| 626 | + } else { |
| 627 | + (win.width as f32, win.height as f32) |
| 628 | + }; |
| 629 | + self.shadow_pipeline.update_uniforms( |
| 630 | + &self.gpu.queue, |
| 631 | + shadow_buffer, |
| 632 | + win.x as f32, |
| 633 | + win.y as f32, |
| 634 | + w, |
| 635 | + h, |
| 636 | + vw as f32, |
| 637 | + vh as f32, |
| 638 | + win.corner_radius, |
| 639 | + &self.shadow_config, |
| 640 | + ); |
| 641 | + self.shadow_pipeline.render(&mut render_pass, shadow_bind); |
| 642 | + } |
| 615 | 643 | } |
| 616 | 644 | } |
| 617 | 645 | |
@@ -693,6 +721,7 @@ impl Renderer { |
| 693 | 721 | pub fn remove_window(&mut self, window_id: u32) { |
| 694 | 722 | self.texture_manager.remove_texture(window_id); |
| 695 | 723 | self.window_bind_groups.remove(&window_id); |
| 724 | + self.shadow_bind_groups.remove(&window_id); |
| 696 | 725 | } |
| 697 | 726 | |
| 698 | 727 | /// Poll the GPU device and sync display. |
@@ -807,12 +836,17 @@ impl Renderer { |
| 807 | 836 | // Create bind groups for HDR pipeline (must use HDR pipeline's bind group layout) |
| 808 | 837 | // Store both bind group and uniform buffer per window |
| 809 | 838 | let mut hdr_bind_groups: HashMap<u32, (wgpu::BindGroup, wgpu::Buffer)> = HashMap::new(); |
| 839 | + let mut hdr_shadow_bind_groups: HashMap<u32, (wgpu::BindGroup, wgpu::Buffer)> = HashMap::new(); |
| 810 | 840 | for (id, (_, _, _, _)) in &self.window_bind_groups { |
| 811 | 841 | if let Some(tex) = self.texture_manager.get_texture(*id) { |
| 812 | 842 | let uniform_buffer = hdr_pipeline.create_uniform_buffer(&self.gpu.device); |
| 813 | 843 | let bind_group = hdr_pipeline.create_bind_group(&self.gpu.device, &tex.view, &uniform_buffer); |
| 814 | 844 | hdr_bind_groups.insert(*id, (bind_group, uniform_buffer)); |
| 815 | 845 | } |
| 846 | + // Create HDR shadow bind group for this window |
| 847 | + let shadow_buffer = hdr_shadow_pipeline.create_uniform_buffer(&self.gpu.device); |
| 848 | + let shadow_bind = hdr_shadow_pipeline.create_bind_group(&self.gpu.device, &shadow_buffer); |
| 849 | + hdr_shadow_bind_groups.insert(*id, (shadow_bind, shadow_buffer)); |
| 816 | 850 | } |
| 817 | 851 | |
| 818 | 852 | let frame = self.gpu.begin_frame()?; |
@@ -848,23 +882,26 @@ impl Renderer { |
| 848 | 882 | // Render shadows using HDR shadow pipeline |
| 849 | 883 | for win in windows { |
| 850 | 884 | if win.shadow_enabled { |
| 851 | | - let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 852 | | - (*tex_w as f32, *tex_h as f32) |
| 853 | | - } else { |
| 854 | | - (win.width as f32, win.height as f32) |
| 855 | | - }; |
| 856 | | - hdr_shadow_pipeline.update_uniforms( |
| 857 | | - &self.gpu.queue, |
| 858 | | - win.x as f32, |
| 859 | | - win.y as f32, |
| 860 | | - w, |
| 861 | | - h, |
| 862 | | - vw as f32, |
| 863 | | - vh as f32, |
| 864 | | - win.corner_radius, |
| 865 | | - &self.shadow_config, |
| 866 | | - ); |
| 867 | | - hdr_shadow_pipeline.render(&mut render_pass); |
| 885 | + if let Some((shadow_bind, shadow_buffer)) = hdr_shadow_bind_groups.get(&win.id) { |
| 886 | + let (w, h) = if let Some((_, _, tex_w, tex_h)) = self.window_bind_groups.get(&win.id) { |
| 887 | + (*tex_w as f32, *tex_h as f32) |
| 888 | + } else { |
| 889 | + (win.width as f32, win.height as f32) |
| 890 | + }; |
| 891 | + hdr_shadow_pipeline.update_uniforms( |
| 892 | + &self.gpu.queue, |
| 893 | + shadow_buffer, |
| 894 | + win.x as f32, |
| 895 | + win.y as f32, |
| 896 | + w, |
| 897 | + h, |
| 898 | + vw as f32, |
| 899 | + vh as f32, |
| 900 | + win.corner_radius, |
| 901 | + &self.shadow_config, |
| 902 | + ); |
| 903 | + hdr_shadow_pipeline.render(&mut render_pass, shadow_bind); |
| 904 | + } |
| 868 | 905 | } |
| 869 | 906 | } |
| 870 | 907 | |