@bornengine/engine 0.4.16 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -14
- package/native/android/Cargo.lock +577 -28
- package/native/android/Cargo.toml +2 -0
- package/native/android/src/lib.rs +39 -3
- package/native/ios/Cargo.lock +573 -24
- package/native/ios/Cargo.toml +2 -0
- package/native/ios/src/lib.rs +72 -9
- package/native/ios/src/text_input.rs +48 -0
- package/native/linux/Cargo.lock +623 -35
- package/native/linux/Cargo.toml +3 -0
- package/native/macos/Cargo.lock +630 -35
- package/native/macos/Cargo.toml +3 -0
- package/native/shared/Cargo.lock +430 -27
- package/native/shared/Cargo.toml +5 -0
- package/native/shared/build.rs +41 -1
- package/native/shared/src/engine.rs +54 -2
- package/native/shared/src/ffi.rs +54 -1
- package/native/shared/src/ffi_core/mod.rs +2 -0
- package/native/shared/src/ffi_core/ui.rs +182 -0
- package/native/shared/src/input.rs +317 -12
- package/native/shared/src/lib.rs +1 -0
- package/native/shared/src/renderer/mod.rs +47 -201
- package/native/shared/src/renderer/texture_store.rs +5 -0
- package/native/shared/src/renderer/ui_pass.rs +742 -0
- package/native/shared/src/textures.rs +2 -0
- package/native/shared/src/ui/commands.rs +213 -0
- package/native/shared/src/ui/egui.rs +1282 -0
- package/native/shared/src/ui/imgui.rs +1056 -0
- package/native/shared/src/ui/input.rs +173 -0
- package/native/shared/src/ui/mod.rs +252 -0
- package/native/shared/src/ui/responses.rs +61 -0
- package/native/tvos/Cargo.lock +576 -27
- package/native/tvos/Cargo.toml +2 -0
- package/native/visionos/Cargo.lock +571 -22
- package/native/visionos/Cargo.toml +2 -0
- package/native/watchos/src/ffi_stubs.rs +27 -0
- package/native/web/Cargo.lock +346 -11
- package/native/web/Cargo.toml +6 -0
- package/native/web/bloom_glue.js +84 -11
- package/native/web/build.sh +150 -34
- package/native/web/src/lib.rs +2 -0
- package/native/web/src/ui_ffi.rs +148 -0
- package/native/windows/Cargo.lock +638 -38
- package/native/windows/Cargo.toml +3 -0
- package/native/windows/src/lib.rs +50 -1
- package/native/windows/src/windows_text.rs +69 -0
- package/package.json +91 -2
- package/src/debug-ui/index.ts +4 -0
- package/src/debug-ui/types.ts +1 -0
- package/src/game/adapters/audio-source-component.ts +67 -0
- package/src/game/adapters/rigid-body-component.ts +69 -0
- package/src/game/adapters/scene-node-component.ts +49 -0
- package/src/game/game-component.ts +89 -0
- package/src/game/game-object.ts +338 -0
- package/src/game/game-scene.ts +303 -0
- package/src/game/index.ts +7 -0
- package/src/game/transform.ts +315 -0
- package/src/index.ts +27 -0
- package/src/ui/api.ts +216 -0
- package/src/ui/index.ts +5 -0
- package/src/ui/opcodes.ts +66 -0
- package/src/ui/types.ts +82 -0
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
use std::collections::HashMap;
|
|
2
|
+
|
|
3
|
+
use super::*;
|
|
4
|
+
|
|
5
|
+
pub(super) struct UiRendererState {
|
|
6
|
+
egui_renderer: egui_wgpu::Renderer,
|
|
7
|
+
pending_egui_frame: Option<(crate::ui::EguiFrameOutput, HashMap<u64, u32>)>,
|
|
8
|
+
egui_texture_ids: HashMap<u64, (u32, egui::TextureId)>,
|
|
9
|
+
egui_pending_frees: Vec<egui::TextureId>,
|
|
10
|
+
#[cfg(feature = "debug-ui")]
|
|
11
|
+
dear_imgui_renderer: Option<imgui_wgpu::Renderer>,
|
|
12
|
+
#[cfg(feature = "debug-ui")]
|
|
13
|
+
dear_imgui_textures: HashMap<u64, (u32, imgui::TextureId)>,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl UiRendererState {
|
|
17
|
+
pub(super) fn new(device: &wgpu::Device, output_format: wgpu::TextureFormat) -> Self {
|
|
18
|
+
Self {
|
|
19
|
+
egui_renderer: egui_wgpu::Renderer::new(
|
|
20
|
+
device,
|
|
21
|
+
output_format,
|
|
22
|
+
egui_wgpu::RendererOptions::default(),
|
|
23
|
+
),
|
|
24
|
+
pending_egui_frame: None,
|
|
25
|
+
egui_texture_ids: HashMap::new(),
|
|
26
|
+
egui_pending_frees: Vec::new(),
|
|
27
|
+
#[cfg(feature = "debug-ui")]
|
|
28
|
+
dear_imgui_renderer: None,
|
|
29
|
+
#[cfg(feature = "debug-ui")]
|
|
30
|
+
dear_imgui_textures: HashMap::new(),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn should_render_ui(is_render_texture: bool) -> bool {
|
|
36
|
+
!is_render_texture
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[cfg(feature = "debug-ui")]
|
|
40
|
+
fn imgui_texture_from_native(
|
|
41
|
+
device: &wgpu::Device,
|
|
42
|
+
renderer: &imgui_wgpu::Renderer,
|
|
43
|
+
texture: &wgpu::Texture,
|
|
44
|
+
width: u32,
|
|
45
|
+
height: u32,
|
|
46
|
+
) -> imgui_wgpu::Texture {
|
|
47
|
+
use std::sync::Arc;
|
|
48
|
+
|
|
49
|
+
let texture = Arc::new(texture.clone());
|
|
50
|
+
let view = Arc::new(texture.create_view(&wgpu::TextureViewDescriptor::default()));
|
|
51
|
+
let sampler_desc = wgpu::SamplerDescriptor {
|
|
52
|
+
label: Some("bornengine_imgui_texture_sampler"),
|
|
53
|
+
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
|
54
|
+
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
|
55
|
+
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
|
56
|
+
mag_filter: wgpu::FilterMode::Linear,
|
|
57
|
+
min_filter: wgpu::FilterMode::Linear,
|
|
58
|
+
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
|
59
|
+
..Default::default()
|
|
60
|
+
};
|
|
61
|
+
let config = imgui_wgpu::RawTextureConfig {
|
|
62
|
+
label: Some("bornengine_imgui_native_texture"),
|
|
63
|
+
sampler_desc,
|
|
64
|
+
};
|
|
65
|
+
imgui_wgpu::Texture::from_raw_parts(
|
|
66
|
+
device,
|
|
67
|
+
renderer,
|
|
68
|
+
texture,
|
|
69
|
+
view,
|
|
70
|
+
None,
|
|
71
|
+
Some(&config),
|
|
72
|
+
wgpu::Extent3d {
|
|
73
|
+
width,
|
|
74
|
+
height,
|
|
75
|
+
depth_or_array_layers: 1,
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl Renderer {
|
|
81
|
+
pub fn set_egui_frame(
|
|
82
|
+
&mut self,
|
|
83
|
+
frame: crate::ui::EguiFrameOutput,
|
|
84
|
+
native_texture_indices: HashMap<u64, u32>,
|
|
85
|
+
) {
|
|
86
|
+
self.ui_renderer.pending_egui_frame = Some((frame, native_texture_indices));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[cfg(feature = "debug-ui")]
|
|
90
|
+
pub(crate) fn init_dear_imgui(&mut self) {
|
|
91
|
+
// Headless renderers never submit the Dear ImGui surface pass. Avoid
|
|
92
|
+
// creating a process-global ImGui context for offscreen renderers,
|
|
93
|
+
// which can be constructed concurrently by tests or server tools.
|
|
94
|
+
if self.surface.is_none() {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
crate::ui::with_dear_imgui(|ui| {
|
|
98
|
+
self.ui_renderer.dear_imgui_renderer = Some(imgui_wgpu::Renderer::new(
|
|
99
|
+
ui.context_mut(),
|
|
100
|
+
&self.device,
|
|
101
|
+
&self.queue,
|
|
102
|
+
imgui_wgpu::RendererConfig {
|
|
103
|
+
texture_format: self.output_format,
|
|
104
|
+
..Default::default()
|
|
105
|
+
},
|
|
106
|
+
));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
pub fn unload_egui_texture(&mut self, engine_handle: u64) {
|
|
111
|
+
if let Some((_, texture_id)) = self.ui_renderer.egui_texture_ids.remove(&engine_handle) {
|
|
112
|
+
self.ui_renderer.egui_renderer.free_texture(&texture_id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pub(crate) fn refresh_egui_texture_index(&mut self, texture_index: u32) {
|
|
117
|
+
let affected: Vec<_> = self
|
|
118
|
+
.ui_renderer
|
|
119
|
+
.egui_texture_ids
|
|
120
|
+
.iter()
|
|
121
|
+
.filter_map(|(&handle, &(index, texture_id))| {
|
|
122
|
+
(index == texture_index).then_some((handle, texture_id))
|
|
123
|
+
})
|
|
124
|
+
.collect();
|
|
125
|
+
if affected.is_empty() {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
let index = texture_index as usize;
|
|
129
|
+
let Some(texture) = self.textures.get(index) else {
|
|
130
|
+
return;
|
|
131
|
+
};
|
|
132
|
+
let size = self.texture_sizes.get(index).copied().unwrap_or_default();
|
|
133
|
+
for (handle, texture_id) in affected {
|
|
134
|
+
if size.0 == 0 || size.1 == 0 {
|
|
135
|
+
self.ui_renderer.egui_renderer.free_texture(&texture_id);
|
|
136
|
+
self.ui_renderer.egui_texture_ids.remove(&handle);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
140
|
+
self.ui_renderer
|
|
141
|
+
.egui_renderer
|
|
142
|
+
.update_egui_texture_from_wgpu_texture(
|
|
143
|
+
&self.device,
|
|
144
|
+
&view,
|
|
145
|
+
wgpu::FilterMode::Linear,
|
|
146
|
+
texture_id,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn unload_imgui_texture(&mut self, engine_handle: u64) {
|
|
152
|
+
#[cfg(feature = "debug-ui")]
|
|
153
|
+
if let (Some(renderer), Some((_, texture_id))) = (
|
|
154
|
+
self.ui_renderer.dear_imgui_renderer.as_mut(),
|
|
155
|
+
self.ui_renderer.dear_imgui_textures.remove(&engine_handle),
|
|
156
|
+
) {
|
|
157
|
+
renderer.textures.remove(texture_id);
|
|
158
|
+
}
|
|
159
|
+
#[cfg(not(feature = "debug-ui"))]
|
|
160
|
+
let _ = engine_handle;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
pub(super) fn encode_egui_pass(
|
|
164
|
+
&mut self,
|
|
165
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
166
|
+
target: &wgpu::TextureView,
|
|
167
|
+
is_render_texture: bool,
|
|
168
|
+
) -> Vec<wgpu::CommandBuffer> {
|
|
169
|
+
let Some((frame, native_texture_indices)) = self.ui_renderer.pending_egui_frame.take()
|
|
170
|
+
else {
|
|
171
|
+
return Vec::new();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
for (texture_id, delta) in &frame.textures_delta.set {
|
|
175
|
+
self.ui_renderer.egui_renderer.update_texture(
|
|
176
|
+
&self.device,
|
|
177
|
+
&self.queue,
|
|
178
|
+
*texture_id,
|
|
179
|
+
delta,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (engine_handle, texture_index) in native_texture_indices {
|
|
184
|
+
let Some(texture) = self.textures.get(texture_index as usize) else {
|
|
185
|
+
continue;
|
|
186
|
+
};
|
|
187
|
+
let Some(size) = self.texture_sizes.get(texture_index as usize) else {
|
|
188
|
+
continue;
|
|
189
|
+
};
|
|
190
|
+
if size.0 == 0 || size.1 == 0 {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let Some(existing) = self
|
|
195
|
+
.ui_renderer
|
|
196
|
+
.egui_texture_ids
|
|
197
|
+
.get(&engine_handle)
|
|
198
|
+
.copied()
|
|
199
|
+
else {
|
|
200
|
+
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
201
|
+
let texture_id = self.ui_renderer.egui_renderer.register_native_texture(
|
|
202
|
+
&self.device,
|
|
203
|
+
&view,
|
|
204
|
+
wgpu::FilterMode::Linear,
|
|
205
|
+
);
|
|
206
|
+
self.ui_renderer
|
|
207
|
+
.egui_texture_ids
|
|
208
|
+
.insert(engine_handle, (texture_index, texture_id));
|
|
209
|
+
continue;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if existing.0 != texture_index {
|
|
213
|
+
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
|
214
|
+
self.ui_renderer
|
|
215
|
+
.egui_renderer
|
|
216
|
+
.update_egui_texture_from_wgpu_texture(
|
|
217
|
+
&self.device,
|
|
218
|
+
&view,
|
|
219
|
+
wgpu::FilterMode::Linear,
|
|
220
|
+
existing.1,
|
|
221
|
+
);
|
|
222
|
+
self.ui_renderer
|
|
223
|
+
.egui_texture_ids
|
|
224
|
+
.insert(engine_handle, (texture_index, existing.1));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let can_paint = should_render_ui(is_render_texture) && self.surface.is_some();
|
|
229
|
+
let mut user_command_buffers = Vec::new();
|
|
230
|
+
if can_paint {
|
|
231
|
+
let mut paint_jobs = frame.paint_jobs;
|
|
232
|
+
for clipped in &mut paint_jobs {
|
|
233
|
+
if let egui::epaint::Primitive::Mesh(mesh) = &mut clipped.primitive {
|
|
234
|
+
if let egui::TextureId::User(engine_handle) = mesh.texture_id {
|
|
235
|
+
if let Some((_, texture_id)) =
|
|
236
|
+
self.ui_renderer.egui_texture_ids.get(&engine_handle)
|
|
237
|
+
{
|
|
238
|
+
mesh.texture_id = *texture_id;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let pixels_per_point = frame.pixels_per_point.max(0.1);
|
|
245
|
+
let screen_descriptor = egui_wgpu::ScreenDescriptor {
|
|
246
|
+
size_in_pixels: [self.surface_config.width, self.surface_config.height],
|
|
247
|
+
pixels_per_point,
|
|
248
|
+
};
|
|
249
|
+
user_command_buffers = self.ui_renderer.egui_renderer.update_buffers(
|
|
250
|
+
&self.device,
|
|
251
|
+
&self.queue,
|
|
252
|
+
encoder,
|
|
253
|
+
&paint_jobs,
|
|
254
|
+
&screen_descriptor,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
if !paint_jobs.is_empty() {
|
|
258
|
+
let pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
259
|
+
label: Some("egui_ui_pass"),
|
|
260
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
261
|
+
view: target,
|
|
262
|
+
resolve_target: None,
|
|
263
|
+
depth_slice: None,
|
|
264
|
+
ops: wgpu::Operations {
|
|
265
|
+
load: wgpu::LoadOp::Load,
|
|
266
|
+
store: wgpu::StoreOp::Store,
|
|
267
|
+
},
|
|
268
|
+
})],
|
|
269
|
+
depth_stencil_attachment: None,
|
|
270
|
+
timestamp_writes: None,
|
|
271
|
+
occlusion_query_set: None,
|
|
272
|
+
multiview_mask: None,
|
|
273
|
+
});
|
|
274
|
+
self.ui_renderer.egui_renderer.render(
|
|
275
|
+
&mut pass.forget_lifetime(),
|
|
276
|
+
&paint_jobs,
|
|
277
|
+
&screen_descriptor,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
self.ui_renderer
|
|
283
|
+
.egui_pending_frees
|
|
284
|
+
.extend(frame.textures_delta.free.iter().copied());
|
|
285
|
+
user_command_buffers
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
pub(super) fn free_pending_egui_textures(&mut self) {
|
|
289
|
+
for texture_id in self.ui_renderer.egui_pending_frees.drain(..) {
|
|
290
|
+
self.ui_renderer.egui_renderer.free_texture(&texture_id);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#[cfg(feature = "debug-ui")]
|
|
295
|
+
fn sync_dear_imgui_textures(
|
|
296
|
+
&mut self,
|
|
297
|
+
native_texture_indices: &HashMap<u64, u32>,
|
|
298
|
+
) -> HashMap<u64, (imgui::TextureId, [f32; 2])> {
|
|
299
|
+
let mut texture_ids = HashMap::new();
|
|
300
|
+
let Some(renderer) = self.ui_renderer.dear_imgui_renderer.as_mut() else {
|
|
301
|
+
return texture_ids;
|
|
302
|
+
};
|
|
303
|
+
for (&engine_handle, &texture_index) in native_texture_indices {
|
|
304
|
+
let index = texture_index as usize;
|
|
305
|
+
let Some(texture) = self.textures.get(index) else {
|
|
306
|
+
continue;
|
|
307
|
+
};
|
|
308
|
+
let Some(&(width, height)) = self.texture_sizes.get(index) else {
|
|
309
|
+
continue;
|
|
310
|
+
};
|
|
311
|
+
if width == 0 || height == 0 {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if let Some((cached_index, texture_id)) = self
|
|
315
|
+
.ui_renderer
|
|
316
|
+
.dear_imgui_textures
|
|
317
|
+
.get(&engine_handle)
|
|
318
|
+
.copied()
|
|
319
|
+
{
|
|
320
|
+
if cached_index == texture_index {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
renderer.textures.remove(texture_id);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let imgui_texture =
|
|
327
|
+
imgui_texture_from_native(&self.device, renderer, texture, width, height);
|
|
328
|
+
let texture_id = renderer.textures.insert(imgui_texture);
|
|
329
|
+
self.ui_renderer
|
|
330
|
+
.dear_imgui_textures
|
|
331
|
+
.insert(engine_handle, (texture_index, texture_id));
|
|
332
|
+
}
|
|
333
|
+
texture_ids.extend(self.ui_renderer.dear_imgui_textures.iter().filter_map(
|
|
334
|
+
|(&handle, &(index, texture_id))| {
|
|
335
|
+
let &(width, height) = self.texture_sizes.get(index as usize)?;
|
|
336
|
+
(width > 0 && height > 0)
|
|
337
|
+
.then_some((handle, (texture_id, [width as f32, height as f32])))
|
|
338
|
+
},
|
|
339
|
+
));
|
|
340
|
+
texture_ids
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[cfg(feature = "debug-ui")]
|
|
344
|
+
pub(crate) fn refresh_dear_imgui_texture_index(&mut self, texture_index: u32) {
|
|
345
|
+
let Some(renderer) = self.ui_renderer.dear_imgui_renderer.as_mut() else {
|
|
346
|
+
return;
|
|
347
|
+
};
|
|
348
|
+
let affected: Vec<_> = self
|
|
349
|
+
.ui_renderer
|
|
350
|
+
.dear_imgui_textures
|
|
351
|
+
.iter()
|
|
352
|
+
.filter_map(|(&handle, &(index, texture_id))| {
|
|
353
|
+
(index == texture_index).then_some((handle, texture_id))
|
|
354
|
+
})
|
|
355
|
+
.collect();
|
|
356
|
+
let index = texture_index as usize;
|
|
357
|
+
let Some(texture) = self.textures.get(index) else {
|
|
358
|
+
return;
|
|
359
|
+
};
|
|
360
|
+
let size = self.texture_sizes.get(index).copied().unwrap_or_default();
|
|
361
|
+
for (handle, texture_id) in affected {
|
|
362
|
+
if size.0 == 0 || size.1 == 0 {
|
|
363
|
+
renderer.textures.remove(texture_id);
|
|
364
|
+
self.ui_renderer.dear_imgui_textures.remove(&handle);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
let imgui_texture =
|
|
368
|
+
imgui_texture_from_native(&self.device, renderer, texture, size.0, size.1);
|
|
369
|
+
renderer.textures.replace(texture_id, imgui_texture);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#[cfg(feature = "debug-ui")]
|
|
374
|
+
pub(super) fn encode_dear_imgui_pass(
|
|
375
|
+
&mut self,
|
|
376
|
+
encoder: &mut wgpu::CommandEncoder,
|
|
377
|
+
target: &wgpu::TextureView,
|
|
378
|
+
is_render_texture: bool,
|
|
379
|
+
ui_system: &mut crate::ui::UiSystem,
|
|
380
|
+
dt: f64,
|
|
381
|
+
native_texture_indices: &HashMap<u64, u32>,
|
|
382
|
+
) {
|
|
383
|
+
if is_render_texture || self.surface.is_none() {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if self.ui_renderer.dear_imgui_renderer.is_none() {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let commands = ui_system.take_commands(crate::ui::UiBackend::DearImGui);
|
|
391
|
+
let texture_ids = self.sync_dear_imgui_textures(native_texture_indices);
|
|
392
|
+
let input = ui_system.input_snapshot().clone();
|
|
393
|
+
let display_size = [self.width().max(1) as f32, self.height().max(1) as f32];
|
|
394
|
+
let framebuffer_scale = self.physical_width() as f32 / display_size[0];
|
|
395
|
+
let (responses, capture) = crate::ui::with_dear_imgui(|ui| {
|
|
396
|
+
if ui.register_fonts(&commands) {
|
|
397
|
+
if let Some(renderer) = self.ui_renderer.dear_imgui_renderer.as_mut() {
|
|
398
|
+
renderer.reload_font_texture(ui.context_mut(), &self.device, &self.queue);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
let draw_data = ui.prepare_frame(
|
|
402
|
+
&commands,
|
|
403
|
+
&input,
|
|
404
|
+
display_size,
|
|
405
|
+
framebuffer_scale,
|
|
406
|
+
dt,
|
|
407
|
+
&texture_ids,
|
|
408
|
+
);
|
|
409
|
+
if draw_data.total_vtx_count > 0 {
|
|
410
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
411
|
+
label: Some("dear_imgui_debug_ui_pass"),
|
|
412
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
413
|
+
view: target,
|
|
414
|
+
resolve_target: None,
|
|
415
|
+
depth_slice: None,
|
|
416
|
+
ops: wgpu::Operations {
|
|
417
|
+
load: wgpu::LoadOp::Load,
|
|
418
|
+
store: wgpu::StoreOp::Store,
|
|
419
|
+
},
|
|
420
|
+
})],
|
|
421
|
+
depth_stencil_attachment: None,
|
|
422
|
+
timestamp_writes: None,
|
|
423
|
+
occlusion_query_set: None,
|
|
424
|
+
multiview_mask: None,
|
|
425
|
+
});
|
|
426
|
+
if let Some(renderer) = self.ui_renderer.dear_imgui_renderer.as_mut() {
|
|
427
|
+
if let Err(error) =
|
|
428
|
+
renderer.render(draw_data, &self.queue, &self.device, &mut pass)
|
|
429
|
+
{
|
|
430
|
+
eprintln!("bloom: Dear ImGui rendering failed: {error}");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
ui.take_frame_readback()
|
|
435
|
+
});
|
|
436
|
+
ui_system.publish_completed_responses(crate::ui::UiBackend::DearImGui, responses);
|
|
437
|
+
ui_system.set_capture(crate::ui::UiBackend::DearImGui, capture);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#[cfg(test)]
|
|
442
|
+
mod tests {
|
|
443
|
+
#[test]
|
|
444
|
+
fn ui_paint_is_skipped_for_render_texture_frames() {
|
|
445
|
+
assert!(super::should_render_ui(false));
|
|
446
|
+
assert!(!super::should_render_ui(true));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
impl Renderer {
|
|
451
|
+
pub fn end_frame(&mut self) {
|
|
452
|
+
self.end_frame_internal(None, 1.0 / 60.0, HashMap::new());
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
pub fn end_frame_with_ui(
|
|
456
|
+
&mut self,
|
|
457
|
+
ui: &mut crate::ui::UiSystem,
|
|
458
|
+
dt: f64,
|
|
459
|
+
native_texture_indices: HashMap<u64, u32>,
|
|
460
|
+
) {
|
|
461
|
+
self.end_frame_internal(Some(ui), dt, native_texture_indices);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
#[allow(unused_variables)]
|
|
465
|
+
fn end_frame_internal(
|
|
466
|
+
&mut self,
|
|
467
|
+
ui: Option<&mut crate::ui::UiSystem>,
|
|
468
|
+
dt: f64,
|
|
469
|
+
native_texture_indices: HashMap<u64, u32>,
|
|
470
|
+
) {
|
|
471
|
+
if !self.material_per_view_bg_live {
|
|
472
|
+
self.refresh_material_per_view_bg();
|
|
473
|
+
}
|
|
474
|
+
// Flush pending joint matrices to GPU right before rendering
|
|
475
|
+
self.flush_joint_matrices();
|
|
476
|
+
// One pooled upload for every cached-model draw's uniforms.
|
|
477
|
+
self.flush_model_uniforms();
|
|
478
|
+
|
|
479
|
+
// Q1: If rendering to a texture, use the RT view. Otherwise use the surface.
|
|
480
|
+
// We take ownership of the RT views (via Option::take) to avoid holding a
|
|
481
|
+
// borrow on `self` while the rest of end_frame mutates it.
|
|
482
|
+
let rt_color = self.rt_color_view.take();
|
|
483
|
+
let rt_depth = self.rt_depth_view.take();
|
|
484
|
+
let using_rt = rt_color.is_some();
|
|
485
|
+
|
|
486
|
+
let surface_output = if using_rt {
|
|
487
|
+
None
|
|
488
|
+
} else {
|
|
489
|
+
match self.acquire_frame() {
|
|
490
|
+
Some(t) => Some(t),
|
|
491
|
+
None => {
|
|
492
|
+
// Swapchain lost+reconfigured. Restore RT views if set.
|
|
493
|
+
self.rt_color_view = rt_color;
|
|
494
|
+
self.rt_depth_view = rt_depth;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
let view: wgpu::TextureView;
|
|
501
|
+
let owned_depth_view: wgpu::TextureView;
|
|
502
|
+
|
|
503
|
+
if let Some(ref rt_view) = rt_color {
|
|
504
|
+
view = rt_view.clone();
|
|
505
|
+
owned_depth_view = rt_depth.as_ref().unwrap().clone();
|
|
506
|
+
} else {
|
|
507
|
+
view = self
|
|
508
|
+
.frame_texture(surface_output.as_ref().unwrap())
|
|
509
|
+
.create_view(&wgpu::TextureViewDescriptor {
|
|
510
|
+
format: Some(self.output_format),
|
|
511
|
+
..Default::default()
|
|
512
|
+
});
|
|
513
|
+
owned_depth_view = self
|
|
514
|
+
.depth_texture
|
|
515
|
+
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Restore RT views so they persist across frames.
|
|
519
|
+
self.rt_color_view = rt_color;
|
|
520
|
+
self.rt_depth_view = rt_depth;
|
|
521
|
+
|
|
522
|
+
let mut encoder = self
|
|
523
|
+
.device
|
|
524
|
+
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
525
|
+
label: Some("bloom_encoder"),
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Upload 2D data to persistent GPU buffers
|
|
529
|
+
let has_2d = !self.vertices_2d.is_empty();
|
|
530
|
+
if has_2d {
|
|
531
|
+
let vb_size = std::mem::size_of_val(self.vertices_2d.as_slice());
|
|
532
|
+
let ib_size = std::mem::size_of_val(self.indices_2d.as_slice());
|
|
533
|
+
self.ensure_buffer_capacity_2d(vb_size, ib_size);
|
|
534
|
+
self.queue.write_buffer(
|
|
535
|
+
&self.persistent_vb_2d,
|
|
536
|
+
0,
|
|
537
|
+
bytemuck::cast_slice(&self.vertices_2d),
|
|
538
|
+
);
|
|
539
|
+
self.queue.write_buffer(
|
|
540
|
+
&self.persistent_ib_2d,
|
|
541
|
+
0,
|
|
542
|
+
bytemuck::cast_slice(&self.indices_2d),
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Upload 3D data to persistent GPU buffers
|
|
547
|
+
let has_3d = !self.vertices_3d.is_empty();
|
|
548
|
+
if has_3d {
|
|
549
|
+
let vb_size = std::mem::size_of_val(self.vertices_3d.as_slice());
|
|
550
|
+
let ib_size = std::mem::size_of_val(self.indices_3d.as_slice());
|
|
551
|
+
self.ensure_buffer_capacity_3d(vb_size, ib_size);
|
|
552
|
+
self.queue.write_buffer(
|
|
553
|
+
&self.persistent_vb_3d,
|
|
554
|
+
0,
|
|
555
|
+
bytemuck::cast_slice(&self.vertices_3d),
|
|
556
|
+
);
|
|
557
|
+
self.queue.write_buffer(
|
|
558
|
+
&self.persistent_ib_3d,
|
|
559
|
+
0,
|
|
560
|
+
bytemuck::cast_slice(&self.indices_3d),
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
{
|
|
565
|
+
// Only attach a depth target when we're drawing 3D. pipeline_2d is
|
|
566
|
+
// depth-less; on some mobile Vulkan drivers (Adreno) pairing a
|
|
567
|
+
// depth-less pipeline with a pass that carries a depth attachment
|
|
568
|
+
// discards all draws silently. Matches the overlay_2d pass in
|
|
569
|
+
// end_frame_with_scene, which also omits depth.
|
|
570
|
+
let depth_attachment = if has_3d {
|
|
571
|
+
Some(wgpu::RenderPassDepthStencilAttachment {
|
|
572
|
+
view: &owned_depth_view,
|
|
573
|
+
depth_ops: Some(wgpu::Operations {
|
|
574
|
+
load: wgpu::LoadOp::Clear(1.0),
|
|
575
|
+
store: wgpu::StoreOp::Store,
|
|
576
|
+
}),
|
|
577
|
+
stencil_ops: None,
|
|
578
|
+
})
|
|
579
|
+
} else {
|
|
580
|
+
None
|
|
581
|
+
};
|
|
582
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
583
|
+
label: Some("bloom_pass"),
|
|
584
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
585
|
+
view: &view,
|
|
586
|
+
resolve_target: None,
|
|
587
|
+
depth_slice: None,
|
|
588
|
+
ops: wgpu::Operations {
|
|
589
|
+
load: wgpu::LoadOp::Clear(self.clear_color),
|
|
590
|
+
store: wgpu::StoreOp::Store,
|
|
591
|
+
},
|
|
592
|
+
})],
|
|
593
|
+
depth_stencil_attachment: depth_attachment,
|
|
594
|
+
timestamp_writes: None,
|
|
595
|
+
occlusion_query_set: None,
|
|
596
|
+
multiview_mask: None,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// Draw 3D geometry first (with depth testing), batched by texture
|
|
600
|
+
if has_3d {
|
|
601
|
+
pass.set_pipeline(&self.pipeline_3d);
|
|
602
|
+
pass.set_bind_group(0, &self.uniform_bind_group_3d, &[]);
|
|
603
|
+
pass.set_bind_group(1, &self.lighting_bind_group, &[]);
|
|
604
|
+
pass.set_bind_group(3, &self.joint_bind_group, &[]);
|
|
605
|
+
pass.set_vertex_buffer(0, self.persistent_vb_3d.slice(..));
|
|
606
|
+
pass.set_index_buffer(self.persistent_ib_3d.slice(..), wgpu::IndexFormat::Uint32);
|
|
607
|
+
|
|
608
|
+
if self.draw_calls_3d.is_empty() {
|
|
609
|
+
// No draw calls tracked — draw all with white texture (backward compat)
|
|
610
|
+
pass.set_bind_group(2, &self.texture_bind_groups[0], &[]);
|
|
611
|
+
pass.draw_indexed(0..self.indices_3d.len() as u32, 0, 0..1);
|
|
612
|
+
} else {
|
|
613
|
+
let num_calls = self.draw_calls_3d.len();
|
|
614
|
+
for i in 0..num_calls {
|
|
615
|
+
let call = &self.draw_calls_3d[i];
|
|
616
|
+
let next_start = if i + 1 < num_calls {
|
|
617
|
+
self.draw_calls_3d[i + 1].index_start
|
|
618
|
+
} else {
|
|
619
|
+
self.indices_3d.len() as u32
|
|
620
|
+
};
|
|
621
|
+
let count = next_start - call.index_start;
|
|
622
|
+
if count == 0 {
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
let tex_idx = call.texture_idx as usize;
|
|
626
|
+
if tex_idx < self.texture_bind_groups.len() {
|
|
627
|
+
pass.set_bind_group(2, &self.texture_bind_groups[tex_idx], &[]);
|
|
628
|
+
} else {
|
|
629
|
+
pass.set_bind_group(2, &self.texture_bind_groups[0], &[]);
|
|
630
|
+
}
|
|
631
|
+
pass.draw_indexed(call.index_start..next_start, 0, 0..1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Draw cached models (static models with GPU-resident buffers).
|
|
637
|
+
// Use the scene pipeline so PBR-style material bindings (base
|
|
638
|
+
// color + normal map) apply — drawModel should behave the same
|
|
639
|
+
// as attachModelToNode for PBR purposes.
|
|
640
|
+
if !self.model_draw_commands.is_empty() {
|
|
641
|
+
pass.set_pipeline(&self.scene_pipeline);
|
|
642
|
+
pass.set_bind_group(1, &self.lighting_bind_group, &[]);
|
|
643
|
+
pass.set_bind_group(3, &self.joint_bind_group, &[]);
|
|
644
|
+
|
|
645
|
+
for cmd in &self.model_draw_commands {
|
|
646
|
+
if let Some(Some(meshes)) = self.model_gpu_cache.get(&cmd.cache_handle) {
|
|
647
|
+
if cmd.mesh_idx < meshes.len() {
|
|
648
|
+
let mesh = &meshes[cmd.mesh_idx];
|
|
649
|
+
pass.set_bind_group(
|
|
650
|
+
0,
|
|
651
|
+
&self.model_uniform_bind_groups[cmd.uniform_slot],
|
|
652
|
+
&[],
|
|
653
|
+
);
|
|
654
|
+
pass.set_bind_group(2, &mesh.material_bg, &[]);
|
|
655
|
+
pass.set_vertex_buffer(0, mesh.vb.slice(..));
|
|
656
|
+
pass.set_index_buffer(mesh.ib.slice(..), wgpu::IndexFormat::Uint32);
|
|
657
|
+
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Draw 2D geometry (no depth testing, always passes)
|
|
664
|
+
if has_2d {
|
|
665
|
+
pass.set_pipeline(&self.pipeline_2d);
|
|
666
|
+
pass.set_vertex_buffer(0, self.persistent_vb_2d.slice(..));
|
|
667
|
+
pass.set_index_buffer(self.persistent_ib_2d.slice(..), wgpu::IndexFormat::Uint32);
|
|
668
|
+
|
|
669
|
+
let num_calls = self.draw_calls_2d.len();
|
|
670
|
+
for i in 0..num_calls {
|
|
671
|
+
let call = &self.draw_calls_2d[i];
|
|
672
|
+
let next_start = if i + 1 < num_calls {
|
|
673
|
+
self.draw_calls_2d[i + 1].index_start
|
|
674
|
+
} else {
|
|
675
|
+
self.indices_2d.len() as u32
|
|
676
|
+
};
|
|
677
|
+
let count = next_start - call.index_start;
|
|
678
|
+
if count == 0 {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
pass.set_bind_group(
|
|
683
|
+
0,
|
|
684
|
+
&self.uniform_bind_groups[call.uniform_idx as usize],
|
|
685
|
+
&[],
|
|
686
|
+
);
|
|
687
|
+
if (call.texture_idx as usize) < self.texture_bind_groups.len() {
|
|
688
|
+
pass.set_bind_group(
|
|
689
|
+
1,
|
|
690
|
+
&self.texture_bind_groups[call.texture_idx as usize],
|
|
691
|
+
&[],
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
pass.draw_indexed(call.index_start..next_start, 0, 0..1);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
let ui_command_buffers =
|
|
700
|
+
self.encode_egui_pass(&mut encoder, &view, using_rt || self.surface.is_none());
|
|
701
|
+
#[cfg(feature = "debug-ui")]
|
|
702
|
+
if let Some(ui) = ui {
|
|
703
|
+
self.encode_dear_imgui_pass(
|
|
704
|
+
&mut encoder,
|
|
705
|
+
&view,
|
|
706
|
+
using_rt || self.surface.is_none(),
|
|
707
|
+
ui,
|
|
708
|
+
dt,
|
|
709
|
+
&native_texture_indices,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
self.queue.submit(
|
|
713
|
+
ui_command_buffers
|
|
714
|
+
.into_iter()
|
|
715
|
+
.chain(std::iter::once(encoder.finish())),
|
|
716
|
+
);
|
|
717
|
+
self.free_pending_egui_textures();
|
|
718
|
+
if let Some(out) = surface_output {
|
|
719
|
+
self.present_frame(out);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/// Like end_frame, but also renders retained scene graph nodes.
|
|
724
|
+
pub fn end_frame_with_scene(
|
|
725
|
+
&mut self,
|
|
726
|
+
scene: &mut crate::scene::SceneGraph,
|
|
727
|
+
profiler: &mut crate::profiler::Profiler,
|
|
728
|
+
) {
|
|
729
|
+
self.end_frame_with_scene_internal(scene, profiler, None, 1.0 / 60.0, HashMap::new());
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
pub fn end_frame_with_scene_ui(
|
|
733
|
+
&mut self,
|
|
734
|
+
scene: &mut crate::scene::SceneGraph,
|
|
735
|
+
profiler: &mut crate::profiler::Profiler,
|
|
736
|
+
ui: &mut crate::ui::UiSystem,
|
|
737
|
+
dt: f64,
|
|
738
|
+
native_texture_indices: HashMap<u64, u32>,
|
|
739
|
+
) {
|
|
740
|
+
self.end_frame_with_scene_internal(scene, profiler, Some(ui), dt, native_texture_indices);
|
|
741
|
+
}
|
|
742
|
+
}
|