@bornengine/engine 0.4.16

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.
Files changed (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,154 @@
1
+ //! Split-sum BRDF LUT generator.
2
+ //!
3
+ //! Pre-integrates the GGX BRDF into a 2D `Rg16Float` table indexed by
4
+ //! `(NdotV, roughness)` that the PBR shader samples at runtime via
5
+ //! the Karis-2013 split-sum approximation. Entirely CPU-side —
6
+ //! builds once at startup via `build_brdf_lut(size)` and hands the
7
+ //! result to `queue.write_texture` in `Renderer::new`.
8
+ //!
9
+ //! Private helpers (Hammersley, GGX importance sampling, Smith
10
+ //! geometry) stay module-scoped; only `build_brdf_lut` is `pub` so
11
+ //! `Renderer::new` can reach it.
12
+
13
+ // ============================================================
14
+ // Split-sum BRDF LUT
15
+ // ============================================================
16
+ //
17
+ // Pre-integrate the GGX BRDF over hemisphere directions. The output
18
+ // 2D table is sampled at runtime as `brdf_lut(NdotV, roughness)` and
19
+ // gives a (scale, bias) pair such that:
20
+ // IBL_specular = prefiltered_env_sample * (F0 * scale + bias)
21
+ // This is the second sum of the Karis 2013 split-sum approximation.
22
+ //
23
+ // Importance-samples GGX in the H direction, integrates the visibility
24
+ // × Fresnel(VdotH) part. The Fresnel split into scale (F0) and bias
25
+ // (1) lets us factor F0 out of the integral.
26
+
27
+ const BRDF_LUT_SAMPLES: u32 = 1024;
28
+
29
+ fn radical_inverse_vdc(mut bits: u32) -> f32 {
30
+ bits = (bits << 16) | (bits >> 16);
31
+ bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
32
+ bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
33
+ bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
34
+ bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
35
+ (bits as f32) * 2.328_306_4e-10
36
+ }
37
+
38
+ fn hammersley(i: u32, n: u32) -> (f32, f32) {
39
+ (i as f32 / n as f32, radical_inverse_vdc(i))
40
+ }
41
+
42
+ fn importance_sample_ggx(xi: (f32, f32), n: [f32; 3], roughness: f32) -> [f32; 3] {
43
+ let a = roughness * roughness;
44
+ let phi = 2.0 * std::f32::consts::PI * xi.0;
45
+ let cos_theta = ((1.0 - xi.1) / (1.0 + (a * a - 1.0) * xi.1)).sqrt();
46
+ let sin_theta = (1.0 - cos_theta * cos_theta).max(0.0).sqrt();
47
+ let h_local = [sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta];
48
+
49
+ // Build TBN around N.
50
+ let up = if n[2].abs() < 0.999 { [0.0, 0.0, 1.0] } else { [1.0, 0.0, 0.0] };
51
+ let t = normalize3(cross3(up, n));
52
+ let b = cross3(n, t);
53
+ [
54
+ t[0] * h_local[0] + b[0] * h_local[1] + n[0] * h_local[2],
55
+ t[1] * h_local[0] + b[1] * h_local[1] + n[1] * h_local[2],
56
+ t[2] * h_local[0] + b[2] * h_local[1] + n[2] * h_local[2],
57
+ ]
58
+ }
59
+
60
+ fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
61
+ [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
62
+ }
63
+
64
+ fn normalize3(v: [f32; 3]) -> [f32; 3] {
65
+ let l = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-8);
66
+ [v[0] / l, v[1] / l, v[2] / l]
67
+ }
68
+
69
+ fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
70
+ a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
71
+ }
72
+
73
+ fn geometry_smith_ggx_ibl(n_dot_v: f32, n_dot_l: f32, roughness: f32) -> f32 {
74
+ // IBL geometry uses k = (alpha²)/2 (Disney) — different from
75
+ // direct-lighting k. Returns G1(V) * G1(L).
76
+ let a = roughness;
77
+ let k = (a * a) / 2.0;
78
+ let g1v = n_dot_v / (n_dot_v * (1.0 - k) + k);
79
+ let g1l = n_dot_l / (n_dot_l * (1.0 - k) + k);
80
+ g1v * g1l
81
+ }
82
+
83
+ fn build_brdf_lut_row(y: usize, size: usize) -> Vec<u16> {
84
+ let n = [0.0_f32, 0.0, 1.0];
85
+ let roughness = ((y as f32) + 0.5) / size as f32;
86
+ let mut row = Vec::with_capacity(size * 2);
87
+ for x in 0..size {
88
+ let n_dot_v = ((x as f32) + 0.5) / size as f32;
89
+ let v = [
90
+ (1.0 - n_dot_v * n_dot_v).max(0.0).sqrt(),
91
+ 0.0,
92
+ n_dot_v,
93
+ ];
94
+ let mut a_sum = 0.0_f32;
95
+ let mut b_sum = 0.0_f32;
96
+ for i in 0..BRDF_LUT_SAMPLES {
97
+ let xi = hammersley(i, BRDF_LUT_SAMPLES);
98
+ let h = importance_sample_ggx(xi, n, roughness);
99
+ let v_dot_h = dot3(v, h).max(0.0);
100
+ let l = [
101
+ 2.0 * v_dot_h * h[0] - v[0],
102
+ 2.0 * v_dot_h * h[1] - v[1],
103
+ 2.0 * v_dot_h * h[2] - v[2],
104
+ ];
105
+ let n_dot_l = l[2].max(0.0);
106
+ let n_dot_h = h[2].max(0.0);
107
+ if n_dot_l > 0.0 {
108
+ let g = geometry_smith_ggx_ibl(n_dot_v, n_dot_l, roughness);
109
+ let g_vis = (g * v_dot_h) / (n_dot_h * n_dot_v + 1e-6);
110
+ let fc = (1.0 - v_dot_h).powi(5);
111
+ a_sum += (1.0 - fc) * g_vis;
112
+ b_sum += fc * g_vis;
113
+ }
114
+ }
115
+ let scale = a_sum / BRDF_LUT_SAMPLES as f32;
116
+ let bias = b_sum / BRDF_LUT_SAMPLES as f32;
117
+ row.push(half::f16::from_f32(scale).to_bits());
118
+ row.push(half::f16::from_f32(bias).to_bits());
119
+ }
120
+ row
121
+ }
122
+
123
+ /// Build a `size × size` BRDF LUT as packed Rg16Float texels. Each
124
+ /// row is constant `roughness` (v axis), each column constant `NdotV`
125
+ /// (u axis). Output is row-major suitable for write_texture. Splits
126
+ /// across `available_parallelism()` threads since cells are
127
+ /// independent — keeps startup latency manageable even at 1024 spp.
128
+ pub fn build_brdf_lut(size: usize) -> Vec<u16> {
129
+ #[cfg(not(target_arch = "wasm32"))]
130
+ {
131
+ let nthreads = std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4);
132
+ let rows_per_thread = (size + nthreads - 1) / nthreads;
133
+ let mut all_rows: Vec<Option<Vec<Vec<u16>>>> = (0..nthreads).map(|_| None).collect();
134
+ std::thread::scope(|s| {
135
+ let mut handles = Vec::with_capacity(nthreads);
136
+ for t in 0..nthreads {
137
+ let y_start = t * rows_per_thread;
138
+ let y_end = ((t + 1) * rows_per_thread).min(size);
139
+ let h = s.spawn(move || {
140
+ (y_start..y_end).map(|y| build_brdf_lut_row(y, size)).collect::<Vec<_>>()
141
+ });
142
+ handles.push(h);
143
+ }
144
+ for (t, h) in handles.into_iter().enumerate() {
145
+ all_rows[t] = Some(h.join().unwrap());
146
+ }
147
+ });
148
+ all_rows.into_iter().flatten().flatten().flatten().collect()
149
+ }
150
+ #[cfg(target_arch = "wasm32")]
151
+ {
152
+ (0..size).flat_map(|y| build_brdf_lut_row(y, size)).collect()
153
+ }
154
+ }
@@ -0,0 +1,143 @@
1
+ //! Immediate-mode 2D shape drawing (rect/line/circle — batched quads
2
+ //! over the white texture) and this-frame output plumbing
3
+ //! (FrameTarget: swapchain frame or the headless offscreen target).
4
+ //! Split out of renderer/mod.rs (2000-line file policy).
5
+
6
+ use super::types::Vertex2D;
7
+ use super::Renderer;
8
+
9
+ impl Renderer {
10
+
11
+ pub fn draw_rect(&mut self, x: f64, y: f64, w: f64, h: f64, r: f64, g: f64, b: f64, a: f64) {
12
+ self.ensure_draw_state(0);
13
+ let color = Self::color_to_f32_srgb(r, g, b, a);
14
+ let base = self.vertices_2d.len() as u32;
15
+ let (x, y, w, h) = (x as f32, y as f32, w as f32, h as f32);
16
+
17
+ self.vertices_2d.push(Vertex2D { position: [x, y], uv: [0.0, 0.0], color });
18
+ self.vertices_2d.push(Vertex2D { position: [x + w, y], uv: [0.0, 0.0], color });
19
+ self.vertices_2d.push(Vertex2D { position: [x + w, y + h], uv: [0.0, 0.0], color });
20
+ self.vertices_2d.push(Vertex2D { position: [x, y + h], uv: [0.0, 0.0], color });
21
+
22
+ self.indices_2d.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
23
+ }
24
+
25
+ pub fn draw_rect_lines(&mut self, x: f64, y: f64, w: f64, h: f64, thickness: f64, r: f64, g: f64, b: f64, a: f64) {
26
+ let t = thickness;
27
+ self.draw_rect(x, y, w, t, r, g, b, a);
28
+ self.draw_rect(x, y + h - t, w, t, r, g, b, a);
29
+ self.draw_rect(x, y + t, t, h - 2.0 * t, r, g, b, a);
30
+ self.draw_rect(x + w - t, y + t, t, h - 2.0 * t, r, g, b, a);
31
+ }
32
+
33
+ pub fn draw_line(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, thickness: f64, r: f64, g: f64, b: f64, a: f64) {
34
+ self.ensure_draw_state(0);
35
+ let color = Self::color_to_f32_srgb(r, g, b, a);
36
+ let dx = (x2 - x1) as f32;
37
+ let dy = (y2 - y1) as f32;
38
+ let len = (dx * dx + dy * dy).sqrt();
39
+ if len == 0.0 { return; }
40
+ let half_t = (thickness as f32) * 0.5;
41
+ let nx = -dy / len * half_t;
42
+ let ny = dx / len * half_t;
43
+ let (x1, y1, x2, y2) = (x1 as f32, y1 as f32, x2 as f32, y2 as f32);
44
+ let base = self.vertices_2d.len() as u32;
45
+
46
+ self.vertices_2d.push(Vertex2D { position: [x1 + nx, y1 + ny], uv: [0.0, 0.0], color });
47
+ self.vertices_2d.push(Vertex2D { position: [x1 - nx, y1 - ny], uv: [0.0, 0.0], color });
48
+ self.vertices_2d.push(Vertex2D { position: [x2 - nx, y2 - ny], uv: [0.0, 0.0], color });
49
+ self.vertices_2d.push(Vertex2D { position: [x2 + nx, y2 + ny], uv: [0.0, 0.0], color });
50
+
51
+ self.indices_2d.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
52
+ }
53
+
54
+ pub fn draw_circle(&mut self, cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
55
+ self.ensure_draw_state(0);
56
+ let color = Self::color_to_f32_srgb(r, g, b, a);
57
+ let segments = 36u32;
58
+ let base = self.vertices_2d.len() as u32;
59
+ let (cx, cy, radius) = (cx as f32, cy as f32, radius as f32);
60
+
61
+ self.vertices_2d.push(Vertex2D { position: [cx, cy], uv: [0.0, 0.0], color });
62
+ for i in 0..segments {
63
+ let angle = (i as f32) / (segments as f32) * std::f32::consts::TAU;
64
+ self.vertices_2d.push(Vertex2D {
65
+ position: [cx + radius * angle.cos(), cy + radius * angle.sin()],
66
+ uv: [0.0, 0.0],
67
+ color,
68
+ });
69
+ }
70
+ for i in 0..segments {
71
+ let next = if i + 1 < segments { i + 1 } else { 0 };
72
+ self.indices_2d.extend_from_slice(&[base, base + 1 + i, base + 1 + next]);
73
+ }
74
+ }
75
+
76
+ pub fn draw_circle_lines(&mut self, cx: f64, cy: f64, radius: f64, r: f64, g: f64, b: f64, a: f64) {
77
+ let segments = 36;
78
+ for i in 0..segments {
79
+ let a1 = (i as f64) / (segments as f64) * std::f64::consts::TAU;
80
+ let a2 = ((i + 1) as f64) / (segments as f64) * std::f64::consts::TAU;
81
+ self.draw_line(
82
+ cx + radius * a1.cos(), cy + radius * a1.sin(),
83
+ cx + radius * a2.cos(), cy + radius * a2.sin(),
84
+ 1.0, r, g, b, a,
85
+ );
86
+ }
87
+ }
88
+ }
89
+
90
+ /// This frame's output: a swapchain frame, or the persistent offscreen
91
+ /// texture in headless mode.
92
+ pub(crate) enum FrameTarget {
93
+ Surface(wgpu::SurfaceTexture),
94
+ Headless,
95
+ }
96
+
97
+ impl Renderer {
98
+ /// Acquire the frame target. `None` means the swapchain was lost and
99
+ /// has been reconfigured — skip this frame.
100
+ pub(super) fn acquire_frame(&self) -> Option<FrameTarget> {
101
+ match &self.surface {
102
+ Some(surface) => match surface.get_current_texture() {
103
+ wgpu::CurrentSurfaceTexture::Success(t)
104
+ | wgpu::CurrentSurfaceTexture::Suboptimal(t) => Some(FrameTarget::Surface(t)),
105
+ _ => {
106
+ // Diagnostic (title-freeze investigation): this path used
107
+ // to fail silently; an eternal acquire failure looks like
108
+ // a frozen game (headless spin, last frame stays on
109
+ // screen). One-shot + periodic so a wedged swapchain is
110
+ // visible in stderr.
111
+ static ACQ_FAILS: std::sync::atomic::AtomicU32 =
112
+ std::sync::atomic::AtomicU32::new(0);
113
+ let n = ACQ_FAILS.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
114
+ if n == 1 || n % 300 == 0 {
115
+ crate::ffi::log_error(&format!(
116
+ "bloom: surface acquire failed (count={n}) — reconfiguring and skipping frame"
117
+ ));
118
+ }
119
+ surface.configure(&self.device, &self.surface_config);
120
+ None
121
+ }
122
+ },
123
+ None => Some(FrameTarget::Headless),
124
+ }
125
+ }
126
+
127
+ pub(super) fn frame_texture<'a>(&'a self, target: &'a FrameTarget) -> &'a wgpu::Texture {
128
+ match target {
129
+ FrameTarget::Surface(t) => &t.texture,
130
+ FrameTarget::Headless => self
131
+ .headless_target
132
+ .as_ref()
133
+ .expect("headless renderer always owns a headless_target"),
134
+ }
135
+ }
136
+
137
+ pub(super) fn present_frame(&self, target: FrameTarget) {
138
+ if let FrameTarget::Surface(t) = target {
139
+ t.present();
140
+ }
141
+ }
142
+ }
143
+