1use std::sync::Arc;
6use std::time::Instant;
7use winit::{
8 application::ApplicationHandler,
9 event::{WindowEvent, DeviceEvent, DeviceId},
10 event_loop::ActiveEventLoop,
11 window::{Window, WindowId},
12 dpi::PhysicalSize,
13};
14use log::{info, error, debug};
15
16use anvilkit_ecs::app::App;
17use anvilkit_ecs::physics::DeltaTime;
18use anvilkit_input::prelude::{InputState, KeyCode, MouseButton};
19use crate::window::{WindowConfig, WindowState};
20use crate::renderer::{RenderDevice, RenderSurface};
21use crate::renderer::assets::RenderAssets;
22use crate::renderer::draw::{ActiveCamera, DrawCommandList, SceneLights};
23use crate::renderer::state::{RenderState, PbrSceneUniform, GpuLight, MAX_LIGHTS};
24use crate::renderer::buffer::{
25 create_uniform_buffer, create_depth_texture_msaa,
26 create_hdr_render_target, create_hdr_msaa_texture,
27 create_sampler, create_texture_linear, create_shadow_map, create_shadow_sampler,
28 Vertex, PbrVertex, SHADOW_MAP_SIZE,
29};
30use crate::renderer::{RenderPipelineBuilder, DEPTH_FORMAT};
31use crate::renderer::ibl::generate_brdf_lut;
32use anvilkit_core::error::{AnvilKitError, Result};
33
34pub fn pack_lights(scene_lights: &SceneLights) -> ([GpuLight; MAX_LIGHTS], u32) {
39 let mut lights = [GpuLight::default(); MAX_LIGHTS];
40 let mut count = 0u32;
41
42 let dir = &scene_lights.directional;
44 lights[0] = GpuLight {
45 position_type: [0.0, 0.0, 0.0, 0.0], direction_range: [dir.direction.x, dir.direction.y, dir.direction.z, 0.0],
47 color_intensity: [dir.color.x, dir.color.y, dir.color.z, dir.intensity],
48 params: [0.0; 4],
49 };
50 count += 1;
51
52 for pl in &scene_lights.point_lights {
54 if count as usize >= MAX_LIGHTS { break; }
55 lights[count as usize] = GpuLight {
56 position_type: [pl.position.x, pl.position.y, pl.position.z, 1.0],
57 direction_range: [0.0, 0.0, 0.0, pl.range],
58 color_intensity: [pl.color.x, pl.color.y, pl.color.z, pl.intensity],
59 params: [0.0; 4],
60 };
61 count += 1;
62 }
63
64 for sl in &scene_lights.spot_lights {
66 if count as usize >= MAX_LIGHTS { break; }
67 lights[count as usize] = GpuLight {
68 position_type: [sl.position.x, sl.position.y, sl.position.z, 2.0],
69 direction_range: [sl.direction.x, sl.direction.y, sl.direction.z, sl.range],
70 color_intensity: [sl.color.x, sl.color.y, sl.color.z, sl.intensity],
71 params: [sl.inner_cone_angle.cos(), sl.outer_cone_angle.cos(), 0.0, 0.0],
72 };
73 count += 1;
74 }
75
76 (lights, count)
77}
78
79pub fn compute_light_space_matrix(light_direction: &glam::Vec3) -> glam::Mat4 {
84 let light_dir = light_direction.normalize();
85 let light_pos = -light_dir * 15.0;
87 let light_view = glam::Mat4::look_at_lh(light_pos, glam::Vec3::ZERO, glam::Vec3::Y);
88 let light_proj = glam::Mat4::orthographic_lh(-10.0, 10.0, -10.0, 10.0, 0.1, 30.0);
90 light_proj * light_view
91}
92
93const SHADOW_SHADER: &str = include_str!("../shaders/shadow.wgsl");
95
96const TONEMAP_SHADER: &str = include_str!("../shaders/tonemap.wgsl");
98
99pub struct RenderApp {
123 config: WindowConfig,
125 window: Option<Arc<Window>>,
127 window_state: WindowState,
129 render_device: Option<RenderDevice>,
131 render_surface: Option<RenderSurface>,
133
134 exit_requested: bool,
136
137 app: Option<App>,
140 gpu_initialized: bool,
142
143 last_frame_time: Instant,
145}
146
147impl RenderApp {
148 pub fn new(config: WindowConfig) -> Self {
163 info!("创建渲染应用: {}", config.title);
164
165 Self {
166 config,
167 window: None,
168 window_state: WindowState::new(),
169 render_device: None,
170 render_surface: None,
171 exit_requested: false,
172 app: None,
173 gpu_initialized: false,
174 last_frame_time: Instant::now(),
175 }
176 }
177
178 pub fn run(app: App) {
187 let event_loop = winit::event_loop::EventLoop::new().unwrap();
188
189 let window_config = app.world.get_resource::<crate::plugin::RenderConfig>()
191 .map(|c| c.window_config.clone())
192 .unwrap_or_default();
193
194 let mut render_app = Self::new(window_config);
195 render_app.app = Some(app);
196
197 event_loop.run_app(&mut render_app).unwrap();
198 }
199
200 pub fn config(&self) -> &WindowConfig {
202 &self.config
203 }
204
205 pub fn window_state(&self) -> &WindowState {
207 &self.window_state
208 }
209
210 pub fn window(&self) -> Option<&Arc<Window>> {
212 self.window.as_ref()
213 }
214
215 pub fn request_exit(&mut self) {
217 info!("请求退出应用");
218 self.exit_requested = true;
219 }
220
221 pub fn is_exit_requested(&self) -> bool {
223 self.exit_requested
224 }
225
226 pub fn render_device(&self) -> Option<&RenderDevice> {
228 self.render_device.as_ref()
229 }
230
231 pub fn surface_format(&self) -> Option<wgpu::TextureFormat> {
233 self.render_surface.as_ref().map(|s| s.format())
234 }
235
236 pub fn get_current_frame(&self) -> Option<wgpu::SurfaceTexture> {
238 self.render_surface.as_ref().and_then(|s| s.get_current_frame().ok())
239 }
240
241 fn create_window(&mut self, event_loop: &ActiveEventLoop) -> Result<()> {
245 if self.window.is_some() {
246 return Ok(());
247 }
248
249 info!("创建窗口: {} ({}x{})",
250 self.config.title, self.config.width, self.config.height);
251
252 let attributes = self.config.to_window_attributes();
253 let window = event_loop.create_window(attributes)
254 .map_err(|e| AnvilKitError::render(format!("创建窗口失败: {}", e)))?;
255
256 let size = window.inner_size();
257 self.window_state.set_size(size.width, size.height);
258 self.window_state.set_scale_factor(window.scale_factor());
259
260 self.window = Some(Arc::new(window));
261
262 info!("窗口创建成功");
263 Ok(())
264 }
265
266 async fn init_render(&mut self) -> Result<()> {
268 if self.render_device.is_some() {
269 return Ok(());
270 }
271
272 let window = self.window.as_ref()
273 .ok_or_else(|| AnvilKitError::render("窗口未创建".to_string()))?;
274
275 info!("初始化渲染设备和表面");
276
277 let device = RenderDevice::new(window).await?;
278 let surface = RenderSurface::new_with_vsync(&device, window, self.config.vsync)?;
279
280 self.render_device = Some(device);
281 self.render_surface = Some(surface);
282
283 info!("渲染设备和表面初始化成功");
284 Ok(())
285 }
286
287 fn inject_render_state_to_ecs(&mut self) {
289 if self.gpu_initialized {
290 return;
291 }
292
293 let Some(app) = &mut self.app else { return };
294 let Some(device) = &self.render_device else { return };
295 let Some(surface) = &self.render_surface else { return };
296
297 let format = surface.format();
298 let (w, h) = self.window_state.size();
299
300 let initial_uniform = PbrSceneUniform::default();
302 let scene_uniform_buffer = create_uniform_buffer(
303 device,
304 "ECS Scene Uniform",
305 bytemuck::bytes_of(&initial_uniform),
306 );
307
308 let scene_bind_group_layout = device.device().create_bind_group_layout(
309 &wgpu::BindGroupLayoutDescriptor {
310 label: Some("ECS Scene BGL"),
311 entries: &[wgpu::BindGroupLayoutEntry {
312 binding: 0,
313 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
314 ty: wgpu::BindingType::Buffer {
315 ty: wgpu::BufferBindingType::Uniform,
316 has_dynamic_offset: false,
317 min_binding_size: None,
318 },
319 count: None,
320 }],
321 },
322 );
323
324 let scene_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
325 label: Some("ECS Scene BG"),
326 layout: &scene_bind_group_layout,
327 entries: &[wgpu::BindGroupEntry {
328 binding: 0,
329 resource: scene_uniform_buffer.as_entire_binding(),
330 }],
331 });
332
333 let (_, depth_texture_view) = create_depth_texture_msaa(device, w, h, "ECS Depth MSAA");
334
335 let (_, hdr_texture_view) = create_hdr_render_target(device, w, h, "ECS HDR RT");
337 let (_, hdr_msaa_texture_view) = create_hdr_msaa_texture(device, w, h, "ECS HDR MSAA");
338 let sampler = create_sampler(device, "ECS Tonemap Sampler");
339
340 let tonemap_bind_group_layout = device.device().create_bind_group_layout(
342 &wgpu::BindGroupLayoutDescriptor {
343 label: Some("ECS Tonemap BGL"),
344 entries: &[
345 wgpu::BindGroupLayoutEntry {
346 binding: 0, visibility: wgpu::ShaderStages::FRAGMENT,
347 ty: wgpu::BindingType::Texture {
348 sample_type: wgpu::TextureSampleType::Float { filterable: true },
349 view_dimension: wgpu::TextureViewDimension::D2,
350 multisampled: false,
351 }, count: None,
352 },
353 wgpu::BindGroupLayoutEntry {
354 binding: 1, visibility: wgpu::ShaderStages::FRAGMENT,
355 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
356 count: None,
357 },
358 ],
359 },
360 );
361
362 let tonemap_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
363 label: Some("ECS Tonemap BG"),
364 layout: &tonemap_bind_group_layout,
365 entries: &[
366 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&hdr_texture_view) },
367 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
368 ],
369 });
370
371 let tonemap_pipeline_bgl = device.device().create_bind_group_layout(
373 &wgpu::BindGroupLayoutDescriptor {
374 label: Some("ECS Tonemap Pipeline BGL"),
375 entries: &[
376 wgpu::BindGroupLayoutEntry {
377 binding: 0, visibility: wgpu::ShaderStages::FRAGMENT,
378 ty: wgpu::BindingType::Texture {
379 sample_type: wgpu::TextureSampleType::Float { filterable: true },
380 view_dimension: wgpu::TextureViewDimension::D2,
381 multisampled: false,
382 }, count: None,
383 },
384 wgpu::BindGroupLayoutEntry {
385 binding: 1, visibility: wgpu::ShaderStages::FRAGMENT,
386 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
387 count: None,
388 },
389 ],
390 },
391 );
392
393 let tonemap_pipeline = RenderPipelineBuilder::new()
394 .with_vertex_shader(TONEMAP_SHADER)
395 .with_fragment_shader(TONEMAP_SHADER)
396 .with_format(format)
397 .with_vertex_layouts(vec![])
398 .with_bind_group_layouts(vec![tonemap_pipeline_bgl])
399 .with_label("ECS Tonemap Pipeline")
400 .build(device)
401 .expect("创建 Tonemap 管线失败")
402 .into_pipeline();
403
404 let brdf_lut_data = generate_brdf_lut(256);
406 let (_, brdf_lut_view) = create_texture_linear(device, 256, 256, &brdf_lut_data, "ECS BRDF LUT");
407 let (_, shadow_map_view) = create_shadow_map(device, SHADOW_MAP_SIZE, "ECS Shadow Map");
408 let shadow_sampler = create_shadow_sampler(device, "ECS Shadow Sampler");
409
410 let ibl_shadow_bind_group_layout = device.device().create_bind_group_layout(
411 &wgpu::BindGroupLayoutDescriptor {
412 label: Some("ECS IBL+Shadow BGL"),
413 entries: &[
414 wgpu::BindGroupLayoutEntry {
415 binding: 0, visibility: wgpu::ShaderStages::FRAGMENT,
416 ty: wgpu::BindingType::Texture {
417 sample_type: wgpu::TextureSampleType::Float { filterable: true },
418 view_dimension: wgpu::TextureViewDimension::D2,
419 multisampled: false,
420 }, count: None,
421 },
422 wgpu::BindGroupLayoutEntry {
423 binding: 1, visibility: wgpu::ShaderStages::FRAGMENT,
424 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
425 count: None,
426 },
427 wgpu::BindGroupLayoutEntry {
428 binding: 2, visibility: wgpu::ShaderStages::FRAGMENT,
429 ty: wgpu::BindingType::Texture {
430 sample_type: wgpu::TextureSampleType::Depth,
431 view_dimension: wgpu::TextureViewDimension::D2,
432 multisampled: false,
433 }, count: None,
434 },
435 wgpu::BindGroupLayoutEntry {
436 binding: 3, visibility: wgpu::ShaderStages::FRAGMENT,
437 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
438 count: None,
439 },
440 ],
441 },
442 );
443
444 let ibl_shadow_bind_group = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
445 label: Some("ECS IBL+Shadow BG"),
446 layout: &ibl_shadow_bind_group_layout,
447 entries: &[
448 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&brdf_lut_view) },
449 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
450 wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::TextureView(&shadow_map_view) },
451 wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Sampler(&shadow_sampler) },
452 ],
453 });
454
455 let shadow_scene_bgl = device.device().create_bind_group_layout(
457 &wgpu::BindGroupLayoutDescriptor {
458 label: Some("Shadow Scene BGL"),
459 entries: &[wgpu::BindGroupLayoutEntry {
460 binding: 0,
461 visibility: wgpu::ShaderStages::VERTEX,
462 ty: wgpu::BindingType::Buffer {
463 ty: wgpu::BufferBindingType::Uniform,
464 has_dynamic_offset: false,
465 min_binding_size: None,
466 },
467 count: None,
468 }],
469 },
470 );
471
472 let shadow_pipeline = RenderPipelineBuilder::new()
473 .with_vertex_shader(SHADOW_SHADER)
474 .with_format(wgpu::TextureFormat::Rgba8Unorm) .with_vertex_layouts(vec![PbrVertex::layout()])
476 .with_depth_format(DEPTH_FORMAT)
477 .with_bind_group_layouts(vec![shadow_scene_bgl])
478 .with_label("ECS Shadow Pipeline")
479 .build_depth_only(device)
480 .expect("创建 Shadow 管线失败")
481 .into_pipeline();
482
483 app.insert_resource(RenderState {
484 surface_format: format,
485 surface_size: (w, h),
486 scene_uniform_buffer,
487 scene_bind_group,
488 scene_bind_group_layout,
489 depth_texture_view,
490 hdr_texture_view,
491 tonemap_pipeline,
492 tonemap_bind_group,
493 tonemap_bind_group_layout,
494 ibl_shadow_bind_group,
495 ibl_shadow_bind_group_layout,
496 shadow_pipeline,
497 shadow_map_view,
498 hdr_msaa_texture_view,
499 });
500
501 self.gpu_initialized = true;
502 info!("RenderState (HDR + IBL + Shadow) 已注入 ECS World");
503 }
504
505 fn handle_resize(&mut self, new_size: PhysicalSize<u32>) {
507 debug!("窗口大小变化: {}x{}", new_size.width, new_size.height);
508
509 self.window_state.set_size(new_size.width, new_size.height);
510
511 if let (Some(device), Some(surface)) = (&self.render_device, &mut self.render_surface) {
512 if let Err(e) = surface.resize(device, new_size.width, new_size.height) {
513 error!("调整渲染表面大小失败: {}", e);
514 }
515 }
516
517 if self.gpu_initialized && new_size.width > 0 && new_size.height > 0 {
519 if let (Some(app), Some(device)) = (&mut self.app, &self.render_device) {
520 if let Some(mut rs) = app.world.get_resource_mut::<RenderState>() {
521 rs.surface_size = (new_size.width, new_size.height);
522 let (_, depth_view) = create_depth_texture_msaa(device, new_size.width, new_size.height, "ECS Depth MSAA");
523 rs.depth_texture_view = depth_view;
524
525 let (_, hdr_view) = create_hdr_render_target(device, new_size.width, new_size.height, "ECS HDR RT");
527 let (_, hdr_msaa_view) = create_hdr_msaa_texture(device, new_size.width, new_size.height, "ECS HDR MSAA");
528 let sampler = create_sampler(device, "ECS Sampler");
529 let new_bg = device.device().create_bind_group(&wgpu::BindGroupDescriptor {
530 label: Some("ECS Tonemap BG"),
531 layout: &rs.tonemap_bind_group_layout,
532 entries: &[
533 wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&hdr_view) },
534 wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&sampler) },
535 ],
536 });
537 rs.hdr_texture_view = hdr_view;
538 rs.hdr_msaa_texture_view = hdr_msaa_view;
539 rs.tonemap_bind_group = new_bg;
540 }
541 }
542 }
543 }
544
545 fn handle_scale_factor_changed(&mut self, scale_factor: f64) {
547 debug!("缩放因子变化: {}", scale_factor);
548 self.window_state.set_scale_factor(scale_factor);
549 }
550
551 fn render_ecs(&mut self) {
556 let (Some(device), Some(surface)) = (&self.render_device, &self.render_surface) else {
557 return;
558 };
559
560 let Some(app) = &self.app else { return };
561
562 let Some(active_camera) = app.world.get_resource::<ActiveCamera>() else { return };
563 let Some(draw_list) = app.world.get_resource::<DrawCommandList>() else { return };
564 let Some(render_assets) = app.world.get_resource::<RenderAssets>() else { return };
565 let Some(render_state) = app.world.get_resource::<RenderState>() else { return };
566
567 if draw_list.commands.is_empty() {
568 return;
569 }
570
571 let frame = match surface.get_current_frame_with_recovery(device) {
572 Ok(frame) => frame,
573 Err(e) => {
574 error!("获取当前帧失败: {}", e);
575 return;
576 }
577 };
578
579 let swapchain_view = frame.texture.create_view(&Default::default());
580 let view_proj = active_camera.view_proj;
581 let camera_pos = active_camera.camera_pos;
582
583 let default_lights = SceneLights::default();
585 let scene_lights = app.world.get_resource::<SceneLights>()
586 .unwrap_or(&default_lights);
587 let (gpu_lights, light_count) = pack_lights(scene_lights);
588 let light = &scene_lights.directional;
589
590 let shadow_view_proj = compute_light_space_matrix(&light.direction);
592
593 let mut encoder = device.device().create_command_encoder(
595 &wgpu::CommandEncoderDescriptor { label: Some("ECS Frame Encoder") },
596 );
597
598 {
600 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
601 label: Some("Shadow Pass"),
602 color_attachments: &[],
603 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
604 view: &render_state.shadow_map_view,
605 depth_ops: Some(wgpu::Operations {
606 load: wgpu::LoadOp::Clear(1.0),
607 store: wgpu::StoreOp::Store,
608 }),
609 stencil_ops: None,
610 }),
611 timestamp_writes: None,
612 occlusion_query_set: None,
613 });
614 rp.set_pipeline(&render_state.shadow_pipeline);
615 rp.set_bind_group(0, &render_state.scene_bind_group, &[]);
616
617 for cmd in draw_list.commands.iter() {
618 let Some(gpu_mesh) = render_assets.get_mesh(&cmd.mesh) else { continue };
619
620 let shadow_uniform = PbrSceneUniform {
621 model: cmd.model_matrix.to_cols_array_2d(),
622 view_proj: shadow_view_proj.to_cols_array_2d(),
623 ..Default::default()
624 };
625 device.queue().write_buffer(
626 &render_state.scene_uniform_buffer, 0, bytemuck::bytes_of(&shadow_uniform),
627 );
628
629 rp.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
630 rp.set_index_buffer(gpu_mesh.index_buffer.slice(..), gpu_mesh.index_format);
631 rp.draw_indexed(0..gpu_mesh.index_count, 0, 0..1);
632 }
633 }
634
635 {
637 let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
638 label: Some("ECS HDR Scene Pass"),
639 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
640 view: &render_state.hdr_msaa_texture_view,
641 resolve_target: Some(&render_state.hdr_texture_view),
642 ops: wgpu::Operations {
643 load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.15, g: 0.3, b: 0.6, a: 1.0 }),
644 store: wgpu::StoreOp::Discard,
645 },
646 })],
647 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
648 view: &render_state.depth_texture_view,
649 depth_ops: Some(wgpu::Operations {
650 load: wgpu::LoadOp::Clear(1.0),
651 store: wgpu::StoreOp::Store,
652 }),
653 stencil_ops: None,
654 }),
655 timestamp_writes: None,
656 occlusion_query_set: None,
657 });
658
659 for cmd in draw_list.commands.iter() {
660 let Some(gpu_mesh) = render_assets.get_mesh(&cmd.mesh) else { continue };
661 let Some(gpu_material) = render_assets.get_material(&cmd.material) else { continue };
662
663 let model = cmd.model_matrix;
664 let scale = glam::Vec3::new(
668 model.x_axis.truncate().length(),
669 model.y_axis.truncate().length(),
670 model.z_axis.truncate().length(),
671 );
672 let normal_matrix = if (scale.x - scale.y).abs() < 0.001
673 && (scale.y - scale.z).abs() < 0.001
674 {
675 model.transpose()
677 } else {
678 model.inverse().transpose()
680 };
681
682 let uniform = PbrSceneUniform {
683 model: model.to_cols_array_2d(),
684 view_proj: view_proj.to_cols_array_2d(),
685 normal_matrix: normal_matrix.to_cols_array_2d(),
686 camera_pos: [camera_pos.x, camera_pos.y, camera_pos.z, 0.0],
687 light_dir: [light.direction.x, light.direction.y, light.direction.z, 0.0],
688 light_color: [light.color.x, light.color.y, light.color.z, light.intensity],
689 material_params: [cmd.metallic, cmd.roughness, cmd.normal_scale, light_count as f32],
690 lights: gpu_lights,
691 shadow_view_proj: shadow_view_proj.to_cols_array_2d(),
692 emissive_factor: [cmd.emissive_factor[0], cmd.emissive_factor[1], cmd.emissive_factor[2], 1.0 / SHADOW_MAP_SIZE as f32],
693 };
694 device.queue().write_buffer(
695 &render_state.scene_uniform_buffer, 0, bytemuck::bytes_of(&uniform),
696 );
697
698 let Some(pipeline) = render_assets.get_pipeline(&gpu_material.pipeline_handle) else {
699 log::error!("材质引用了不存在的管线");
700 continue;
701 };
702 render_pass.set_pipeline(pipeline);
703 render_pass.set_bind_group(0, &render_state.scene_bind_group, &[]);
704 render_pass.set_bind_group(1, &gpu_material.bind_group, &[]);
705 render_pass.set_bind_group(2, &render_state.ibl_shadow_bind_group, &[]);
706 render_pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
707 render_pass.set_index_buffer(gpu_mesh.index_buffer.slice(..), gpu_mesh.index_format);
708 render_pass.draw_indexed(0..gpu_mesh.index_count, 0, 0..1);
709 }
710 }
711
712 {
714 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
715 label: Some("ECS Tonemap Pass"),
716 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
717 view: &swapchain_view,
718 resolve_target: None,
719 ops: wgpu::Operations {
720 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
721 store: wgpu::StoreOp::Store,
722 },
723 })],
724 depth_stencil_attachment: None,
725 timestamp_writes: None,
726 occlusion_query_set: None,
727 });
728
729 rp.set_pipeline(&render_state.tonemap_pipeline);
730 rp.set_bind_group(0, &render_state.tonemap_bind_group, &[]);
731 rp.draw(0..3, 0..1); }
733
734 device.queue().submit(std::iter::once(encoder.finish()));
736
737 frame.present();
738 }
739
740 fn render(&mut self) {
742 if self.app.is_some() && self.gpu_initialized {
743 self.render_ecs();
744 }
745 }
746}
747
748impl ApplicationHandler for RenderApp {
749 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
751 info!("应用恢复");
752
753 if let Err(e) = self.create_window(event_loop) {
754 error!("创建窗口失败: {}", e);
755 event_loop.exit();
756 return;
757 }
758
759 if let Err(e) = pollster::block_on(self.init_render()) {
760 error!("初始化渲染失败: {}", e);
761 event_loop.exit();
762 return;
763 }
764
765 self.inject_render_state_to_ecs();
767
768 if let Some(window) = &self.window {
769 window.request_redraw();
770 }
771 }
772
773 fn window_event(
775 &mut self,
776 event_loop: &ActiveEventLoop,
777 _window_id: WindowId,
778 event: WindowEvent,
779 ) {
780 match event {
781 WindowEvent::CloseRequested => {
782 info!("收到窗口关闭请求");
783 self.request_exit();
784 event_loop.exit();
785 }
786
787 WindowEvent::Resized(new_size) => {
788 self.handle_resize(new_size);
789 }
790
791 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
792 self.handle_scale_factor_changed(scale_factor);
793 }
794
795 WindowEvent::KeyboardInput { event, .. } => {
796 if let winit::keyboard::PhysicalKey::Code(code) = event.physical_key {
797 if let Some(app) = &mut self.app {
798 if let Some(mut input) = app.world.get_resource_mut::<InputState>() {
799 if let Some(key) = KeyCode::from_winit(code) {
800 if event.state.is_pressed() {
801 input.press_key(key);
802 } else {
803 input.release_key(key);
804 }
805 }
806 }
807 }
808 }
809 }
810
811 WindowEvent::MouseInput { state, button, .. } => {
812 if let Some(app) = &mut self.app {
813 if let Some(mut input) = app.world.get_resource_mut::<InputState>() {
814 if let Some(btn) = MouseButton::from_winit(button) {
815 if state.is_pressed() {
816 input.press_mouse(btn);
817 } else {
818 input.release_mouse(btn);
819 }
820 }
821 }
822 }
823 }
824
825 WindowEvent::CursorMoved { position, .. } => {
826 if let Some(app) = &mut self.app {
827 if let Some(mut input) = app.world.get_resource_mut::<InputState>() {
828 input.set_mouse_position(glam::Vec2::new(position.x as f32, position.y as f32));
829 }
830 }
831 }
832
833 WindowEvent::MouseWheel { delta, .. } => {
834 if let Some(app) = &mut self.app {
835 if let Some(mut input) = app.world.get_resource_mut::<InputState>() {
836 let scroll = match delta {
837 winit::event::MouseScrollDelta::LineDelta(_, y) => y,
838 winit::event::MouseScrollDelta::PixelDelta(pos) => pos.y as f32 / 120.0,
839 };
840 input.add_scroll_delta(scroll);
841 }
842 }
843 }
844
845 WindowEvent::Focused(focused) => {
846 debug!("窗口焦点变化: {}", focused);
847 self.window_state.set_focused(focused);
848 }
849
850 WindowEvent::Occluded(occluded) => {
851 debug!("窗口遮挡状态: {}", occluded);
852 self.window_state.set_minimized(occluded);
853 }
854
855 WindowEvent::RedrawRequested => {
856 self.render();
857 }
858
859 _ => {}
860 }
861 }
862
863 fn device_event(
865 &mut self,
866 _event_loop: &ActiveEventLoop,
867 _device_id: DeviceId,
868 _event: DeviceEvent,
869 ) {
870 }
872
873 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
875 if let Some(app) = &mut self.app {
877 let now = Instant::now();
879 let raw_dt = now.duration_since(self.last_frame_time).as_secs_f32();
880 self.last_frame_time = now;
881 let dt = raw_dt.clamp(0.001, 0.1);
883 app.world.insert_resource(DeltaTime(dt));
884
885 app.update();
886
887 if let Some(mut input) = app.world.get_resource_mut::<InputState>() {
889 input.end_frame();
890 }
891 }
892
893 if let Some(window) = &self.window {
894 window.request_redraw();
895 }
896 }
897}
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902
903 #[test]
904 fn test_render_app_creation() {
905 let config = WindowConfig::new().with_title("Test App");
906 let app = RenderApp::new(config);
907
908 assert_eq!(app.config().title, "Test App");
909 assert!(app.window().is_none());
910 assert!(!app.is_exit_requested());
911 }
912
913 #[test]
914 fn test_exit_request() {
915 let mut app = RenderApp::new(WindowConfig::default());
916
917 assert!(!app.is_exit_requested());
918 app.request_exit();
919 assert!(app.is_exit_requested());
920 }
921
922 #[test]
923 fn test_window_state_updates() {
924 let mut app = RenderApp::new(WindowConfig::default());
925
926 let new_size = PhysicalSize::new(1920, 1080);
927 app.handle_resize(new_size);
928 assert_eq!(app.window_state().size(), (1920, 1080));
929
930 app.handle_scale_factor_changed(2.0);
931 assert_eq!(app.window_state().scale_factor(), 2.0);
932 }
933
934 #[test]
935 fn test_render_app_config() {
936 let config = WindowConfig::new()
937 .with_title("Test")
938 .with_size(640, 480);
939 let app = RenderApp::new(config);
940
941 assert_eq!(app.config().title, "Test");
942 assert_eq!(app.config().width, 640);
943 }
944
945 #[test]
946 fn test_render_app_exit_request_toggle() {
947 let mut app = RenderApp::new(WindowConfig::new());
948 assert!(!app.is_exit_requested());
949
950 app.request_exit();
951 assert!(app.is_exit_requested());
952 }
953
954 #[test]
955 fn test_render_app_window_state() {
956 let config = WindowConfig::new().with_size(1024, 768);
957 let app = RenderApp::new(config);
958
959 let state = app.window_state();
961 assert_eq!(state.size(), (1280, 720));
962 }
963}