@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.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- package/src/world/version.ts +114 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
use crate::renderer::Renderer;
|
|
2
|
+
use fontdue::{Font, FontSettings};
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
|
|
5
|
+
static DEFAULT_FONT_DATA: &[u8] = include_bytes!("../assets/default_font.ttf");
|
|
6
|
+
|
|
7
|
+
struct GlyphInfo {
|
|
8
|
+
atlas_x: u32,
|
|
9
|
+
atlas_y: u32,
|
|
10
|
+
width: u32,
|
|
11
|
+
height: u32,
|
|
12
|
+
advance: f32,
|
|
13
|
+
x_offset: f32,
|
|
14
|
+
y_offset: f32,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Generate a signed distance field from a bitmap glyph.
|
|
18
|
+
/// `bitmap` is the alpha channel, `w`/`h` are dimensions.
|
|
19
|
+
/// `spread` is the distance range in pixels.
|
|
20
|
+
/// Returns an RGBA SDF texture (white + SDF in alpha).
|
|
21
|
+
fn generate_sdf(bitmap: &[u8], w: u32, h: u32, spread: f32) -> Vec<u8> {
|
|
22
|
+
let ww = w as i32;
|
|
23
|
+
let hh = h as i32;
|
|
24
|
+
let spread_i = spread as i32;
|
|
25
|
+
let mut sdf = vec![0u8; (w * h * 4) as usize];
|
|
26
|
+
|
|
27
|
+
for y in 0..hh {
|
|
28
|
+
for x in 0..ww {
|
|
29
|
+
let inside = bitmap[(y * ww + x) as usize] > 127;
|
|
30
|
+
let mut min_dist = spread;
|
|
31
|
+
|
|
32
|
+
// Search in a window around the pixel for the nearest edge
|
|
33
|
+
let x0 = (x - spread_i).max(0);
|
|
34
|
+
let x1 = (x + spread_i).min(ww - 1);
|
|
35
|
+
let y0 = (y - spread_i).max(0);
|
|
36
|
+
let y1 = (y + spread_i).min(hh - 1);
|
|
37
|
+
|
|
38
|
+
for sy in y0..=y1 {
|
|
39
|
+
for sx in x0..=x1 {
|
|
40
|
+
let other_inside = bitmap[(sy * ww + sx) as usize] > 127;
|
|
41
|
+
if other_inside != inside {
|
|
42
|
+
let dx = (sx - x) as f32;
|
|
43
|
+
let dy = (sy - y) as f32;
|
|
44
|
+
let dist = (dx * dx + dy * dy).sqrt();
|
|
45
|
+
if dist < min_dist { min_dist = dist; }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let normalized = if inside {
|
|
51
|
+
0.5 + 0.5 * (min_dist / spread)
|
|
52
|
+
} else {
|
|
53
|
+
0.5 - 0.5 * (min_dist / spread)
|
|
54
|
+
};
|
|
55
|
+
let alpha = (normalized.clamp(0.0, 1.0) * 255.0) as u8;
|
|
56
|
+
let idx = ((y * ww + x) * 4) as usize;
|
|
57
|
+
sdf[idx] = 255;
|
|
58
|
+
sdf[idx + 1] = 255;
|
|
59
|
+
sdf[idx + 2] = 255;
|
|
60
|
+
sdf[idx + 3] = alpha;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
sdf
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub struct TextRenderer {
|
|
67
|
+
fonts: Vec<Font>,
|
|
68
|
+
glyph_cache: HashMap<(usize, char, u32), GlyphInfo>,
|
|
69
|
+
atlas_data: Vec<u8>,
|
|
70
|
+
atlas_width: u32,
|
|
71
|
+
atlas_height: u32,
|
|
72
|
+
atlas_cursor_x: u32,
|
|
73
|
+
atlas_cursor_y: u32,
|
|
74
|
+
atlas_row_height: u32,
|
|
75
|
+
atlas_bind_group_idx: Option<u32>,
|
|
76
|
+
atlas_dirty: bool,
|
|
77
|
+
/// One-shot guard for the atlas-full warning.
|
|
78
|
+
atlas_overflow_warned: bool,
|
|
79
|
+
// SDF atlas (separate from bitmap atlas)
|
|
80
|
+
sdf_glyph_cache: HashMap<(usize, char), GlyphInfo>,
|
|
81
|
+
sdf_atlas_data: Vec<u8>,
|
|
82
|
+
sdf_atlas_cursor_x: u32,
|
|
83
|
+
sdf_atlas_cursor_y: u32,
|
|
84
|
+
sdf_atlas_row_height: u32,
|
|
85
|
+
sdf_atlas_bind_group_idx: Option<u32>,
|
|
86
|
+
sdf_atlas_dirty: bool,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
impl TextRenderer {
|
|
90
|
+
/// Cheap placeholder that allocates nothing. Used as a temporary swap target
|
|
91
|
+
/// when we need to split borrows on EngineState (text + renderer).
|
|
92
|
+
pub fn empty() -> Self {
|
|
93
|
+
Self {
|
|
94
|
+
fonts: Vec::new(),
|
|
95
|
+
glyph_cache: HashMap::new(),
|
|
96
|
+
atlas_data: Vec::new(),
|
|
97
|
+
atlas_width: 0,
|
|
98
|
+
atlas_height: 0,
|
|
99
|
+
atlas_cursor_x: 0,
|
|
100
|
+
atlas_cursor_y: 0,
|
|
101
|
+
atlas_row_height: 0,
|
|
102
|
+
atlas_bind_group_idx: None,
|
|
103
|
+
atlas_dirty: false,
|
|
104
|
+
atlas_overflow_warned: false,
|
|
105
|
+
sdf_glyph_cache: HashMap::new(),
|
|
106
|
+
sdf_atlas_data: Vec::new(),
|
|
107
|
+
sdf_atlas_cursor_x: 0,
|
|
108
|
+
sdf_atlas_cursor_y: 0,
|
|
109
|
+
sdf_atlas_row_height: 0,
|
|
110
|
+
sdf_atlas_bind_group_idx: None,
|
|
111
|
+
sdf_atlas_dirty: false,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub fn new() -> Self {
|
|
116
|
+
let default_font = Font::from_bytes(DEFAULT_FONT_DATA, FontSettings::default())
|
|
117
|
+
.expect("Failed to load default font");
|
|
118
|
+
|
|
119
|
+
let atlas_width = 1024;
|
|
120
|
+
let atlas_height = 1024;
|
|
121
|
+
|
|
122
|
+
Self {
|
|
123
|
+
fonts: vec![default_font],
|
|
124
|
+
glyph_cache: HashMap::new(),
|
|
125
|
+
atlas_data: vec![0u8; (atlas_width * atlas_height * 4) as usize],
|
|
126
|
+
atlas_width,
|
|
127
|
+
atlas_height,
|
|
128
|
+
atlas_cursor_x: 0,
|
|
129
|
+
atlas_cursor_y: 0,
|
|
130
|
+
atlas_row_height: 0,
|
|
131
|
+
atlas_bind_group_idx: None,
|
|
132
|
+
atlas_dirty: true,
|
|
133
|
+
atlas_overflow_warned: false,
|
|
134
|
+
sdf_glyph_cache: HashMap::new(),
|
|
135
|
+
sdf_atlas_data: vec![0u8; (atlas_width * atlas_height * 4) as usize],
|
|
136
|
+
sdf_atlas_cursor_x: 0,
|
|
137
|
+
sdf_atlas_cursor_y: 0,
|
|
138
|
+
sdf_atlas_row_height: 0,
|
|
139
|
+
sdf_atlas_bind_group_idx: None,
|
|
140
|
+
sdf_atlas_dirty: true,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
pub fn load_font(&mut self, data: &[u8]) -> usize {
|
|
145
|
+
let font = Font::from_bytes(data, FontSettings::default())
|
|
146
|
+
.expect("Failed to load font");
|
|
147
|
+
self.fonts.push(font);
|
|
148
|
+
self.fonts.len() - 1
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fn rasterize_glyph(&mut self, font_idx: usize, ch: char, size: u32) -> &GlyphInfo {
|
|
152
|
+
let key = (font_idx, ch, size);
|
|
153
|
+
if self.glyph_cache.contains_key(&key) {
|
|
154
|
+
return &self.glyph_cache[&key];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let font = &self.fonts[font_idx];
|
|
158
|
+
let (metrics, bitmap) = font.rasterize(ch, size as f32);
|
|
159
|
+
|
|
160
|
+
let gw = metrics.width as u32;
|
|
161
|
+
let gh = metrics.height as u32;
|
|
162
|
+
|
|
163
|
+
if self.atlas_cursor_x + gw > self.atlas_width {
|
|
164
|
+
self.atlas_cursor_x = 0;
|
|
165
|
+
self.atlas_cursor_y += self.atlas_row_height;
|
|
166
|
+
self.atlas_row_height = 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Round-2 audit F5: the atlas used to overflow SILENTLY — writes
|
|
170
|
+
// were clipped by the bounds check below and the quads then
|
|
171
|
+
// sampled wrapped UVs (garbage glyphs). Warn once so the failure
|
|
172
|
+
// mode is named; grow-or-evict is the durable follow-up.
|
|
173
|
+
if self.atlas_cursor_y + gh > self.atlas_height && !self.atlas_overflow_warned {
|
|
174
|
+
self.atlas_overflow_warned = true;
|
|
175
|
+
eprintln!(
|
|
176
|
+
"bloom text: glyph atlas ({}x{}) is full — new glyphs will render corrupted \
|
|
177
|
+
until eviction lands (too many distinct size/char combinations)",
|
|
178
|
+
self.atlas_width, self.atlas_height
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let ax = self.atlas_cursor_x;
|
|
183
|
+
let ay = self.atlas_cursor_y;
|
|
184
|
+
|
|
185
|
+
for row in 0..gh {
|
|
186
|
+
for col in 0..gw {
|
|
187
|
+
let src = (row * gw + col) as usize;
|
|
188
|
+
let dst = (((ay + row) * self.atlas_width + ax + col) * 4) as usize;
|
|
189
|
+
if src < bitmap.len() && dst + 3 < self.atlas_data.len() {
|
|
190
|
+
let alpha = bitmap[src];
|
|
191
|
+
self.atlas_data[dst] = 255;
|
|
192
|
+
self.atlas_data[dst + 1] = 255;
|
|
193
|
+
self.atlas_data[dst + 2] = 255;
|
|
194
|
+
self.atlas_data[dst + 3] = alpha;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
self.atlas_cursor_x += gw + 1;
|
|
200
|
+
if gh + 1 > self.atlas_row_height {
|
|
201
|
+
self.atlas_row_height = gh + 1;
|
|
202
|
+
}
|
|
203
|
+
self.atlas_dirty = true;
|
|
204
|
+
|
|
205
|
+
self.glyph_cache.insert(key, GlyphInfo {
|
|
206
|
+
atlas_x: ax,
|
|
207
|
+
atlas_y: ay,
|
|
208
|
+
width: gw,
|
|
209
|
+
height: gh,
|
|
210
|
+
advance: metrics.advance_width,
|
|
211
|
+
x_offset: metrics.xmin as f32,
|
|
212
|
+
y_offset: metrics.ymin as f32,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
&self.glyph_cache[&key]
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn ensure_atlas_uploaded(&mut self, renderer: &mut Renderer) {
|
|
219
|
+
if !self.atlas_dirty { return; }
|
|
220
|
+
|
|
221
|
+
match self.atlas_bind_group_idx {
|
|
222
|
+
None => {
|
|
223
|
+
let idx = renderer.register_texture_no_mips(self.atlas_width, self.atlas_height, &self.atlas_data);
|
|
224
|
+
self.atlas_bind_group_idx = Some(idx);
|
|
225
|
+
}
|
|
226
|
+
Some(idx) => {
|
|
227
|
+
renderer.replace_texture_no_mips(idx, self.atlas_width, self.atlas_height, &self.atlas_data);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
self.atlas_dirty = false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
pub fn measure_text(&mut self, text: &str, size: u32) -> f64 {
|
|
234
|
+
self.measure_text_ex(0, text, size, 0.0)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
pub fn measure_text_ex(&mut self, font_idx: usize, text: &str, size: u32, spacing: f32) -> f64 {
|
|
238
|
+
let idx = if font_idx < self.fonts.len() { font_idx } else { 0 };
|
|
239
|
+
let mut width = 0.0f32;
|
|
240
|
+
let mut first = true;
|
|
241
|
+
for ch in text.chars() {
|
|
242
|
+
if !first && spacing != 0.0 {
|
|
243
|
+
width += spacing;
|
|
244
|
+
}
|
|
245
|
+
first = false;
|
|
246
|
+
let glyph = self.rasterize_glyph(idx, ch, size);
|
|
247
|
+
width += glyph.advance;
|
|
248
|
+
}
|
|
249
|
+
width as f64
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
pub fn draw_text(
|
|
253
|
+
&mut self,
|
|
254
|
+
renderer: &mut Renderer,
|
|
255
|
+
text: &str,
|
|
256
|
+
x: f64,
|
|
257
|
+
y: f64,
|
|
258
|
+
size: u32,
|
|
259
|
+
r: f64, g: f64, b: f64, a: f64,
|
|
260
|
+
) {
|
|
261
|
+
self.draw_text_ex(renderer, 0, text, x, y, size, 0.0, r, g, b, a);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
pub fn draw_text_ex(
|
|
265
|
+
&mut self,
|
|
266
|
+
renderer: &mut Renderer,
|
|
267
|
+
font_idx: usize,
|
|
268
|
+
text: &str,
|
|
269
|
+
x: f64,
|
|
270
|
+
y: f64,
|
|
271
|
+
size: u32,
|
|
272
|
+
spacing: f32,
|
|
273
|
+
r: f64, g: f64, b: f64, a: f64,
|
|
274
|
+
) {
|
|
275
|
+
let idx = if font_idx < self.fonts.len() { font_idx } else { 0 };
|
|
276
|
+
|
|
277
|
+
// Round-2 audit F5: glyphs were rasterized at LOGICAL pixel size
|
|
278
|
+
// and stretched ×dpi by the 2D projection — a soft, bilinear-
|
|
279
|
+
// magnified HUD at 150% display scaling. Rasterize at PHYSICAL
|
|
280
|
+
// size and divide the layout metrics back to logical. fontdue
|
|
281
|
+
// advances are exactly linear in pixel size, so measureText
|
|
282
|
+
// (which stays in logical units) remains consistent with the
|
|
283
|
+
// drawn advances.
|
|
284
|
+
let dpi = if renderer.logical_width > 0 {
|
|
285
|
+
(renderer.surface_config.width as f32 / renderer.logical_width as f32).max(0.25)
|
|
286
|
+
} else {
|
|
287
|
+
1.0
|
|
288
|
+
};
|
|
289
|
+
let phys_size = (((size as f32) * dpi).round().max(1.0)) as u32;
|
|
290
|
+
// Exact logical/physical ratio actually in use (phys_size is
|
|
291
|
+
// rounded, so derive from it rather than from dpi).
|
|
292
|
+
let inv_dpi = size as f32 / phys_size as f32;
|
|
293
|
+
|
|
294
|
+
// Ensure all glyphs are rasterized first
|
|
295
|
+
for ch in text.chars() {
|
|
296
|
+
self.rasterize_glyph(idx, ch, phys_size);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Upload atlas if dirty
|
|
300
|
+
self.ensure_atlas_uploaded(renderer);
|
|
301
|
+
|
|
302
|
+
let atlas_bg = match self.atlas_bind_group_idx {
|
|
303
|
+
Some(idx) => idx,
|
|
304
|
+
None => return,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// sRGB-decode to linear like every other 2D vertex color — the
|
|
308
|
+
// swapchain view's hardware encode expects linear shader output
|
|
309
|
+
// (see renderer::srgb_u8_to_linear).
|
|
310
|
+
let color = [
|
|
311
|
+
crate::renderer::srgb_u8_to_linear(r),
|
|
312
|
+
crate::renderer::srgb_u8_to_linear(g),
|
|
313
|
+
crate::renderer::srgb_u8_to_linear(b),
|
|
314
|
+
(a / 255.0) as f32,
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
let aw = self.atlas_width as f32;
|
|
318
|
+
let ah = self.atlas_height as f32;
|
|
319
|
+
|
|
320
|
+
let mut cursor_x = x as f32;
|
|
321
|
+
let mut first = true;
|
|
322
|
+
for ch in text.chars() {
|
|
323
|
+
if !first && spacing != 0.0 {
|
|
324
|
+
cursor_x += spacing;
|
|
325
|
+
}
|
|
326
|
+
first = false;
|
|
327
|
+
let key = (idx, ch, phys_size);
|
|
328
|
+
if let Some(glyph) = self.glyph_cache.get(&key) {
|
|
329
|
+
// Physical-res glyph metrics, scaled back to the logical
|
|
330
|
+
// coordinate space the 2D pass works in. The projection's
|
|
331
|
+
// ×dpi stretch then lands the bitmap 1:1 on physical
|
|
332
|
+
// pixels instead of magnifying a logical-res raster.
|
|
333
|
+
let gx = cursor_x + glyph.x_offset * inv_dpi;
|
|
334
|
+
let gy = y as f32
|
|
335
|
+
- (glyph.y_offset + glyph.height as f32) * inv_dpi
|
|
336
|
+
+ size as f32;
|
|
337
|
+
let gw = glyph.width as f32 * inv_dpi;
|
|
338
|
+
let gh = glyph.height as f32 * inv_dpi;
|
|
339
|
+
|
|
340
|
+
if gw > 0.0 && gh > 0.0 {
|
|
341
|
+
let u0 = glyph.atlas_x as f32 / aw;
|
|
342
|
+
let v0 = glyph.atlas_y as f32 / ah;
|
|
343
|
+
let u1 = (glyph.atlas_x + glyph.width) as f32 / aw;
|
|
344
|
+
let v1 = (glyph.atlas_y + glyph.height) as f32 / ah;
|
|
345
|
+
|
|
346
|
+
renderer.draw_textured_quad(gx, gy, gw, gh, u0, v0, u1, v1, color, atlas_bg);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
cursor_x += glyph.advance * inv_dpi;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
pub fn unload_font(&mut self, font_idx: usize) {
|
|
355
|
+
if font_idx > 0 && font_idx < self.fonts.len() {
|
|
356
|
+
self.glyph_cache.retain(|k, _| k.0 != font_idx);
|
|
357
|
+
self.sdf_glyph_cache.retain(|k, _| k.0 != font_idx);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// SDF text rendering: rasterize at a fixed large size, generate SDF, then render at any size
|
|
362
|
+
|
|
363
|
+
const SDF_BASE_SIZE: u32 = 48;
|
|
364
|
+
const SDF_SPREAD: f32 = 6.0;
|
|
365
|
+
|
|
366
|
+
fn rasterize_sdf_glyph(&mut self, font_idx: usize, ch: char) -> &GlyphInfo {
|
|
367
|
+
let key = (font_idx, ch);
|
|
368
|
+
if self.sdf_glyph_cache.contains_key(&key) {
|
|
369
|
+
return &self.sdf_glyph_cache[&key];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let font = &self.fonts[font_idx];
|
|
373
|
+
let (metrics, bitmap) = font.rasterize(ch, Self::SDF_BASE_SIZE as f32);
|
|
374
|
+
let gw = metrics.width as u32;
|
|
375
|
+
let gh = metrics.height as u32;
|
|
376
|
+
|
|
377
|
+
// Generate SDF from bitmap
|
|
378
|
+
let sdf_data = if gw > 0 && gh > 0 {
|
|
379
|
+
generate_sdf(&bitmap, gw, gh, Self::SDF_SPREAD)
|
|
380
|
+
} else {
|
|
381
|
+
Vec::new()
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Place in SDF atlas
|
|
385
|
+
if self.sdf_atlas_cursor_x + gw > self.atlas_width {
|
|
386
|
+
self.sdf_atlas_cursor_x = 0;
|
|
387
|
+
self.sdf_atlas_cursor_y += self.sdf_atlas_row_height;
|
|
388
|
+
self.sdf_atlas_row_height = 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let ax = self.sdf_atlas_cursor_x;
|
|
392
|
+
let ay = self.sdf_atlas_cursor_y;
|
|
393
|
+
|
|
394
|
+
for row in 0..gh {
|
|
395
|
+
for col in 0..gw {
|
|
396
|
+
let src = ((row * gw + col) * 4) as usize;
|
|
397
|
+
let dst = (((ay + row) * self.atlas_width + ax + col) * 4) as usize;
|
|
398
|
+
if src + 3 < sdf_data.len() && dst + 3 < self.sdf_atlas_data.len() {
|
|
399
|
+
self.sdf_atlas_data[dst] = sdf_data[src];
|
|
400
|
+
self.sdf_atlas_data[dst + 1] = sdf_data[src + 1];
|
|
401
|
+
self.sdf_atlas_data[dst + 2] = sdf_data[src + 2];
|
|
402
|
+
self.sdf_atlas_data[dst + 3] = sdf_data[src + 3];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
self.sdf_atlas_cursor_x += gw + 1;
|
|
408
|
+
if gh + 1 > self.sdf_atlas_row_height {
|
|
409
|
+
self.sdf_atlas_row_height = gh + 1;
|
|
410
|
+
}
|
|
411
|
+
self.sdf_atlas_dirty = true;
|
|
412
|
+
|
|
413
|
+
self.sdf_glyph_cache.insert(key, GlyphInfo {
|
|
414
|
+
atlas_x: ax,
|
|
415
|
+
atlas_y: ay,
|
|
416
|
+
width: gw,
|
|
417
|
+
height: gh,
|
|
418
|
+
advance: metrics.advance_width,
|
|
419
|
+
x_offset: metrics.xmin as f32,
|
|
420
|
+
y_offset: metrics.ymin as f32,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
&self.sdf_glyph_cache[&key]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
fn ensure_sdf_atlas_uploaded(&mut self, renderer: &mut Renderer) {
|
|
427
|
+
if !self.sdf_atlas_dirty { return; }
|
|
428
|
+
match self.sdf_atlas_bind_group_idx {
|
|
429
|
+
None => {
|
|
430
|
+
let idx = renderer.register_texture(self.atlas_width, self.atlas_height, &self.sdf_atlas_data);
|
|
431
|
+
self.sdf_atlas_bind_group_idx = Some(idx);
|
|
432
|
+
}
|
|
433
|
+
Some(idx) => {
|
|
434
|
+
renderer.update_texture(idx, self.atlas_width, self.atlas_height, &self.sdf_atlas_data);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
self.sdf_atlas_dirty = false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/// Draw text using SDF atlas. The text scales smoothly to any size.
|
|
441
|
+
pub fn draw_text_sdf(
|
|
442
|
+
&mut self,
|
|
443
|
+
renderer: &mut Renderer,
|
|
444
|
+
font_idx: usize,
|
|
445
|
+
text: &str,
|
|
446
|
+
x: f64,
|
|
447
|
+
y: f64,
|
|
448
|
+
size: u32,
|
|
449
|
+
spacing: f32,
|
|
450
|
+
r: f64, g: f64, b: f64, a: f64,
|
|
451
|
+
) {
|
|
452
|
+
let idx = if font_idx < self.fonts.len() { font_idx } else { 0 };
|
|
453
|
+
let scale = size as f32 / Self::SDF_BASE_SIZE as f32;
|
|
454
|
+
|
|
455
|
+
for ch in text.chars() {
|
|
456
|
+
self.rasterize_sdf_glyph(idx, ch);
|
|
457
|
+
}
|
|
458
|
+
self.ensure_sdf_atlas_uploaded(renderer);
|
|
459
|
+
|
|
460
|
+
let atlas_bg = match self.sdf_atlas_bind_group_idx {
|
|
461
|
+
Some(idx) => idx,
|
|
462
|
+
None => return,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// sRGB-decode to linear like every other 2D vertex color — the
|
|
466
|
+
// swapchain view's hardware encode expects linear shader output
|
|
467
|
+
// (see renderer::srgb_u8_to_linear).
|
|
468
|
+
let color = [
|
|
469
|
+
crate::renderer::srgb_u8_to_linear(r),
|
|
470
|
+
crate::renderer::srgb_u8_to_linear(g),
|
|
471
|
+
crate::renderer::srgb_u8_to_linear(b),
|
|
472
|
+
(a / 255.0) as f32,
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
let aw = self.atlas_width as f32;
|
|
476
|
+
let ah = self.atlas_height as f32;
|
|
477
|
+
|
|
478
|
+
let mut cursor_x = x as f32;
|
|
479
|
+
let mut first = true;
|
|
480
|
+
for ch in text.chars() {
|
|
481
|
+
if !first && spacing != 0.0 { cursor_x += spacing * scale; }
|
|
482
|
+
first = false;
|
|
483
|
+
let key = (idx, ch);
|
|
484
|
+
if let Some(glyph) = self.sdf_glyph_cache.get(&key) {
|
|
485
|
+
let gx = cursor_x + glyph.x_offset * scale;
|
|
486
|
+
let gy = y as f32 - glyph.y_offset * scale - glyph.height as f32 * scale + size as f32;
|
|
487
|
+
let gw = glyph.width as f32 * scale;
|
|
488
|
+
let gh = glyph.height as f32 * scale;
|
|
489
|
+
|
|
490
|
+
if gw > 0.0 && gh > 0.0 {
|
|
491
|
+
let u0 = glyph.atlas_x as f32 / aw;
|
|
492
|
+
let v0 = glyph.atlas_y as f32 / ah;
|
|
493
|
+
let u1 = (glyph.atlas_x + glyph.width) as f32 / aw;
|
|
494
|
+
let v1 = (glyph.atlas_y + glyph.height) as f32 / ah;
|
|
495
|
+
renderer.draw_textured_quad(gx, gy, gw, gh, u0, v0, u1, v1, color, atlas_bg);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
cursor_x += glyph.advance * scale;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
use crate::handles::HandleRegistry;
|
|
2
|
+
use crate::renderer::Renderer;
|
|
3
|
+
|
|
4
|
+
pub struct TextureData {
|
|
5
|
+
pub bind_group_idx: u32,
|
|
6
|
+
pub width: u32,
|
|
7
|
+
pub height: u32,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub struct ImageData {
|
|
11
|
+
pub data: Vec<u8>,
|
|
12
|
+
pub width: u32,
|
|
13
|
+
pub height: u32,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Placeholder for Q1 render texture data. Actual GPU implementation
|
|
17
|
+
/// requires creating a wgpu::Texture with RENDER_ATTACHMENT + TEXTURE_BINDING
|
|
18
|
+
/// usage and wiring it into the renderer's pass management. The FFI surface
|
|
19
|
+
/// is stable; the implementation lands in a focused GPU session.
|
|
20
|
+
pub struct RenderTextureData {
|
|
21
|
+
pub width: u32,
|
|
22
|
+
pub height: u32,
|
|
23
|
+
pub texture_handle: f64, // Points to the corresponding TextureData entry.
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub struct TextureManager {
|
|
27
|
+
pub textures: HandleRegistry<TextureData>,
|
|
28
|
+
pub images: HandleRegistry<ImageData>,
|
|
29
|
+
pub render_textures: HandleRegistry<RenderTextureData>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl TextureManager {
|
|
33
|
+
pub fn new() -> Self {
|
|
34
|
+
Self {
|
|
35
|
+
textures: HandleRegistry::new(),
|
|
36
|
+
images: HandleRegistry::new(),
|
|
37
|
+
render_textures: HandleRegistry::new(),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Q1: Create a render texture. Returns a handle. The actual wgpu texture
|
|
42
|
+
/// is created by the calling FFI function which has access to the Renderer.
|
|
43
|
+
pub fn load_render_texture(&mut self, width: u32, height: u32) -> f64 {
|
|
44
|
+
self.render_textures.alloc(RenderTextureData {
|
|
45
|
+
width, height, texture_handle: 0.0,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Set the texture handle for a render texture (called after GPU creation).
|
|
50
|
+
pub fn set_render_texture_handle(&mut self, rt_handle: f64, tex_handle: f64) {
|
|
51
|
+
if let Some(rt) = self.render_textures.get_mut(rt_handle) {
|
|
52
|
+
rt.texture_handle = tex_handle;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn unload_render_texture(&mut self, handle: f64) {
|
|
57
|
+
self.render_textures.free(handle);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub fn get_render_texture_texture(&self, handle: f64) -> f64 {
|
|
61
|
+
match self.render_textures.get(handle) {
|
|
62
|
+
Some(rt) => rt.texture_handle,
|
|
63
|
+
None => 0.0,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Decode an in-memory image (PNG/JPEG/…, whatever `image` is built with)
|
|
68
|
+
/// to raw RGBA8. Shared by the web texture-array-from-files path, which
|
|
69
|
+
/// fetches file bytes in JS and needs the same decode the native
|
|
70
|
+
/// from-files FFI gets from `image::open`.
|
|
71
|
+
pub fn decode_rgba8(file_data: &[u8]) -> Option<(Vec<u8>, u32, u32)> {
|
|
72
|
+
let img = image::load_from_memory(file_data).ok()?.to_rgba8();
|
|
73
|
+
let (w, h) = img.dimensions();
|
|
74
|
+
Some((img.into_raw(), w, h))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn load_texture(&mut self, renderer: &mut Renderer, file_data: &[u8]) -> f64 {
|
|
78
|
+
// Cooked textures (bloom-cook BC7 DDS) take the compressed path:
|
|
79
|
+
// direct mip-chain upload where the adapter has BC support, CPU
|
|
80
|
+
// decode to RGBA elsewhere. Magic-sniffed so loadTexture() is the
|
|
81
|
+
// one entry point for raw and cooked assets alike.
|
|
82
|
+
if file_data.len() >= 4 && &file_data[..4] == b"DDS " {
|
|
83
|
+
#[cfg(feature = "image-extras")]
|
|
84
|
+
return self.load_texture_dds(renderer, file_data);
|
|
85
|
+
#[cfg(not(feature = "image-extras"))]
|
|
86
|
+
{
|
|
87
|
+
crate::ffi::feature_off_warn_once("loadTexture(.dds)", "image-extras");
|
|
88
|
+
return 0.0;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
let img = match image::load_from_memory(file_data) {
|
|
92
|
+
Ok(img) => img.to_rgba8(),
|
|
93
|
+
Err(_) => return 0.0,
|
|
94
|
+
};
|
|
95
|
+
let width = img.width();
|
|
96
|
+
let height = img.height();
|
|
97
|
+
let data = img.into_raw();
|
|
98
|
+
|
|
99
|
+
let bind_group_idx = renderer.register_texture(width, height, &data);
|
|
100
|
+
self.textures.alloc(TextureData { bind_group_idx, width, height })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn unload_texture(&mut self, handle: f64, renderer: &mut Renderer) {
|
|
104
|
+
if let Some(tex) = self.textures.free(handle) {
|
|
105
|
+
renderer.unload_texture(tex.bind_group_idx);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub fn get(&self, handle: f64) -> Option<&TextureData> {
|
|
110
|
+
self.textures.get(handle)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pub fn load_image(&mut self, file_data: &[u8]) -> f64 {
|
|
114
|
+
let img = match image::load_from_memory(file_data) {
|
|
115
|
+
Ok(img) => img.to_rgba8(),
|
|
116
|
+
Err(_) => return 0.0,
|
|
117
|
+
};
|
|
118
|
+
let width = img.width();
|
|
119
|
+
let height = img.height();
|
|
120
|
+
let data = img.into_raw();
|
|
121
|
+
|
|
122
|
+
self.images.alloc(ImageData { data, width, height })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub fn image_resize(&mut self, handle: f64, new_w: u32, new_h: u32) {
|
|
126
|
+
if let Some(img_data) = self.images.get_mut(handle) {
|
|
127
|
+
let src = image::RgbaImage::from_raw(img_data.width, img_data.height, std::mem::take(&mut img_data.data));
|
|
128
|
+
if let Some(src) = src {
|
|
129
|
+
let resized = image::imageops::resize(&src, new_w, new_h, image::imageops::FilterType::Triangle);
|
|
130
|
+
img_data.width = new_w;
|
|
131
|
+
img_data.height = new_h;
|
|
132
|
+
img_data.data = resized.into_raw();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub fn image_crop(&mut self, handle: f64, x: u32, y: u32, w: u32, h: u32) {
|
|
138
|
+
if let Some(img_data) = self.images.get_mut(handle) {
|
|
139
|
+
let src = image::RgbaImage::from_raw(img_data.width, img_data.height, std::mem::take(&mut img_data.data));
|
|
140
|
+
if let Some(mut src) = src {
|
|
141
|
+
let cropped = image::imageops::crop(&mut src, x, y, w, h).to_image();
|
|
142
|
+
img_data.width = w;
|
|
143
|
+
img_data.height = h;
|
|
144
|
+
img_data.data = cropped.into_raw();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pub fn image_flip_h(&mut self, handle: f64) {
|
|
150
|
+
if let Some(img_data) = self.images.get_mut(handle) {
|
|
151
|
+
let src = image::RgbaImage::from_raw(img_data.width, img_data.height, std::mem::take(&mut img_data.data));
|
|
152
|
+
if let Some(src) = src {
|
|
153
|
+
let flipped = image::imageops::flip_horizontal(&src);
|
|
154
|
+
img_data.data = flipped.into_raw();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pub fn image_flip_v(&mut self, handle: f64) {
|
|
160
|
+
if let Some(img_data) = self.images.get_mut(handle) {
|
|
161
|
+
let src = image::RgbaImage::from_raw(img_data.width, img_data.height, std::mem::take(&mut img_data.data));
|
|
162
|
+
if let Some(src) = src {
|
|
163
|
+
let flipped = image::imageops::flip_vertical(&src);
|
|
164
|
+
img_data.data = flipped.into_raw();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[cfg(feature = "image-extras")]
|
|
170
|
+
fn load_texture_dds(&mut self, renderer: &mut Renderer, file_data: &[u8]) -> f64 {
|
|
171
|
+
let Ok(dds) = image_dds::ddsfile::Dds::read(std::io::Cursor::new(file_data)) else {
|
|
172
|
+
return 0.0;
|
|
173
|
+
};
|
|
174
|
+
let (width, height) = (dds.get_width(), dds.get_height());
|
|
175
|
+
if let Some(bind_group_idx) = renderer.register_texture_dds(&dds) {
|
|
176
|
+
return self.textures.alloc(TextureData { bind_group_idx, width, height });
|
|
177
|
+
}
|
|
178
|
+
// No BC support on this adapter (mobile GL): CPU-decode the top
|
|
179
|
+
// mip and feed the regular RGBA path (which regenerates mips).
|
|
180
|
+
match image_dds::image_from_dds(&dds, 0) {
|
|
181
|
+
Ok(rgba) => {
|
|
182
|
+
let bind_group_idx = renderer.register_texture(width, height, rgba.as_raw());
|
|
183
|
+
self.textures.alloc(TextureData { bind_group_idx, width, height })
|
|
184
|
+
}
|
|
185
|
+
Err(_) => 0.0,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
pub fn load_texture_from_image(&mut self, handle: f64, renderer: &mut Renderer) -> f64 {
|
|
190
|
+
if let Some(img_data) = self.images.get(handle) {
|
|
191
|
+
let bind_group_idx = renderer.register_texture(img_data.width, img_data.height, &img_data.data);
|
|
192
|
+
self.textures.alloc(TextureData { bind_group_idx, width: img_data.width, height: img_data.height })
|
|
193
|
+
} else {
|
|
194
|
+
0.0
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|