Bevy 迁移指南:Render World 窗口数据改为 ECS 组件(ExtractedWindow 与 SurfaceData)
【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy
在 Bevy 的 Render World 中,窗口数据不再以ExtractedWindows/ExtractedWindowSurfaces这类资源形式存在,而是直接作为组件挂在渲染世界中与窗口关联的实体上。如果你曾在自定义渲染逻辑中读取过这两个资源,本次迁移只需将资源查询替换为Query<&ExtractedWindow>或Query<&SurfaceData>;如果你依赖ExtractedWindows::primary,现在可以改用Query<&ExtractedWindow, With<PrimaryWindow>>。读完本文,你将理解新组件的完整字段结构、它在 Extract / Render 调度中的实际数据流,以及逐条可执行的迁移替换方案。
迁移背景:从资源到组件
Bevy 的渲染流程分为 Main World 与 Render World 两个世界:Main World 承载游戏逻辑,Render World 承载渲染所需的提取(extracted)数据。过去,窗口相关的提取数据被集中放在两个资源里:
ExtractedWindows:所有窗口的提取数据(尺寸、present mode 等),并带有primary字段指向主窗口;ExtractedWindowSurfaces:每个窗口对应的SurfaceData(wgpu surface 与 surface 配置)。
本次变更(对应上游 PR #25005)将这两份数据拆散,直接作为组件挂到 Render World 中每个窗口对应的实体上。这带来三个直接收益:
- 逐窗口访问更自然:过去要从资源里按 entity 查找,现在标准的 ECS 查询即可,且能享受查询缓存与调度器依赖分析的好处;
- 与
PrimaryWindow标签组件统一:主窗口判断从资源字段变为实体上的标签组件,可用With<PrimaryWindow>过滤; - 与 Render World 实体的生命周期同步:窗口关闭时实体被 despawn,其携带的窗口数据随之销毁,无需资源手动清理。
从当前仓库源码可以确认,旧的ExtractedWindows与ExtractedWindowSurfaces资源类型已不存在于 bevy_render 中,全仓库仅余一处注释提及旧名。
新 API:ExtractedWindow组件
新的核心类型是 ExtractedWindow,定义于bevy_render的view::window模块。它是#[derive(Component)]组件,字段说明如下:
| 字段 | 类型 | 说明 |
|---|---|---|
physical_width/physical_height | u32 | 窗口的物理像素尺寸,提取时保证最小为 1 |
present_mode | PresentMode | 来自 Main World 的Window::present_mode |
desired_maximum_frame_latency | Option<NonZero<u32>> | 期望的最大帧延迟;未设置时 surface 配置使用默认值 2(见下文create_surfaces) |
swap_chain_texture_view | Option<TextureView> | 交换链纹理视图;截图时会指向替代纹理以便把渲染结果拷回 CPU |
swap_chain_texture | Option<SurfaceTexture> | 当前帧的交换链纹理(wgpu::SurfaceTexture包装) |
swap_chain_texture_format | Option<TextureFormat> | 交换链纹理格式(即 surface 配置格式) |
swap_chain_texture_view_format | Option<TextureFormat> | 上者加 sRGB 后缀的视图格式,使着色器中始终处于线性空间 |
size_changed | bool | 本帧尺寸是否变化,create_surfaces据此决定是否重配 surface |
present_mode_changed | bool | 本帧 present mode 是否变化 |
alpha_mode | CompositeAlphaMode | 窗口的合成 alpha 模式,逐值映射到wgpu::CompositeAlphaMode |
needs_initial_present | bool | 是否需要初始提交;Wayland 下窗口必须至少 present 一次才会显示 |
窗口实体上还会同时携带:
PrimaryWindow:标签组件,标识主窗口;RawHandleWrapper:原始窗口句柄(display handle + window handle),供创建 wgpu surface 使用;SurfaceData:见下一节。
ExtractedWindow还提供了一个实例方法 present():取出swap_chain_texture并调用其present(queue)将帧提交给 wgpu。
新 API:SurfaceData组件
SurfaceData 是ExtractedWindowSurfaces资源的替代,同样是窗口实体上的组件:
#[derive(Component)] pub struct SurfaceData { surface: WgpuSurface, // wgpu::Surface 的 Send 包装 configuration: SurfaceConfiguration, texture_view_format: Option<TextureFormat>, }其中WgpuSurface通过wgpu_wrapper!宏生成(见同一文件 L212-L213),用于把wgpu::Surface<'static>装进 ECS 的 Send 约束体系。若你过去通过ExtractedWindowSurfaces读取 surface 配置(格式、宽度、高度、present mode),现在直接Query<&SurfaceData>即可。
数据如何产生:extract_windows 系统
窗口数据由 extract_windows 系统在ExtractSchedule中产生,执行顺序位于extract_cameras之前(在 WindowRenderPlugin 中注册为extract_windows.before(extract_cameras))。其工作逻辑:
- 遍历 Main World 中带
Window组件的实体(通过Extract<Query<(RenderEntity, &Window, &RawHandleWrapper, Has<PrimaryWindow>)>>); - 若窗口带
PrimaryWindow,向 Render World 对应实体插入PrimaryWindow; - 若该实体尚无
ExtractedWindow,则插入一份初始组件(尺寸取window.resolution的物理分辨率并钳制最小为 1,needs_initial_present初始为true)并附带RawHandleWrapper; - 否则比较新旧尺寸与 present mode,分别置位
size_changed/present_mode_changed标志,并输出 debug 日志; - 响应
WindowClosing消息、RawHandleWrapper移除与PrimaryWindow移除事件,在 Render World 中对应地 despawn 窗口实体或移除PrimaryWindow。
插件初始化部分还值得注意:它用 observer 在每次Add<Window>时向新窗口实体插入SyncToRenderWorld,并用一次run_system_once补漏——因为主窗口在该插件构建前就已存在(L36-L50)。
Surface 的创建与每帧更新流程
Render World 中还有两个系统消费ExtractedWindow:
create_surfaces(L353-L455),仅在need_surface_configuration为真时运行(即存在尚未配置、或size_changed/present_mode_changed的窗口):
- 首次运行时,用实体上的
RawHandleWrapper构建SurfaceTargetUnsafe::RawHandle,经create_surface_unsafe创建 surface,并偏好 sRGB 格式(Rgba8UnormSrgb/Bgra8UnormSrgb,否则回退到第一个可用格式);desired_maximum_frame_latency缺省时使用常量DEFAULT_DESIRED_MAXIMUM_FRAME_LATENCY = 2(L346-L350,注释说明了 1 会因 CPU 等待 GPU 而可能降低帧率);随后把SurfaceData插入窗口实体; - 尺寸或 present mode 变化时,丢弃旧交换链纹理(避免 wgpu 校验错误),更新
configuration的宽高并重新configure_surface; - 在 macOS/iOS 上通过
NonSendMarker被调度器约束到主线程,因为部分系统要求 surface 创建必须在主线程。
prepare_windows(L243-L335),每个 Render 帧在RenderSystems::PrepareViews中运行,负责获取交换链纹理:
- 跳过没有相机以该窗口为目标的窗口(除非仍需初始 present),避免无谓的 clear pass;
- 若已有交换链纹理且尺寸、present mode 均未变化,则继续复用;
- 按
surface.get_current_texture()的结果分支处理:成功/次优时调用set_swapchain_texture更新swap_chain_texture*系列字段;Outdated时重新configure_surface并重取;Occluded与超时(Linux mesa 的已知驱动怪癖)按注释中的条件安全忽略或告警。
present mode 的协商由 present_mode() 完成:按AutoVsync/AutoNoVsync/Mailbox等预设回退序列挑选 surface 实际支持的wgpu::PresentMode,兜底必为Fifo。
渲染完成后的提交发生在主渲染系统 render_system 中:它对每个带ViewTarget的视图检查是否需要 present,满足条件(或needs_initial_present)时调用window.present(&render_queue)并清零标志。
迁移操作:逐条替换
结合上述源码,迁移时可以按以下对照执行:
1. 读取提取的窗口数据
// 旧:资源查询(已移除) fn my_system(windows: Res<ExtractedWindows>) { let (w, h) = (windows.physical_width, windows.physical_height); } // 新:组件查询 fn my_system(windows: Query<(MainEntity, &ExtractedWindow)>) { for (main_entity, window) in &windows { let (w, h) = (window.physical_width, window.physical_height); } }2. 读取 surface 数据
// 旧:Res<ExtractedWindowSurfaces> // 新: fn my_system(surfaces: Query<&SurfaceData>) { /* ... */ }3. 获取主窗口
// 旧:ExtractedWindows::primary(Option<Entity> 字段) // 新: fn my_system(primary: Query<&ExtractedWindow, With<PrimaryWindow>>) { for window in &primary { /* ... */ } }4. 判断主窗口实体:旧代码若用windows.primary == Some(entity)比较,现在等价写法是对 Render World 实体查询Has<PrimaryWindow>。
仓库内既有的用法参考
当前代码库中已有若干按新模式访问窗口数据的示例,可以直接参照:
- bevy_render/src/camera.rs:
extracted_swap_chains: Query<(MainEntity, &ExtractedWindow)>,用于解析相机交换链格式; - bevy_render/src/view/mod.rs:视图准备阶段查询
(MainEntity, &ExtractedWindow); - bevy_render/src/view/window/screenshot.rs:截图系统通过
(MainEntity, &'s ExtractedWindow)找到目标窗口的交换链; - bevy_core_pipeline/src/schedule.rs:core pipeline 每帧以
SystemState<Query<(MainEntity, &ExtractedWindow)>>维护窗口状态。
这些查询普遍搭配MainEntity使用——即把 Render World 实体映射回 Main World 窗口实体,这也是替换旧windows.by_entity(entity)风格 API 时的标准做法。
小结
本次迁移的本质是把 Render World 的窗口数据从“资源容器”重构为“实体上的组件”:ExtractedWindow承载尺寸、present mode、交换链纹理等每帧数据,SurfaceData承载 wgpu surface 与配置,PrimaryWindow标签组件取代primary字段。替换工作集中在资源句柄改组件查询这一处,且仓库内bevy_render、bevy_core_pipeline的实现提供了完整的查询范式;迁移完成后,你的自定义渲染逻辑即可像访问其他渲染组件一样访问窗口数据。
(参考:迁移文档、窗口渲染模块)
【免费下载链接】bevyA refreshingly simple>项目地址: https://gitcode.com/GitHub_Trending/be/bevy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考