@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,601 @@
|
|
|
1
|
+
//! Tests for material_system: headless-GPU dispatch tests + the
|
|
2
|
+
//! translucent sort. Child module of material_system via #[path] —
|
|
3
|
+
//! the glob below re-exposes the parent's items to the inner mods.
|
|
4
|
+
|
|
5
|
+
#[allow(unused_imports)]
|
|
6
|
+
use super::*;
|
|
7
|
+
|
|
8
|
+
#[cfg(test)]
|
|
9
|
+
mod tests {
|
|
10
|
+
use super::*;
|
|
11
|
+
use crate::renderer::formats;
|
|
12
|
+
use crate::renderer::types::Vertex3D;
|
|
13
|
+
|
|
14
|
+
/// Headless wgpu device. See sibling helpers in `transient.rs` /
|
|
15
|
+
/// `impulse_field.rs` — same fallback adapter pattern. Returns None
|
|
16
|
+
/// (test skips gracefully) when no GPU is available.
|
|
17
|
+
fn try_create_device() -> Option<(wgpu::Device, wgpu::Queue)> {
|
|
18
|
+
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
19
|
+
backends: wgpu::Backends::all(),
|
|
20
|
+
..wgpu::InstanceDescriptor::new_without_display_handle()
|
|
21
|
+
});
|
|
22
|
+
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
|
23
|
+
power_preference: wgpu::PowerPreference::LowPower,
|
|
24
|
+
compatible_surface: None,
|
|
25
|
+
force_fallback_adapter: true,
|
|
26
|
+
})).ok()?;
|
|
27
|
+
// The material ABI uses 5 bind groups (PerFrame, PerView,
|
|
28
|
+
// PerMaterial, PerDraw, SceneInputs). downlevel_defaults caps
|
|
29
|
+
// max_bind_groups at 4, which is fine on Metal (it silently
|
|
30
|
+
// accepts more) but DX12 enforces it strictly — bump to 5 so
|
|
31
|
+
// the user-material pipeline validates on every backend.
|
|
32
|
+
// Also bump max_uniform_buffer_binding_size from 16KB to 64KB
|
|
33
|
+
// for the JointMatrices UBO (1024 × mat4x4 = 64KB).
|
|
34
|
+
let mut required_limits = wgpu::Limits::downlevel_defaults();
|
|
35
|
+
required_limits.max_bind_groups = 5;
|
|
36
|
+
required_limits.max_uniform_buffer_binding_size = 64 << 10;
|
|
37
|
+
let (device, queue) = pollster::block_on(adapter.request_device(
|
|
38
|
+
&wgpu::DeviceDescriptor {
|
|
39
|
+
label: Some("material-test-device"),
|
|
40
|
+
required_features: wgpu::Features::empty(),
|
|
41
|
+
required_limits,
|
|
42
|
+
..Default::default()
|
|
43
|
+
},
|
|
44
|
+
)).ok()?;
|
|
45
|
+
Some((device, queue))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Minimal translucent (Bucket::Refractive) WGSL material. Writes
|
|
49
|
+
/// a constant red+0.5α colour through the alpha-blended HDR target.
|
|
50
|
+
/// Uses #include "material_abi.wgsl" so the same pipeline-layout
|
|
51
|
+
/// / per-frame / per-view bindings are validated as production
|
|
52
|
+
/// materials. Vertex stage transforms via `draw.mvp` so we can
|
|
53
|
+
/// emit a fullscreen-ish triangle from any geometry.
|
|
54
|
+
const TRANSLUCENT_WGSL: &str = r#"
|
|
55
|
+
#include "material_abi.wgsl"
|
|
56
|
+
|
|
57
|
+
struct VsOut {
|
|
58
|
+
@builtin(position) clip_position: vec4<f32>,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
@vertex
|
|
62
|
+
fn vs_main(in: VertexInput) -> VsOut {
|
|
63
|
+
var out: VsOut;
|
|
64
|
+
out.clip_position = draw.mvp * vec4<f32>(in.position, 1.0);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@fragment
|
|
69
|
+
fn fs_main(_in: VsOut) -> TranslucentOut {
|
|
70
|
+
var out: TranslucentOut;
|
|
71
|
+
// Red, half alpha — alpha-blended onto whatever the load-op set.
|
|
72
|
+
out.hdr = vec4<f32>(1.0, 0.0, 0.0, 0.5);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
"#;
|
|
76
|
+
|
|
77
|
+
/// Create a tiny joint buffer so MaterialSystem::new is happy. The
|
|
78
|
+
/// per_draw layout binds it at @binding(1); the test material
|
|
79
|
+
/// doesn't read it but the bind group still has to validate.
|
|
80
|
+
fn make_joint_buffer(device: &wgpu::Device) -> wgpu::Buffer {
|
|
81
|
+
device.create_buffer(&wgpu::BufferDescriptor {
|
|
82
|
+
label: Some("test_joint_buffer"),
|
|
83
|
+
// 1024 mat4s × 64 B = 64 KiB. Same size as production.
|
|
84
|
+
size: 65536,
|
|
85
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
86
|
+
mapped_at_creation: false,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Build a single fullscreen triangle covering NDC: three vertices
|
|
91
|
+
/// at (-1,-1), (3,-1), (-1,3). The pipeline's MVP starts as
|
|
92
|
+
/// identity (we override it below) so the triangle covers the
|
|
93
|
+
/// whole viewport.
|
|
94
|
+
fn make_fullscreen_tri(device: &wgpu::Device, queue: &wgpu::Queue) -> (wgpu::Buffer, wgpu::Buffer, u32) {
|
|
95
|
+
let mut verts: [Vertex3D; 3] = [Vertex3D::default(); 3];
|
|
96
|
+
verts[0].position = [-1.0, -1.0, 0.5];
|
|
97
|
+
verts[1].position = [ 3.0, -1.0, 0.5];
|
|
98
|
+
verts[2].position = [-1.0, 3.0, 0.5];
|
|
99
|
+
// The MaterialPipeline's depth-stencil uses Less; the load-op
|
|
100
|
+
// for a translucent pass clears to 1.0 (far) by default in
|
|
101
|
+
// production but in this test we use a depth attachment with
|
|
102
|
+
// the CLEAR op and clear value 1.0, so anything < 1.0 passes.
|
|
103
|
+
let vb = device.create_buffer(&wgpu::BufferDescriptor {
|
|
104
|
+
label: Some("test_tri_vb"),
|
|
105
|
+
size: std::mem::size_of_val(&verts) as u64,
|
|
106
|
+
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
|
|
107
|
+
mapped_at_creation: false,
|
|
108
|
+
});
|
|
109
|
+
queue.write_buffer(&vb, 0, bytemuck::cast_slice(&verts));
|
|
110
|
+
let indices: [u32; 3] = [0, 1, 2];
|
|
111
|
+
let ib = device.create_buffer(&wgpu::BufferDescriptor {
|
|
112
|
+
label: Some("test_tri_ib"),
|
|
113
|
+
size: std::mem::size_of_val(&indices) as u64,
|
|
114
|
+
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
|
|
115
|
+
mapped_at_creation: false,
|
|
116
|
+
});
|
|
117
|
+
queue.write_buffer(&ib, 0, bytemuck::cast_slice(&indices));
|
|
118
|
+
(vb, ib, 3)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// EN-006 — translucent dispatch path. Compiles a refractive
|
|
122
|
+
/// material via `MaterialSystem::compile`, submits one draw,
|
|
123
|
+
/// dispatches into a 64×64 Rgba16Float HDR render target via
|
|
124
|
+
/// `dispatch_translucent`, then reads back the HDR and verifies
|
|
125
|
+
/// the alpha-blended red shows through the cyan background.
|
|
126
|
+
///
|
|
127
|
+
/// This is the scoped-down version of the acceptance scenario:
|
|
128
|
+
/// it exercises the dispatch site and pipeline-layout / blend-state
|
|
129
|
+
/// wiring without standing up the full `Renderer` (which requires
|
|
130
|
+
/// a surface and ~30 other resources). What this DOES cover:
|
|
131
|
+
/// - MaterialSystem::compile against a refractive WGSL source
|
|
132
|
+
/// - dispatch_translucent's pipeline / bind-group binding loop
|
|
133
|
+
/// - alpha blending via the translucent target's BlendState
|
|
134
|
+
/// - the per-draw / per-view / per-frame UBO writes
|
|
135
|
+
/// What it does NOT cover:
|
|
136
|
+
/// - The scene-color snapshot (test material has reads_scene=false
|
|
137
|
+
/// so group 4 doesn't bind; the e2e copy_texture_to_texture
|
|
138
|
+
/// before this dispatch is exercised by the depth-snapshot test
|
|
139
|
+
/// in transient.rs).
|
|
140
|
+
/// - Sort order across multiple translucent draws (single-draw test).
|
|
141
|
+
/// Skipped on adapters where `try_create_device` returns None.
|
|
142
|
+
#[test]
|
|
143
|
+
fn dispatch_translucent_alpha_blends_into_hdr() {
|
|
144
|
+
let Some((device, queue)) = try_create_device() else { return; };
|
|
145
|
+
let joint_buf = make_joint_buffer(&device);
|
|
146
|
+
let mut sys = MaterialSystem::new(&device, &queue, &joint_buf);
|
|
147
|
+
|
|
148
|
+
// Compile a refractive (translucent) material. Use the engine's
|
|
149
|
+
// production format constants so the pipeline matches what
|
|
150
|
+
// Renderer::new would have produced.
|
|
151
|
+
let handle = sys.compile(
|
|
152
|
+
&device,
|
|
153
|
+
TRANSLUCENT_WGSL,
|
|
154
|
+
FragmentProfile::Translucent,
|
|
155
|
+
Bucket::Transparent,
|
|
156
|
+
false, // reads_scene
|
|
157
|
+
false, // wants_instancing
|
|
158
|
+
wgpu::TextureFormat::Rgba16Float, // hdr_format
|
|
159
|
+
wgpu::TextureFormat::Rg8Unorm, // material_format (unused in translucent)
|
|
160
|
+
wgpu::TextureFormat::Rg16Float, // velocity_format (unused)
|
|
161
|
+
wgpu::TextureFormat::Rgba8Unorm, // albedo_format (unused)
|
|
162
|
+
formats::DEPTH_FORMAT,
|
|
163
|
+
).expect("translucent material compiles");
|
|
164
|
+
assert!(handle != 0, "compile returns a 1-based handle");
|
|
165
|
+
|
|
166
|
+
// Frame uniforms — zeros are fine for a constant-colour shader.
|
|
167
|
+
let pf = PerFrameUniforms {
|
|
168
|
+
time: 0.0, delta_time: 0.0, frame_index: 0, _pad0: 0,
|
|
169
|
+
screen_resolution: [64.0, 64.0], render_resolution: [64.0, 64.0],
|
|
170
|
+
taa_jitter: [0.0; 2], _pad1: [0.0; 2], wind: [0.0; 4],
|
|
171
|
+
cloud: [0.0; 4],
|
|
172
|
+
};
|
|
173
|
+
let pv = bytemuck::Zeroable::zeroed();
|
|
174
|
+
sys.update_frame_uniforms(&queue, &pf, &pv);
|
|
175
|
+
sys.reset_draw_slot(crate::renderer::IDENTITY_MAT4);
|
|
176
|
+
|
|
177
|
+
// MVP = identity so the fullscreen tri stays in NDC.
|
|
178
|
+
let identity = [
|
|
179
|
+
[1.0, 0.0, 0.0, 0.0],
|
|
180
|
+
[0.0, 1.0, 0.0, 0.0],
|
|
181
|
+
[0.0, 0.0, 1.0, 0.0],
|
|
182
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
183
|
+
];
|
|
184
|
+
let (vb, ib, icount) = make_fullscreen_tri(&device, &queue);
|
|
185
|
+
|
|
186
|
+
sys.submit_draw(
|
|
187
|
+
&device, &queue, &joint_buf,
|
|
188
|
+
handle, /* mesh_handle */ 1, /* mesh_idx */ 0,
|
|
189
|
+
identity, identity, identity,
|
|
190
|
+
[1.0; 4], [0; 4],
|
|
191
|
+
);
|
|
192
|
+
assert_eq!(sys.translucent_commands.len(), 1, "draw queued in translucent bucket");
|
|
193
|
+
|
|
194
|
+
// Build the HDR + depth render targets for the dispatch.
|
|
195
|
+
let (rt_w, rt_h) = (64u32, 64u32);
|
|
196
|
+
let hdr_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
197
|
+
label: Some("test_hdr_rt"),
|
|
198
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
199
|
+
mip_level_count: 1, sample_count: 1,
|
|
200
|
+
dimension: wgpu::TextureDimension::D2,
|
|
201
|
+
format: wgpu::TextureFormat::Rgba16Float,
|
|
202
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
203
|
+
view_formats: &[],
|
|
204
|
+
});
|
|
205
|
+
let hdr_view = hdr_rt.create_view(&Default::default());
|
|
206
|
+
let depth_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
207
|
+
label: Some("test_depth_rt"),
|
|
208
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
209
|
+
mip_level_count: 1, sample_count: 1,
|
|
210
|
+
dimension: wgpu::TextureDimension::D2,
|
|
211
|
+
format: formats::DEPTH_FORMAT,
|
|
212
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
213
|
+
view_formats: &[],
|
|
214
|
+
});
|
|
215
|
+
let depth_view = depth_rt.create_view(&Default::default());
|
|
216
|
+
|
|
217
|
+
// Pre-clear HDR to opaque cyan so we can detect alpha-blended red:
|
|
218
|
+
// (1, 0, 0, 0.5) over (0, 1, 1, 1) → (0.5, 0.5, 0.5, 1.0).
|
|
219
|
+
let bg_color = wgpu::Color { r: 0.0, g: 1.0, b: 1.0, a: 1.0 };
|
|
220
|
+
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
221
|
+
label: Some("test_translucent_encoder"),
|
|
222
|
+
});
|
|
223
|
+
{
|
|
224
|
+
// Clear HDR + depth in one pass.
|
|
225
|
+
let _clear = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
226
|
+
label: Some("test_clear_pass"),
|
|
227
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
228
|
+
view: &hdr_view,
|
|
229
|
+
resolve_target: None,
|
|
230
|
+
depth_slice: None,
|
|
231
|
+
ops: wgpu::Operations {
|
|
232
|
+
load: wgpu::LoadOp::Clear(bg_color),
|
|
233
|
+
store: wgpu::StoreOp::Store,
|
|
234
|
+
},
|
|
235
|
+
})],
|
|
236
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
237
|
+
view: &depth_view,
|
|
238
|
+
depth_ops: Some(wgpu::Operations {
|
|
239
|
+
load: wgpu::LoadOp::Clear(1.0),
|
|
240
|
+
store: wgpu::StoreOp::Store,
|
|
241
|
+
}),
|
|
242
|
+
stencil_ops: None,
|
|
243
|
+
}),
|
|
244
|
+
timestamp_writes: None,
|
|
245
|
+
occlusion_query_set: None,
|
|
246
|
+
multiview_mask: None,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// Translucent dispatch — Load (don't clear) HDR; depth read-only.
|
|
250
|
+
{
|
|
251
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
252
|
+
label: Some("test_translucent_pass"),
|
|
253
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
254
|
+
view: &hdr_view,
|
|
255
|
+
resolve_target: None,
|
|
256
|
+
depth_slice: None,
|
|
257
|
+
ops: wgpu::Operations {
|
|
258
|
+
load: wgpu::LoadOp::Load,
|
|
259
|
+
store: wgpu::StoreOp::Store,
|
|
260
|
+
},
|
|
261
|
+
})],
|
|
262
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
263
|
+
view: &depth_view,
|
|
264
|
+
depth_ops: Some(wgpu::Operations {
|
|
265
|
+
load: wgpu::LoadOp::Load,
|
|
266
|
+
store: wgpu::StoreOp::Store,
|
|
267
|
+
}),
|
|
268
|
+
stencil_ops: None,
|
|
269
|
+
}),
|
|
270
|
+
timestamp_writes: None,
|
|
271
|
+
occlusion_query_set: None,
|
|
272
|
+
multiview_mask: None,
|
|
273
|
+
});
|
|
274
|
+
sys.dispatch_translucent(&mut pass, |mh, _idx| {
|
|
275
|
+
if mh == 1 { Some((&vb, &ib, icount)) } else { None }
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Read back the HDR target (Rgba16Float = 8 B / texel).
|
|
280
|
+
let bpr_unpadded = rt_w * 8;
|
|
281
|
+
let bpr = (bpr_unpadded + 255) & !255;
|
|
282
|
+
let buf_size = (bpr * rt_h) as u64;
|
|
283
|
+
let staging = device.create_buffer(&wgpu::BufferDescriptor {
|
|
284
|
+
label: Some("test_hdr_staging"),
|
|
285
|
+
size: buf_size,
|
|
286
|
+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
287
|
+
mapped_at_creation: false,
|
|
288
|
+
});
|
|
289
|
+
encoder.copy_texture_to_buffer(
|
|
290
|
+
wgpu::TexelCopyTextureInfo {
|
|
291
|
+
texture: &hdr_rt,
|
|
292
|
+
mip_level: 0,
|
|
293
|
+
origin: wgpu::Origin3d::ZERO,
|
|
294
|
+
aspect: wgpu::TextureAspect::All,
|
|
295
|
+
},
|
|
296
|
+
wgpu::TexelCopyBufferInfo {
|
|
297
|
+
buffer: &staging,
|
|
298
|
+
layout: wgpu::TexelCopyBufferLayout {
|
|
299
|
+
offset: 0,
|
|
300
|
+
bytes_per_row: Some(bpr),
|
|
301
|
+
rows_per_image: Some(rt_h),
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
305
|
+
);
|
|
306
|
+
queue.submit(std::iter::once(encoder.finish()));
|
|
307
|
+
|
|
308
|
+
let slice = staging.slice(..);
|
|
309
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
310
|
+
slice.map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
|
|
311
|
+
let _ = device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
|
|
312
|
+
rx.recv().expect("map sender").expect("map failed");
|
|
313
|
+
let data = slice.get_mapped_range();
|
|
314
|
+
|
|
315
|
+
// Sample the centre texel. Rgba16Float = 4 × half = 8 bytes.
|
|
316
|
+
// Use half::f16 via bytemuck cast through u16 then manual decode.
|
|
317
|
+
let cx = rt_w / 2;
|
|
318
|
+
let cy = rt_h / 2;
|
|
319
|
+
let row_start = (cy * bpr) as usize;
|
|
320
|
+
let texel_start = row_start + (cx as usize) * 8;
|
|
321
|
+
let halfs: [u16; 4] = [
|
|
322
|
+
u16::from_le_bytes([data[texel_start], data[texel_start + 1]]),
|
|
323
|
+
u16::from_le_bytes([data[texel_start + 2], data[texel_start + 3]]),
|
|
324
|
+
u16::from_le_bytes([data[texel_start + 4], data[texel_start + 5]]),
|
|
325
|
+
u16::from_le_bytes([data[texel_start + 6], data[texel_start + 7]]),
|
|
326
|
+
];
|
|
327
|
+
drop(data);
|
|
328
|
+
staging.unmap();
|
|
329
|
+
|
|
330
|
+
let r = f16_to_f32(halfs[0]);
|
|
331
|
+
let g = f16_to_f32(halfs[1]);
|
|
332
|
+
let b = f16_to_f32(halfs[2]);
|
|
333
|
+
let _a = f16_to_f32(halfs[3]);
|
|
334
|
+
|
|
335
|
+
// Expected SrcAlpha/OneMinusSrcAlpha blend:
|
|
336
|
+
// src = (1, 0, 0, 0.5)
|
|
337
|
+
// dst = (0, 1, 1, 1)
|
|
338
|
+
// out.rgb = src.rgb * src.a + dst.rgb * (1 - src.a)
|
|
339
|
+
// = (0.5, 0.5, 0.5)
|
|
340
|
+
// Allow 1/256 tolerance for half-precision round-trip.
|
|
341
|
+
let eps = 0.02;
|
|
342
|
+
assert!((r - 0.5).abs() < eps, "red channel = {} (expected ~0.5)", r);
|
|
343
|
+
assert!((g - 0.5).abs() < eps, "green channel = {} (expected ~0.5)", g);
|
|
344
|
+
assert!((b - 0.5).abs() < eps, "blue channel = {} (expected ~0.5)", b);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// IEEE-754 binary16 → binary32. We don't pull in the `half` crate
|
|
348
|
+
/// for a single readback; the manual decode is short and exact for
|
|
349
|
+
/// the values this test produces (no NaN / Inf / subnormal cases).
|
|
350
|
+
fn f16_to_f32(bits: u16) -> f32 {
|
|
351
|
+
let sign = (bits >> 15) & 0x1;
|
|
352
|
+
let exp = (bits >> 10) & 0x1f;
|
|
353
|
+
let frac = bits & 0x3ff;
|
|
354
|
+
if exp == 0 {
|
|
355
|
+
if frac == 0 {
|
|
356
|
+
return if sign == 1 { -0.0 } else { 0.0 };
|
|
357
|
+
}
|
|
358
|
+
// Subnormal — not expected in this test; decode for completeness.
|
|
359
|
+
let f = (frac as f32) / 1024.0 * (2.0f32).powi(-14);
|
|
360
|
+
return if sign == 1 { -f } else { f };
|
|
361
|
+
}
|
|
362
|
+
if exp == 0x1f {
|
|
363
|
+
return f32::NAN; // Inf or NaN — unexpected in this test.
|
|
364
|
+
}
|
|
365
|
+
let f = (1.0 + (frac as f32) / 1024.0) * (2.0f32).powi(exp as i32 - 15);
|
|
366
|
+
if sign == 1 { -f } else { f }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// Samples layer 0 of the albedo texture array and writes it straight out.
|
|
370
|
+
/// If the array is bound, this is the array's colour; if the stub is bound,
|
|
371
|
+
/// it is the stub's.
|
|
372
|
+
const ARRAY_SAMPLING_WGSL: &str = r#"
|
|
373
|
+
#include "material_abi.wgsl"
|
|
374
|
+
|
|
375
|
+
struct VsOut {
|
|
376
|
+
@builtin(position) clip_position: vec4<f32>,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
@vertex
|
|
380
|
+
fn vs_main(in: VertexInput) -> VsOut {
|
|
381
|
+
var out: VsOut;
|
|
382
|
+
out.clip_position = draw.mvp * vec4<f32>(in.position, 1.0);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
@fragment
|
|
387
|
+
fn fs_main(_in: VsOut) -> TranslucentOut {
|
|
388
|
+
var out: TranslucentOut;
|
|
389
|
+
let c = textureSampleLevel(albedo_array, albedo_array_samp, vec2<f32>(0.5, 0.5), 0, 0.0);
|
|
390
|
+
out.hdr = vec4<f32>(c.rgb, 1.0);
|
|
391
|
+
return out;
|
|
392
|
+
}
|
|
393
|
+
"#;
|
|
394
|
+
|
|
395
|
+
/// EN-014 regression, end-to-end.
|
|
396
|
+
///
|
|
397
|
+
/// `set_user_params` used to rebuild the per-material bind group with the
|
|
398
|
+
/// 1×1 stub array HARDCODED on bindings 14/15/16, so this sequence —
|
|
399
|
+
///
|
|
400
|
+
/// set_material_texture_array(m, ALBEDO, arr); // link the art
|
|
401
|
+
/// set_material_params(m, [...]); // ...and silently lose it
|
|
402
|
+
///
|
|
403
|
+
/// — left the material sampling the stub. Nothing errored, no validation
|
|
404
|
+
/// complained; the pixels just never appeared. It cost an afternoon in the
|
|
405
|
+
/// VFX round, with particles and decals both simulating perfectly and
|
|
406
|
+
/// rendering nothing.
|
|
407
|
+
///
|
|
408
|
+
/// Asserting the *link* survives is not enough (it always did — it was the
|
|
409
|
+
/// bind group that lost it), so this renders a quad that samples the array
|
|
410
|
+
/// and reads the pixel back. Green means the array is bound; the stub is
|
|
411
|
+
/// not green.
|
|
412
|
+
#[test]
|
|
413
|
+
fn set_user_params_preserves_a_linked_texture_array() {
|
|
414
|
+
let Some((device, queue)) = try_create_device() else { return; };
|
|
415
|
+
let joint_buf = make_joint_buffer(&device);
|
|
416
|
+
let mut sys = MaterialSystem::new(&device, &queue, &joint_buf);
|
|
417
|
+
|
|
418
|
+
let handle = sys.compile(
|
|
419
|
+
&device,
|
|
420
|
+
ARRAY_SAMPLING_WGSL,
|
|
421
|
+
FragmentProfile::Translucent,
|
|
422
|
+
Bucket::Transparent,
|
|
423
|
+
false,
|
|
424
|
+
false,
|
|
425
|
+
wgpu::TextureFormat::Rgba16Float,
|
|
426
|
+
wgpu::TextureFormat::Rg8Unorm,
|
|
427
|
+
wgpu::TextureFormat::Rg16Float,
|
|
428
|
+
wgpu::TextureFormat::Rgba8Unorm,
|
|
429
|
+
formats::DEPTH_FORMAT,
|
|
430
|
+
).expect("array-sampling material compiles");
|
|
431
|
+
|
|
432
|
+
// One 2×2 layer of pure green (linear Rgba8 → format code 1).
|
|
433
|
+
let px: [u8; 2 * 2 * 4] = [
|
|
434
|
+
0, 255, 0, 255, 0, 255, 0, 255,
|
|
435
|
+
0, 255, 0, 255, 0, 255, 0, 255,
|
|
436
|
+
];
|
|
437
|
+
let arr = sys.create_texture_array_ex(&device, &queue, &[(&px[..], 2, 2)], 1, 1);
|
|
438
|
+
assert!(arr != 0, "texture array allocates");
|
|
439
|
+
|
|
440
|
+
// Link the array, THEN set params — the order that used to break.
|
|
441
|
+
// (Clone the stub probe view: set_material_texture_array borrows `sys`
|
|
442
|
+
// mutably and would otherwise conflict with holding a & into it.)
|
|
443
|
+
let probe_view = sys.default_black_view.clone();
|
|
444
|
+
sys.set_material_texture_array(&device, handle, 0, arr, &probe_view);
|
|
445
|
+
sys.set_user_params(&device, &queue, handle, &[0u8; 16]).expect("params set");
|
|
446
|
+
|
|
447
|
+
// Render it.
|
|
448
|
+
let pf = PerFrameUniforms {
|
|
449
|
+
time: 0.0, delta_time: 0.0, frame_index: 0, _pad0: 0,
|
|
450
|
+
screen_resolution: [64.0, 64.0], render_resolution: [64.0, 64.0],
|
|
451
|
+
taa_jitter: [0.0; 2], _pad1: [0.0; 2], wind: [0.0; 4],
|
|
452
|
+
cloud: [0.0; 4],
|
|
453
|
+
};
|
|
454
|
+
let pv = bytemuck::Zeroable::zeroed();
|
|
455
|
+
sys.update_frame_uniforms(&queue, &pf, &pv);
|
|
456
|
+
sys.reset_draw_slot(crate::renderer::IDENTITY_MAT4);
|
|
457
|
+
|
|
458
|
+
let identity = crate::renderer::IDENTITY_MAT4;
|
|
459
|
+
let (vb, ib, icount) = make_fullscreen_tri(&device, &queue);
|
|
460
|
+
sys.submit_draw(
|
|
461
|
+
&device, &queue, &joint_buf,
|
|
462
|
+
handle, 1, 0, identity, identity, identity, [1.0; 4], [0; 4],
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
let (rt_w, rt_h) = (64u32, 64u32);
|
|
466
|
+
let hdr_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
467
|
+
label: Some("test_arr_hdr"),
|
|
468
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
469
|
+
mip_level_count: 1, sample_count: 1,
|
|
470
|
+
dimension: wgpu::TextureDimension::D2,
|
|
471
|
+
format: wgpu::TextureFormat::Rgba16Float,
|
|
472
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
|
473
|
+
view_formats: &[],
|
|
474
|
+
});
|
|
475
|
+
let hdr_view = hdr_rt.create_view(&Default::default());
|
|
476
|
+
let depth_rt = device.create_texture(&wgpu::TextureDescriptor {
|
|
477
|
+
label: Some("test_arr_depth"),
|
|
478
|
+
size: wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
479
|
+
mip_level_count: 1, sample_count: 1,
|
|
480
|
+
dimension: wgpu::TextureDimension::D2,
|
|
481
|
+
format: formats::DEPTH_FORMAT,
|
|
482
|
+
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
|
483
|
+
view_formats: &[],
|
|
484
|
+
});
|
|
485
|
+
let depth_view = depth_rt.create_view(&Default::default());
|
|
486
|
+
|
|
487
|
+
let mut encoder = device.create_command_encoder(&Default::default());
|
|
488
|
+
{
|
|
489
|
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
490
|
+
label: Some("test_arr_pass"),
|
|
491
|
+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
|
492
|
+
view: &hdr_view,
|
|
493
|
+
resolve_target: None,
|
|
494
|
+
depth_slice: None,
|
|
495
|
+
ops: wgpu::Operations {
|
|
496
|
+
// Clear to RED: if the draw never lands, or the stub
|
|
497
|
+
// (white/black) is sampled, the assert below fails loudly
|
|
498
|
+
// instead of accidentally passing.
|
|
499
|
+
load: wgpu::LoadOp::Clear(wgpu::Color { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }),
|
|
500
|
+
store: wgpu::StoreOp::Store,
|
|
501
|
+
},
|
|
502
|
+
})],
|
|
503
|
+
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
|
504
|
+
view: &depth_view,
|
|
505
|
+
depth_ops: Some(wgpu::Operations {
|
|
506
|
+
load: wgpu::LoadOp::Clear(1.0),
|
|
507
|
+
store: wgpu::StoreOp::Store,
|
|
508
|
+
}),
|
|
509
|
+
stencil_ops: None,
|
|
510
|
+
}),
|
|
511
|
+
timestamp_writes: None,
|
|
512
|
+
occlusion_query_set: None,
|
|
513
|
+
multiview_mask: None,
|
|
514
|
+
});
|
|
515
|
+
sys.dispatch_translucent(&mut pass, |mh, _idx| {
|
|
516
|
+
if mh == 1 { Some((&vb, &ib, icount)) } else { None }
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let bpr = ((rt_w * 8) + 255) & !255;
|
|
521
|
+
let staging = device.create_buffer(&wgpu::BufferDescriptor {
|
|
522
|
+
label: Some("test_arr_staging"),
|
|
523
|
+
size: (bpr * rt_h) as u64,
|
|
524
|
+
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
|
525
|
+
mapped_at_creation: false,
|
|
526
|
+
});
|
|
527
|
+
encoder.copy_texture_to_buffer(
|
|
528
|
+
wgpu::TexelCopyTextureInfo {
|
|
529
|
+
texture: &hdr_rt, mip_level: 0,
|
|
530
|
+
origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All,
|
|
531
|
+
},
|
|
532
|
+
wgpu::TexelCopyBufferInfo {
|
|
533
|
+
buffer: &staging,
|
|
534
|
+
layout: wgpu::TexelCopyBufferLayout {
|
|
535
|
+
offset: 0, bytes_per_row: Some(bpr), rows_per_image: Some(rt_h),
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
wgpu::Extent3d { width: rt_w, height: rt_h, depth_or_array_layers: 1 },
|
|
539
|
+
);
|
|
540
|
+
queue.submit(std::iter::once(encoder.finish()));
|
|
541
|
+
|
|
542
|
+
let slice = staging.slice(..);
|
|
543
|
+
let (tx, rx) = std::sync::mpsc::channel();
|
|
544
|
+
slice.map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
|
|
545
|
+
let _ = device.poll(wgpu::PollType::Wait { submission_index: None, timeout: None });
|
|
546
|
+
rx.recv().expect("map sender").expect("map failed");
|
|
547
|
+
let data = slice.get_mapped_range();
|
|
548
|
+
|
|
549
|
+
let texel = ((rt_h / 2) * bpr) as usize + ((rt_w / 2) as usize) * 8;
|
|
550
|
+
let r = f16_to_f32(u16::from_le_bytes([data[texel], data[texel + 1]]));
|
|
551
|
+
let g = f16_to_f32(u16::from_le_bytes([data[texel + 2], data[texel + 3]]));
|
|
552
|
+
drop(data);
|
|
553
|
+
staging.unmap();
|
|
554
|
+
|
|
555
|
+
assert!(
|
|
556
|
+
g > 0.9 && r < 0.1,
|
|
557
|
+
"material sampled the stub array, not the linked one \
|
|
558
|
+
(expected green, got r={r:.2} g={g:.2}) — set_user_params clobbered \
|
|
559
|
+
the texture-array binding",
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
#[cfg(test)]
|
|
565
|
+
mod translucent_sort_tests {
|
|
566
|
+
use super::*;
|
|
567
|
+
|
|
568
|
+
fn cmd(material: MaterialHandle, view_depth: f32) -> MaterialDrawCommand {
|
|
569
|
+
MaterialDrawCommand {
|
|
570
|
+
material,
|
|
571
|
+
mesh_handle: 0,
|
|
572
|
+
mesh_idx: 0,
|
|
573
|
+
draw_slot: 0,
|
|
574
|
+
view_depth,
|
|
575
|
+
instance: None,
|
|
576
|
+
// Identity — the sort under test only reads view_depth.
|
|
577
|
+
model: [
|
|
578
|
+
[1.0, 0.0, 0.0, 0.0],
|
|
579
|
+
[0.0, 1.0, 0.0, 0.0],
|
|
580
|
+
[0.0, 0.0, 1.0, 0.0],
|
|
581
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
582
|
+
],
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#[test]
|
|
587
|
+
fn sorts_back_to_front_keeping_equal_depth_submission_order() {
|
|
588
|
+
let mut ms_cmds = vec![
|
|
589
|
+
cmd(1, 5.0),
|
|
590
|
+
cmd(2, 20.0),
|
|
591
|
+
cmd(3, 10.0),
|
|
592
|
+
cmd(4, 10.0), // same depth as material 3 — must stay after it
|
|
593
|
+
cmd(5, 1.0),
|
|
594
|
+
];
|
|
595
|
+
// exercise the same comparator sort_translucent uses
|
|
596
|
+
ms_cmds.sort_by(|a, b| b.view_depth.total_cmp(&a.view_depth));
|
|
597
|
+
let order: Vec<MaterialHandle> = ms_cmds.iter().map(|c| c.material).collect();
|
|
598
|
+
assert_eq!(order, vec![2, 3, 4, 1, 5]);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//! wasm32-only material bind-group helpers, split out of
|
|
2
|
+
//! `material_system.rs` to keep that file under the 2000-line policy (the
|
|
3
|
+
//! same reason `material_system_tests.rs` is a `#[path]` child). The whole
|
|
4
|
+
//! module is gated `#[cfg(target_arch = "wasm32")]` at its declaration in
|
|
5
|
+
//! `renderer/mod.rs`, so nothing here compiles on native.
|
|
6
|
+
|
|
7
|
+
/// EN-063 — wasm32-only per_frame bind group builder: the PerFrame UBO
|
|
8
|
+
/// at binding 0 plus the seven folded SceneInputs resources at
|
|
9
|
+
/// `WASM_SCENE_INPUTS_BASE..+6` (order and types mirror
|
|
10
|
+
/// `update_scene_inputs` / `create_scene_inputs_layout` exactly). The
|
|
11
|
+
/// single creation path for every bind group made against the wasm32
|
|
12
|
+
/// `abi_per_frame` layout — that layout requires all eight entries.
|
|
13
|
+
#[allow(clippy::too_many_arguments)]
|
|
14
|
+
pub(crate) fn build_per_frame_bg_wasm(
|
|
15
|
+
device: &wgpu::Device,
|
|
16
|
+
layout: &wgpu::BindGroupLayout,
|
|
17
|
+
per_frame_buffer: &wgpu::Buffer,
|
|
18
|
+
scene_color_view: &wgpu::TextureView,
|
|
19
|
+
scene_color_samp: &wgpu::Sampler,
|
|
20
|
+
scene_depth_view: &wgpu::TextureView,
|
|
21
|
+
scene_depth_samp: &wgpu::Sampler,
|
|
22
|
+
impulse_view: &wgpu::TextureView,
|
|
23
|
+
impulse_samp: &wgpu::Sampler,
|
|
24
|
+
motion_vectors_view: &wgpu::TextureView,
|
|
25
|
+
) -> wgpu::BindGroup {
|
|
26
|
+
use super::material_pipeline::WASM_SCENE_INPUTS_BASE as B;
|
|
27
|
+
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
28
|
+
label: Some("material_per_frame_bg"),
|
|
29
|
+
layout,
|
|
30
|
+
entries: &[
|
|
31
|
+
wgpu::BindGroupEntry { binding: 0, resource: per_frame_buffer.as_entire_binding() },
|
|
32
|
+
wgpu::BindGroupEntry { binding: B, resource: wgpu::BindingResource::TextureView(scene_color_view) },
|
|
33
|
+
wgpu::BindGroupEntry { binding: B + 1, resource: wgpu::BindingResource::Sampler(scene_color_samp) },
|
|
34
|
+
wgpu::BindGroupEntry { binding: B + 2, resource: wgpu::BindingResource::TextureView(scene_depth_view) },
|
|
35
|
+
wgpu::BindGroupEntry { binding: B + 3, resource: wgpu::BindingResource::Sampler(scene_depth_samp) },
|
|
36
|
+
wgpu::BindGroupEntry { binding: B + 4, resource: wgpu::BindingResource::TextureView(impulse_view) },
|
|
37
|
+
wgpu::BindGroupEntry { binding: B + 5, resource: wgpu::BindingResource::Sampler(impulse_samp) },
|
|
38
|
+
wgpu::BindGroupEntry { binding: B + 6, resource: wgpu::BindingResource::TextureView(motion_vectors_view) },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
}
|