1use anyhow::{Context, Result};
8use bytemuck::{Pod, Zeroable};
9use std::collections::{HashMap, HashSet};
10use std::path::{Path, PathBuf};
11use wgpu::util::DeviceExt;
12use winit::window::Window;
13
14use crate::assets::load_image_any;
15use crate::image_manager::{ImageId, ImageManager};
16use crate::layer::{
17 ClipRect, RenderSprite, SpriteBlend, SpriteFit, SpriteRuntimeLight, SpriteSizeMode,
18};
19use crate::mesh3d::{load_mesh_asset, MeshAsset};
20use crate::render_math::sprite_quad_points;
21
22#[repr(C)]
23#[derive(Clone, Copy, Debug, Pod, Zeroable)]
24struct Vertex {
25 pos: [f32; 3],
26 uv: [f32; 2],
27 uv_aux: [f32; 2],
28 alpha: f32,
29 effects1: [f32; 4],
30 effects2: [f32; 4],
31 effects3: [f32; 4],
32 effects4: [f32; 4],
33 effects5: [f32; 4],
34 effects6: [f32; 4],
35 effects7: [f32; 4],
36 effects8: [f32; 4],
37 effects9: [f32; 4],
38 effects10: [f32; 4],
39 effects11: [f32; 4],
40 world_pos: [f32; 4],
41 world_normal: [f32; 4],
42 world_tangent: [f32; 4],
43 world_binormal: [f32; 4],
44 shadow_pos: [f32; 4],
45 bone_indices: [f32; 4],
46 bone_weights: [f32; 4],
47 light_pos_kind: [f32; 4],
48 light_dir_shadow: [f32; 4],
49 light_atten: [f32; 4],
50 light_cone: [f32; 4],
51}
52
53#[repr(C)]
54#[derive(Clone, Copy, Debug, Pod, Zeroable)]
55struct VsUniform {
56 model_col0: [f32; 4],
57 model_col1: [f32; 4],
58 model_col2: [f32; 4],
59 model_col3: [f32; 4],
60 normal_col0: [f32; 4],
61 normal_col1: [f32; 4],
62 normal_col2: [f32; 4],
63 frame_col0: [f32; 4],
64 frame_col1: [f32; 4],
65 frame_col2: [f32; 4],
66 frame_col3: [f32; 4],
67 frame_normal0: [f32; 4],
68 frame_normal1: [f32; 4],
69 frame_normal2: [f32; 4],
70 camera_eye: [f32; 4],
71 camera_forward: [f32; 4],
72 camera_right: [f32; 4],
73 camera_up: [f32; 4],
74 camera_params: [f32; 4],
75 shadow_eye: [f32; 4],
76 shadow_forward: [f32; 4],
77 shadow_right: [f32; 4],
78 shadow_up: [f32; 4],
79 shadow_params: [f32; 4],
80 mtrl_diffuse: [f32; 4],
81 mtrl_ambient: [f32; 4],
82 mtrl_specular: [f32; 4],
83 mtrl_emissive: [f32; 4],
84 mtrl_params: [f32; 4],
85 mtrl_rim: [f32; 4],
86 mtrl_extra: [f32; 4],
87 light_diffuse_u: [f32; 4],
88 light_ambient_u: [f32; 4],
89 light_specular_u: [f32; 4],
90 mesh_flags: [f32; 4],
91 mesh_mrbd: [f32; 4],
92 mesh_rgb_rate: [f32; 4],
93 mesh_add_rgb: [f32; 4],
94 mesh_misc: [f32; 4],
95 mesh_light_counts: [f32; 4],
96 dir_light_diffuse: [[f32; 4]; MAX_BATCH_LIGHTS],
97 dir_light_ambient: [[f32; 4]; MAX_BATCH_LIGHTS],
98 dir_light_specular: [[f32; 4]; MAX_BATCH_LIGHTS],
99 dir_light_dir: [[f32; 4]; MAX_BATCH_LIGHTS],
100 point_light_diffuse: [[f32; 4]; MAX_BATCH_LIGHTS],
101 point_light_ambient: [[f32; 4]; MAX_BATCH_LIGHTS],
102 point_light_specular: [[f32; 4]; MAX_BATCH_LIGHTS],
103 point_light_pos: [[f32; 4]; MAX_BATCH_LIGHTS],
104 point_light_atten: [[f32; 4]; MAX_BATCH_LIGHTS],
105 spot_light_diffuse: [[f32; 4]; MAX_BATCH_LIGHTS],
106 spot_light_ambient: [[f32; 4]; MAX_BATCH_LIGHTS],
107 spot_light_specular: [[f32; 4]; MAX_BATCH_LIGHTS],
108 spot_light_pos: [[f32; 4]; MAX_BATCH_LIGHTS],
109 spot_light_dir: [[f32; 4]; MAX_BATCH_LIGHTS],
110 spot_light_atten: [[f32; 4]; MAX_BATCH_LIGHTS],
111 spot_light_cone: [[f32; 4]; MAX_BATCH_LIGHTS],
112 flags: [f32; 4],
113}
114
115impl VsUniform {
116 fn for_2d(win_w: f32, win_h: f32) -> Self {
117 Self {
118 model_col0: [1.0, 0.0, 0.0, 0.0],
119 model_col1: [0.0, 1.0, 0.0, 0.0],
120 model_col2: [0.0, 0.0, 1.0, 0.0],
121 model_col3: [0.0, 0.0, 0.0, 1.0],
122 normal_col0: [1.0, 0.0, 0.0, 0.0],
123 normal_col1: [0.0, 1.0, 0.0, 0.0],
124 normal_col2: [0.0, 0.0, 1.0, 0.0],
125 frame_col0: [1.0, 0.0, 0.0, 0.0],
126 frame_col1: [0.0, 1.0, 0.0, 0.0],
127 frame_col2: [0.0, 0.0, 1.0, 0.0],
128 frame_col3: [0.0, 0.0, 0.0, 1.0],
129 frame_normal0: [1.0, 0.0, 0.0, 0.0],
130 frame_normal1: [0.0, 1.0, 0.0, 0.0],
131 frame_normal2: [0.0, 0.0, 1.0, 0.0],
132 camera_eye: [0.0, 0.0, 0.0, 0.0],
133 camera_forward: [0.0, 0.0, 1.0, 0.0],
134 camera_right: [1.0, 0.0, 0.0, 0.0],
135 camera_up: [0.0, 1.0, 0.0, 0.0],
136 camera_params: [0.0, 0.0, win_w, win_h],
137 shadow_eye: [0.0, 0.0, 0.0, 0.0],
138 shadow_forward: [0.0, 0.0, 1.0, 0.0],
139 shadow_right: [1.0, 0.0, 0.0, 0.0],
140 shadow_up: [0.0, 1.0, 0.0, 0.0],
141 shadow_params: [1.0, 1.0, 0.0, 0.0],
142 mtrl_diffuse: [1.0, 1.0, 1.0, 1.0],
143 mtrl_ambient: [1.0, 1.0, 1.0, 1.0],
144 mtrl_specular: [0.0, 0.0, 0.0, 1.0],
145 mtrl_emissive: [0.0, 0.0, 0.0, 1.0],
146 mtrl_params: [16.0, 0.0, 0.0, 0.0],
147 mtrl_rim: [1.0, 1.0, 1.0, 1.0],
148 mtrl_extra: [0.016, 0.001, 0.0, 0.0],
149 light_diffuse_u: [1.0, 1.0, 1.0, 1.0],
150 light_ambient_u: [0.0, 0.0, 0.0, 1.0],
151 light_specular_u: [0.0, 0.0, 0.0, 1.0],
152 mesh_flags: [1.0, 0.0, 0.0, 0.0],
153 mesh_mrbd: [0.0, 0.0, 0.0, 0.0],
154 mesh_rgb_rate: [0.0, 0.0, 0.0, 0.0],
155 mesh_add_rgb: [0.0, 0.0, 0.0, 0.0],
156 mesh_misc: [1.0, 0.03, 0.0, 0.0],
157 mesh_light_counts: [0.0, 0.0, 0.0, 0.0],
158 dir_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
159 dir_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
160 dir_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
161 dir_light_dir: [[0.0; 4]; MAX_BATCH_LIGHTS],
162 point_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
163 point_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
164 point_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
165 point_light_pos: [[0.0; 4]; MAX_BATCH_LIGHTS],
166 point_light_atten: [[0.0; 4]; MAX_BATCH_LIGHTS],
167 spot_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
168 spot_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
169 spot_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
170 spot_light_pos: [[0.0; 4]; MAX_BATCH_LIGHTS],
171 spot_light_dir: [[0.0; 4]; MAX_BATCH_LIGHTS],
172 spot_light_atten: [[0.0; 4]; MAX_BATCH_LIGHTS],
173 spot_light_cone: [[0.0; 4]; MAX_BATCH_LIGHTS],
174 flags: [0.0, 0.0, 0.0, 0.0],
175 }
176 }
177}
178
179#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
180fn set_sprite2d_effect_uniforms(
181 u: &mut VsUniform,
182 effects1: [f32; 4],
183 effects2: [f32; 4],
184 effects3: [f32; 4],
185 effects4: [f32; 4],
186 effects5: [f32; 4],
187 effects6: [f32; 4],
188 effects7: [f32; 4],
189 effects8: [f32; 4],
190 effects9: [f32; 4],
191 effects10: [f32; 4],
192 effects11: [f32; 4],
193) {
194 u.mesh_mrbd = effects1;
195 u.mesh_rgb_rate = effects2;
196 u.mesh_add_rgb = effects3;
197 u.mesh_flags = effects4;
198 u.mtrl_params = effects5;
199 u.mtrl_rim = effects6;
200 u.mtrl_diffuse = effects7;
201 u.mtrl_ambient = effects8;
202 u.mtrl_specular = effects9;
203 u.dir_light_diffuse[0] = effects10;
204 u.dir_light_ambient[0] = effects11;
205}
206
207#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
208fn sprite2d_uniform_for_effects(
209 win_w: f32,
210 win_h: f32,
211 effects1: [f32; 4],
212 effects2: [f32; 4],
213 effects3: [f32; 4],
214 effects4: [f32; 4],
215 effects5: [f32; 4],
216 effects6: [f32; 4],
217 effects7: [f32; 4],
218 effects8: [f32; 4],
219 effects9: [f32; 4],
220 effects10: [f32; 4],
221 effects11: [f32; 4],
222) -> VsUniform {
223 let mut u = VsUniform::for_2d(win_w, win_h);
224 set_sprite2d_effect_uniforms(
225 &mut u, effects1, effects2, effects3, effects4, effects5, effects6, effects7, effects8,
226 effects9, effects10, effects11,
227 );
228 u
229}
230
231#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
232fn plain_sprite2d_uniform(win_w: f32, win_h: f32) -> VsUniform {
233 sprite2d_uniform_for_effects(
234 win_w,
235 win_h,
236 [1.0, 0.0, 0.0, 0.0],
237 [0.0; 4],
238 [0.0; 4],
239 [0.0; 4],
240 [0.0; 4],
241 [0.0; 4],
242 [0.0; 4],
243 [0.0; 4],
244 [0.0; 4],
245 [0.0; 4],
246 [0.0; 4],
247 )
248}
249
250const MAX_BONES: usize = 64;
251const MAX_BATCH_LIGHTS: usize = 4;
252
253#[repr(C)]
254#[derive(Clone, Copy, Debug, Pod, Zeroable)]
255struct BoneUniform {
256 matrices: [[[f32; 4]; 4]; MAX_BONES],
257}
258
259impl BoneUniform {
260 fn zero() -> Self {
261 Self {
262 matrices: [[[0.0; 4]; 4]; MAX_BONES],
263 }
264 }
265
266 fn from_cols_list(cols: &[[[f32; 4]; 4]]) -> Self {
267 let mut out = Self::zero();
268 for (dst, src) in out.matrices.iter_mut().zip(cols.iter()) {
269 *dst = *src;
270 }
271 out
272 }
273}
274
275impl Vertex {
276 const ATTRS: [wgpu::VertexAttribute; 26] = wgpu::vertex_attr_array![
277 0 => Float32x3,
278 1 => Float32x2,
279 2 => Float32x2,
280 3 => Float32,
281 4 => Float32x4,
282 5 => Float32x4,
283 6 => Float32x4,
284 7 => Float32x4,
285 8 => Float32x4,
286 9 => Float32x4,
287 10 => Float32x4,
288 11 => Float32x4,
289 12 => Float32x4,
290 13 => Float32x4,
291 14 => Float32x4,
292 15 => Float32x4,
293 16 => Float32x4,
294 17 => Float32x4,
295 18 => Float32x4,
296 19 => Float32x4,
297 20 => Float32x4,
298 21 => Float32x4,
299 22 => Float32x4,
300 23 => Float32x4,
301 24 => Float32x4,
302 25 => Float32x4
303 ];
304
305 fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
306 wgpu::VertexBufferLayout {
307 array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
308 step_mode: wgpu::VertexStepMode::Vertex,
309 attributes: &Self::ATTRS,
310 }
311 }
312}
313
314#[repr(C)]
315#[derive(Clone, Copy, Debug, Pod, Zeroable)]
316struct VertexSprite2dData {
317 pos: [f32; 3],
318 uv: [f32; 2],
319 uv_aux: [f32; 2],
320 alpha: f32,
321 effects1: [f32; 4],
322 effects2: [f32; 4],
323 effects3: [f32; 4],
324 effects4: [f32; 4],
325 effects5: [f32; 4],
326 effects6: [f32; 4],
327 effects7: [f32; 4],
328 effects8: [f32; 4],
329 effects9: [f32; 4],
330 effects10: [f32; 4],
331 effects11: [f32; 4],
332}
333
334impl From<Vertex> for VertexSprite2dData {
335 fn from(v: Vertex) -> Self {
336 Self {
337 pos: v.pos,
338 uv: v.uv,
339 uv_aux: v.uv_aux,
340 alpha: v.alpha,
341 effects1: v.effects1,
342 effects2: v.effects2,
343 effects3: v.effects3,
344 effects4: v.effects4,
345 effects5: v.effects5,
346 effects6: v.effects6,
347 effects7: v.effects7,
348 effects8: v.effects8,
349 effects9: v.effects9,
350 effects10: v.effects10,
351 effects11: v.effects11,
352 }
353 }
354}
355
356struct VertexSprite2d;
357
358impl VertexSprite2d {
359 const ATTRS: [wgpu::VertexAttribute; 15] = wgpu::vertex_attr_array![
360 0 => Float32x3,
361 1 => Float32x2,
362 2 => Float32x2,
363 3 => Float32,
364 4 => Float32x4,
365 5 => Float32x4,
366 6 => Float32x4,
367 7 => Float32x4,
368 8 => Float32x4,
369 9 => Float32x4,
370 10 => Float32x4,
371 11 => Float32x4,
372 12 => Float32x4,
373 13 => Float32x4,
374 14 => Float32x4
375 ];
376
377 fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
378 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
379 let array_stride = std::mem::size_of::<VertexSprite2dData>() as wgpu::BufferAddress;
380 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
381 let array_stride = std::mem::size_of::<Vertex>() as wgpu::BufferAddress;
382
383 wgpu::VertexBufferLayout {
384 array_stride,
385 step_mode: wgpu::VertexStepMode::Vertex,
386 attributes: &Self::ATTRS,
387 }
388 }
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
392enum TechniqueSpecial {
393 None,
394 Overlay,
395 WipeMosaic,
396 WipeRasterH,
397 WipeRasterV,
398 WipeExplosionBlur,
399 WipeShimi,
400 WipeShimiInv,
401 WipeCrossMosaic,
402 WipeCrossRasterH,
403 WipeCrossRasterV,
404 WipeCrossExplosionBlur,
405 Mesh,
406 SkinnedMesh,
407 Shadow,
408}
409
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
411enum EffectProgram {
412 Sprite2D,
413 OverlayGpu,
414 WipeMosaic,
415 WipeRasterH,
416 WipeRasterV,
417 WipeExplosionBlur,
418 WipeShimi,
419 WipeShimiInv,
420 WipeCrossMosaic,
421 WipeCrossRasterH,
422 WipeCrossRasterV,
423 WipeCrossExplosionBlur,
424 MeshStaticUnlit,
425 MeshStaticLambert,
426 MeshStaticBlinnPhong,
427 MeshStaticPerPixelBlinnPhong,
428 MeshStaticPerPixelHalfLambert,
429 MeshStaticToon,
430 MeshStaticFixedFunction,
431 MeshStaticPerPixelFixedFunction,
432 MeshStaticBump,
433 MeshStaticParallax,
434 MeshSkinnedUnlit,
435 MeshSkinnedLambert,
436 MeshSkinnedBlinnPhong,
437 MeshSkinnedPerPixelBlinnPhong,
438 MeshSkinnedPerPixelHalfLambert,
439 MeshSkinnedToon,
440 MeshSkinnedFixedFunction,
441 MeshSkinnedPerPixelFixedFunction,
442 MeshSkinnedBump,
443 MeshSkinnedParallax,
444 ShadowStatic,
445 ShadowSkinned,
446}
447
448impl EffectProgram {
449 fn uses_sprite2d_layout(self) -> bool {
450 matches!(
451 self,
452 EffectProgram::Sprite2D
453 | EffectProgram::OverlayGpu
454 | EffectProgram::WipeMosaic
455 | EffectProgram::WipeRasterH
456 | EffectProgram::WipeRasterV
457 | EffectProgram::WipeExplosionBlur
458 | EffectProgram::WipeShimi
459 | EffectProgram::WipeShimiInv
460 | EffectProgram::WipeCrossMosaic
461 | EffectProgram::WipeCrossRasterH
462 | EffectProgram::WipeCrossRasterV
463 | EffectProgram::WipeCrossExplosionBlur
464 )
465 }
466
467 fn vertex_entry(self) -> &'static str {
468 match self {
469 EffectProgram::Sprite2D
470 | EffectProgram::OverlayGpu
471 | EffectProgram::WipeMosaic
472 | EffectProgram::WipeRasterH
473 | EffectProgram::WipeRasterV
474 | EffectProgram::WipeExplosionBlur
475 | EffectProgram::WipeShimi
476 | EffectProgram::WipeShimiInv
477 | EffectProgram::WipeCrossMosaic
478 | EffectProgram::WipeCrossRasterH
479 | EffectProgram::WipeCrossRasterV
480 | EffectProgram::WipeCrossExplosionBlur => "vs_sprite_2d",
481 EffectProgram::MeshStaticUnlit
482 | EffectProgram::MeshStaticLambert
483 | EffectProgram::MeshStaticBlinnPhong
484 | EffectProgram::MeshStaticPerPixelBlinnPhong
485 | EffectProgram::MeshStaticPerPixelHalfLambert
486 | EffectProgram::MeshStaticToon
487 | EffectProgram::MeshStaticFixedFunction
488 | EffectProgram::MeshStaticPerPixelFixedFunction
489 | EffectProgram::MeshStaticBump
490 | EffectProgram::MeshStaticParallax => "vs_mesh_static",
491 EffectProgram::MeshSkinnedUnlit
492 | EffectProgram::MeshSkinnedLambert
493 | EffectProgram::MeshSkinnedBlinnPhong
494 | EffectProgram::MeshSkinnedPerPixelBlinnPhong
495 | EffectProgram::MeshSkinnedPerPixelHalfLambert
496 | EffectProgram::MeshSkinnedToon
497 | EffectProgram::MeshSkinnedFixedFunction
498 | EffectProgram::MeshSkinnedPerPixelFixedFunction
499 | EffectProgram::MeshSkinnedBump
500 | EffectProgram::MeshSkinnedParallax => "vs_mesh_skinned",
501 EffectProgram::ShadowStatic => "vs_shadow_static",
502 EffectProgram::ShadowSkinned => "vs_shadow_skinned",
503 }
504 }
505
506 fn fragment_entry(self) -> &'static str {
507 match self {
508 EffectProgram::Sprite2D => "fs_sprite_2d",
509 EffectProgram::OverlayGpu => "fs_overlay_gpu",
510 EffectProgram::WipeMosaic => "fs_wipe_mosaic",
511 EffectProgram::WipeRasterH => "fs_wipe_raster_h",
512 EffectProgram::WipeRasterV => "fs_wipe_raster_v",
513 EffectProgram::WipeExplosionBlur => "fs_wipe_explosion_blur",
514 EffectProgram::WipeShimi => "fs_wipe_shimi",
515 EffectProgram::WipeShimiInv => "fs_wipe_shimi_inv",
516 EffectProgram::WipeCrossMosaic => "fs_wipe_cross_mosaic",
517 EffectProgram::WipeCrossRasterH => "fs_wipe_cross_raster_h",
518 EffectProgram::WipeCrossRasterV => "fs_wipe_cross_raster_v",
519 EffectProgram::WipeCrossExplosionBlur => "fs_wipe_cross_explosion_blur",
520 EffectProgram::MeshStaticUnlit | EffectProgram::MeshSkinnedUnlit => "fs_mesh_unlit",
521 EffectProgram::MeshStaticLambert | EffectProgram::MeshSkinnedLambert => {
522 "fs_mesh_lambert"
523 }
524 EffectProgram::MeshStaticBlinnPhong | EffectProgram::MeshSkinnedBlinnPhong => {
525 "fs_mesh_blinn_phong"
526 }
527 EffectProgram::MeshStaticPerPixelBlinnPhong
528 | EffectProgram::MeshSkinnedPerPixelBlinnPhong => "fs_mesh_pp_blinn_phong",
529 EffectProgram::MeshStaticPerPixelHalfLambert
530 | EffectProgram::MeshSkinnedPerPixelHalfLambert => "fs_mesh_pp_half_lambert",
531 EffectProgram::MeshStaticToon | EffectProgram::MeshSkinnedToon => "fs_mesh_toon",
532 EffectProgram::MeshStaticFixedFunction | EffectProgram::MeshSkinnedFixedFunction => {
533 "fs_mesh_ffp"
534 }
535 EffectProgram::MeshStaticPerPixelFixedFunction
536 | EffectProgram::MeshSkinnedPerPixelFixedFunction => "fs_mesh_pp_ffp",
537 EffectProgram::MeshStaticBump | EffectProgram::MeshSkinnedBump => "fs_mesh_bump",
538 EffectProgram::MeshStaticParallax | EffectProgram::MeshSkinnedParallax => {
539 "fs_mesh_parallax"
540 }
541 EffectProgram::ShadowStatic | EffectProgram::ShadowSkinned => "fs_shadow_map",
542 }
543 }
544
545 fn short_name(self) -> &'static str {
546 match self {
547 EffectProgram::Sprite2D => "sprite2d",
548 EffectProgram::OverlayGpu => "overlay_gpu",
549 EffectProgram::WipeMosaic => "wipe_mosaic",
550 EffectProgram::WipeRasterH => "wipe_raster_h",
551 EffectProgram::WipeRasterV => "wipe_raster_v",
552 EffectProgram::WipeExplosionBlur => "wipe_explosion_blur",
553 EffectProgram::WipeShimi => "wipe_shimi",
554 EffectProgram::WipeShimiInv => "wipe_shimi_inv",
555 EffectProgram::WipeCrossMosaic => "wipe_cross_mosaic",
556 EffectProgram::WipeCrossRasterH => "wipe_cross_raster_h",
557 EffectProgram::WipeCrossRasterV => "wipe_cross_raster_v",
558 EffectProgram::WipeCrossExplosionBlur => "wipe_cross_explosion_blur",
559 EffectProgram::MeshStaticUnlit => "mesh_static_unlit",
560 EffectProgram::MeshStaticLambert => "mesh_static_lambert",
561 EffectProgram::MeshStaticBlinnPhong => "mesh_static_blinn_phong",
562 EffectProgram::MeshStaticPerPixelBlinnPhong => "mesh_static_pp_blinn_phong",
563 EffectProgram::MeshStaticPerPixelHalfLambert => "mesh_static_pp_half_lambert",
564 EffectProgram::MeshStaticToon => "mesh_static_toon",
565 EffectProgram::MeshStaticFixedFunction => "mesh_static_ffp",
566 EffectProgram::MeshStaticPerPixelFixedFunction => "mesh_static_pp_ffp",
567 EffectProgram::MeshStaticBump => "mesh_static_bump",
568 EffectProgram::MeshStaticParallax => "mesh_static_parallax",
569 EffectProgram::MeshSkinnedUnlit => "mesh_skinned_unlit",
570 EffectProgram::MeshSkinnedLambert => "mesh_skinned_lambert",
571 EffectProgram::MeshSkinnedBlinnPhong => "mesh_skinned_blinn_phong",
572 EffectProgram::MeshSkinnedPerPixelBlinnPhong => "mesh_skinned_pp_blinn_phong",
573 EffectProgram::MeshSkinnedPerPixelHalfLambert => "mesh_skinned_pp_half_lambert",
574 EffectProgram::MeshSkinnedToon => "mesh_skinned_toon",
575 EffectProgram::MeshSkinnedFixedFunction => "mesh_skinned_ffp",
576 EffectProgram::MeshSkinnedPerPixelFixedFunction => "mesh_skinned_pp_ffp",
577 EffectProgram::MeshSkinnedBump => "mesh_skinned_bump",
578 EffectProgram::MeshSkinnedParallax => "mesh_skinned_parallax",
579 EffectProgram::ShadowStatic => "shadow_static",
580 EffectProgram::ShadowSkinned => "shadow_skinned",
581 }
582 }
583}
584
585#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
586struct TechniqueKey {
587 d3: bool,
588 light: bool,
589 fog: bool,
590 tex: u8,
591 diffuse: bool,
592 mrbd: bool,
593 rgb: bool,
594 tonecurve: bool,
595 mask: bool,
596 special: TechniqueSpecial,
597}
598
599#[derive(Debug, Clone, PartialEq, Eq, Hash)]
600struct PipelineKey {
601 technique: TechniqueKey,
602 blend: SpriteBlend,
603 alpha_blend: bool,
604 use_depth: bool,
605 cull_back: bool,
606 mesh_fx_variant: u64,
607 pipeline_name: String,
608 program: EffectProgram,
609}
610
611#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
612pub enum MeshDrawKind {
613 SpriteQuad,
614 StaticMesh,
615 SkinnedMesh,
616 ShadowCaster,
617}
618
619#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
620pub struct MeshMaterialKey {
621 pub lighting: bool,
622 pub fog: bool,
623 pub shadow: bool,
624 pub use_mesh_tex: bool,
625 pub use_mrbd: bool,
626 pub use_rgb: bool,
627 pub use_normal_tex: bool,
628 pub use_toon_tex: bool,
629 pub skinned: bool,
630}
631
632#[derive(Debug, Clone)]
633pub struct SkinnedPoseState {
634 pub world_matrix_count: usize,
635}
636
637#[derive(Debug)]
638pub struct Renderer {
639 pub surface: wgpu::Surface<'static>,
640 pub device: wgpu::Device,
641 pub queue: wgpu::Queue,
642 pub config: wgpu::SurfaceConfiguration,
643 logical_width: f32,
644 logical_height: f32,
645 scale_factor: f32,
646 surface_viewport: SurfaceViewport,
647
648 pipelines: HashMap<PipelineKey, wgpu::RenderPipeline>,
649 bind_group_layout: wgpu::BindGroupLayout,
650 shader: wgpu::ShaderModule,
651 pipeline_layout: wgpu::PipelineLayout,
652
653 vertex_buf: wgpu::Buffer,
654 vertex_capacity: usize,
655 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
656 vertex_sprite2d_buf: wgpu::Buffer,
657 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
658 vertex_sprite2d_capacity: usize,
659
660 textures: HashMap<ImageId, GpuTexture>,
661 external_textures: HashMap<PathBuf, GpuTexture>,
662 mesh_assets: HashMap<String, MeshAsset>,
663 default_aux: GpuTexture,
664 depth: DepthTexture,
665 scene_a: RenderTargetTexture,
666 scene_b: RenderTargetTexture,
667 shadow_map: RenderTargetTexture,
668 shadow_depth: DepthTexture,
669
670 verts: Vec<Vertex>,
671 draws: Vec<DrawCommand>,
672 debug_frame_serial: u64,
673}
674
675#[derive(Debug, Clone, Copy)]
676struct SurfaceViewport {
677 x: u32,
678 y: u32,
679 w: u32,
680 h: u32,
681}
682
683impl SurfaceViewport {
684 fn full(width: u32, height: u32) -> Self {
685 Self {
686 x: 0,
687 y: 0,
688 w: width.max(1),
689 h: height.max(1),
690 }
691 }
692}
693
694#[derive(Debug, Clone)]
695pub struct RendererDebugTexture {
696 pub key: String,
697 pub kind: String,
698 pub label: String,
699 pub usage: String,
700 pub usage_count: usize,
701 pub width: u32,
702 pub height: u32,
703 pub version: u64,
704 pub rgba: Vec<u8>,
705}
706
707#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
708enum RendererDebugRenderTarget {
709 SceneA,
710 SceneB,
711 ShadowMap,
712}
713
714#[derive(Debug, Clone, PartialEq, Eq, Hash)]
715enum RendererDebugTextureKey {
716 DefaultAux,
717 Image(ImageId),
718 External(PathBuf),
719 RenderTarget(RendererDebugRenderTarget),
720}
721
722#[derive(Debug, Clone)]
723struct PendingRendererDebugTexture {
724 order: usize,
725 kind: String,
726 label: String,
727 usage: Vec<String>,
728 width: u32,
729 height: u32,
730 version: u64,
731}
732
733#[derive(Debug)]
734struct DepthTexture {
735 _tex: wgpu::Texture,
736 view: wgpu::TextureView,
737}
738
739#[derive(Debug)]
740struct GpuTexture {
741 _tex: wgpu::Texture,
742 view: wgpu::TextureView,
743 sampler: wgpu::Sampler,
744 width: u32,
745 height: u32,
746 version: u64,
747}
748
749#[derive(Debug)]
750struct RenderTargetTexture {
751 _tex: wgpu::Texture,
752 view: wgpu::TextureView,
753 sampler: wgpu::Sampler,
754 width: u32,
755 height: u32,
756 format: wgpu::TextureFormat,
757}
758
759#[derive(Debug, Clone, Copy)]
760enum InternalColorTarget {
761 SceneA,
762 SceneB,
763 ShadowMap,
764}
765
766#[derive(Debug, Clone, Copy)]
767enum DepthTarget {
768 None,
769 Main,
770 Shadow,
771}
772
773#[derive(Debug, Clone, Copy)]
774enum BackdropTarget {
775 SceneA,
776 SceneB,
777}
778
779#[derive(Debug, Clone, Copy)]
780enum ColorTarget<'a> {
781 External(&'a wgpu::TextureView),
782 Internal(InternalColorTarget),
783}
784
785#[derive(Debug, Clone)]
786struct DrawCommand {
787 image_id: Option<ImageId>,
788 mesh_texture_path: Option<PathBuf>,
789 mesh_normal_texture_path: Option<PathBuf>,
790 mesh_toon_texture_path: Option<PathBuf>,
791 mask_image_id: Option<ImageId>,
792 tonecurve_image_id: Option<ImageId>,
793 fog_image_id: Option<ImageId>,
794 wipe_src_image_id: Option<ImageId>,
795 range: std::ops::Range<u32>,
796 scissor: Option<ScissorRect>,
797 pipeline_key: PipelineKey,
798 shadow_pipeline_name: Option<String>,
799 draw_kind: MeshDrawKind,
800 mesh_material_key: Option<MeshMaterialKey>,
801 shadow_cast: bool,
802 vs_uniform: VsUniform,
803 bone_uniform: BoneUniform,
804}
805
806#[derive(Debug, Clone, Copy, Default)]
807struct EffectGlobalValPackSemantic {
808 use_bone_uniform: bool,
809 use_shadow_tex: bool,
810 use_normal_tex: bool,
811 use_toon_tex: bool,
812}
813
814#[derive(Debug)]
815struct EffectResolvedResources<'a> {
816 base: &'a GpuTexture,
817 mask: &'a GpuTexture,
818 tone: &'a GpuTexture,
819 fog: &'a GpuTexture,
820 normal: &'a GpuTexture,
821 toon: &'a GpuTexture,
822 aux_view: &'a wgpu::TextureView,
823 aux_sampler: &'a wgpu::Sampler,
824 shadow_view: &'a wgpu::TextureView,
825 shadow_sampler: &'a wgpu::Sampler,
826 global_vals: EffectGlobalValPackSemantic,
827}
828
829#[derive(Debug, Copy, Clone)]
830struct ScissorRect {
831 x: u32,
832 y: u32,
833 w: u32,
834 h: u32,
835}
836
837fn uses_depth_pipeline(sprite: &crate::layer::Sprite) -> bool {
838 sprite.camera_enabled
839 || sprite.billboard
840 || sprite.z.abs() > f32::EPSILON
841 || sprite.pivot_z.abs() > f32::EPSILON
842 || (sprite.scale_z - 1.0).abs() > 1e-6
843 || sprite.rotate_x.abs() > f32::EPSILON
844 || sprite.rotate_y.abs() > f32::EPSILON
845}
846
847fn pipeline_cull_back(sprite: &crate::layer::Sprite, material_cull_disable: bool) -> bool {
848 uses_depth_pipeline(sprite) && sprite.culling && !material_cull_disable
849}
850
851fn sprite_has_mrbd(sprite: &crate::layer::Sprite) -> bool {
852 sprite.mono != 0 || sprite.reverse != 0 || sprite.bright != 0 || sprite.dark != 0
853}
854
855fn sprite_has_rgb(sprite: &crate::layer::Sprite) -> bool {
856 sprite.color_rate != 0
857 || sprite.color_add_r != 0
858 || sprite.color_add_g != 0
859 || sprite.color_add_b != 0
860 || sprite.color_r != 0
861 || sprite.color_g != 0
862 || sprite.color_b != 0
863}
864
865fn sprite_has_diffuse(sprite: &crate::layer::Sprite) -> bool {
866 uses_depth_pipeline(sprite) || sprite.tr != 255 || sprite.alpha != 255
867}
868
869fn is_mesh_special(special: TechniqueSpecial) -> bool {
870 matches!(
871 special,
872 TechniqueSpecial::Mesh | TechniqueSpecial::SkinnedMesh | TechniqueSpecial::Shadow
873 )
874}
875
876fn is_wipe_special(special: TechniqueSpecial) -> bool {
877 matches!(
878 special,
879 TechniqueSpecial::WipeMosaic
880 | TechniqueSpecial::WipeRasterH
881 | TechniqueSpecial::WipeRasterV
882 | TechniqueSpecial::WipeExplosionBlur
883 | TechniqueSpecial::WipeShimi
884 | TechniqueSpecial::WipeShimiInv
885 | TechniqueSpecial::WipeCrossMosaic
886 | TechniqueSpecial::WipeCrossRasterH
887 | TechniqueSpecial::WipeCrossRasterV
888 | TechniqueSpecial::WipeCrossExplosionBlur
889 )
890}
891
892fn wipe_special_for_sprite(
893 sprite: &crate::layer::Sprite,
894 has_wipe_src: bool,
895) -> Option<TechniqueSpecial> {
896 match (sprite.wipe_fx_mode, has_wipe_src) {
897 (1, _) => Some(TechniqueSpecial::WipeMosaic),
898 (2, _) => Some(TechniqueSpecial::WipeRasterH),
899 (3, _) => Some(TechniqueSpecial::WipeRasterV),
900 (4, _) => Some(TechniqueSpecial::WipeExplosionBlur),
901 (5, _) => Some(TechniqueSpecial::WipeShimi),
902 (6, _) => Some(TechniqueSpecial::WipeShimiInv),
903 (10, true) => Some(TechniqueSpecial::WipeCrossMosaic),
904 (11, true) => Some(TechniqueSpecial::WipeCrossRasterH),
905 (12, true) => Some(TechniqueSpecial::WipeCrossRasterV),
906 (13, true) => Some(TechniqueSpecial::WipeCrossExplosionBlur),
907 _ => None,
908 }
909}
910
911fn build_technique_key(
912 sprite: &crate::layer::Sprite,
913 has_mask: bool,
914 has_tonecurve: bool,
915 has_wipe_src: bool,
916 special_override: Option<TechniqueSpecial>,
917) -> TechniqueKey {
918 let d3 = uses_depth_pipeline(sprite);
919 let special = if let Some(s) = special_override {
920 s
921 } else if matches!(sprite.blend, SpriteBlend::Overlay) {
922 TechniqueSpecial::Overlay
923 } else if let Some(wipe) = wipe_special_for_sprite(sprite, has_wipe_src) {
924 wipe
925 } else if sprite.mesh_kind == 3 {
926 TechniqueSpecial::SkinnedMesh
927 } else if sprite.mesh_kind == 1 || sprite.mesh_kind == 2 {
928 TechniqueSpecial::Mesh
929 } else {
930 TechniqueSpecial::None
931 };
932 let light = d3 && sprite.light_enabled && !has_mask;
933 let fog = d3 && sprite.fog_enabled && !has_mask;
934 TechniqueKey {
935 d3,
936 light,
937 fog,
938 tex: u8::from(sprite.image_id.is_some()),
939 diffuse: sprite_has_diffuse(sprite),
940 mrbd: sprite_has_mrbd(sprite),
941 rgb: sprite_has_rgb(sprite),
942 tonecurve: has_tonecurve,
943 mask: has_mask,
944 special,
945 }
946}
947
948fn mesh_material_key_for_sprite(
949 sprite: &crate::layer::Sprite,
950 special: TechniqueSpecial,
951) -> Option<MeshMaterialKey> {
952 if !is_mesh_special(special) {
953 return None;
954 }
955 Some(MeshMaterialKey {
956 lighting: sprite.light_enabled,
957 fog: sprite.fog_enabled,
958 shadow: sprite.shadow_receive,
959 use_mesh_tex: sprite.image_id.is_some() || is_mesh_special(special),
960 use_mrbd: sprite_has_mrbd(sprite),
961 use_rgb: sprite_has_rgb(sprite),
962 use_normal_tex: false,
963 use_toon_tex: false,
964 skinned: matches!(special, TechniqueSpecial::SkinnedMesh),
965 })
966}
967
968fn mesh_material_key_for_batch(
969 sprite: &crate::layer::Sprite,
970 special: TechniqueSpecial,
971 batch: &crate::mesh3d::MeshGpuPrimitiveBatch,
972) -> Option<MeshMaterialKey> {
973 if !is_mesh_special(special) {
974 return None;
975 }
976 Some(MeshMaterialKey {
977 lighting: sprite.light_enabled,
978 fog: sprite.fog_enabled,
979 shadow: sprite.shadow_receive,
980 use_mesh_tex: batch.runtime_desc.material_key.use_mesh_tex,
981 use_mrbd: sprite_has_mrbd(sprite) || batch.runtime_desc.material_key.use_mrbd,
982 use_rgb: sprite_has_rgb(sprite) || batch.runtime_desc.material_key.use_rgb,
983 use_normal_tex: batch.runtime_desc.material_key.use_normal_tex,
984 use_toon_tex: batch.runtime_desc.material_key.use_toon_tex,
985 skinned: batch.runtime_desc.material_key.skinned
986 || matches!(special, TechniqueSpecial::SkinnedMesh),
987 })
988}
989
990fn shadow_pipeline_key(src: PipelineKey, pipeline_name: Option<&str>) -> PipelineKey {
991 let mut technique = src.technique;
992 technique.special = TechniqueSpecial::Shadow;
993 PipelineKey {
994 technique,
995 blend: SpriteBlend::Normal,
996 alpha_blend: false,
997 use_depth: true,
998 cull_back: src.cull_back,
999 mesh_fx_variant: src.mesh_fx_variant,
1000 pipeline_name: pipeline_name.unwrap_or("").to_string(),
1001 program: shadow_effect_program_from_source(src.program),
1002 }
1003}
1004
1005#[derive(Clone, Copy)]
1006struct RVec3 {
1007 x: f32,
1008 y: f32,
1009 z: f32,
1010}
1011
1012impl RVec3 {
1013 fn new(x: f32, y: f32, z: f32) -> Self {
1014 Self { x, y, z }
1015 }
1016 fn add(self, rhs: Self) -> Self {
1017 Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
1018 }
1019 fn sub(self, rhs: Self) -> Self {
1020 Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
1021 }
1022 fn dot(self, rhs: Self) -> f32 {
1023 self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
1024 }
1025 fn cross(self, rhs: Self) -> Self {
1026 Self::new(
1027 self.y * rhs.z - self.z * rhs.y,
1028 self.z * rhs.x - self.x * rhs.z,
1029 self.x * rhs.y - self.y * rhs.x,
1030 )
1031 }
1032 fn normalize(self) -> Self {
1033 let len = (self.dot(self)).sqrt();
1034 if len <= 1e-6 {
1035 Self::new(0.0, 0.0, 0.0)
1036 } else {
1037 Self::new(self.x / len, self.y / len, self.z / len)
1038 }
1039 }
1040}
1041
1042fn rrotate_x(v: RVec3, angle: f32) -> RVec3 {
1043 let (s, c) = angle.sin_cos();
1044 RVec3::new(v.x, v.y * c - v.z * s, v.y * s + v.z * c)
1045}
1046
1047fn rrotate_y(v: RVec3, angle: f32) -> RVec3 {
1048 let (s, c) = angle.sin_cos();
1049 RVec3::new(v.x * c + v.z * s, v.y, -v.x * s + v.z * c)
1050}
1051
1052fn rrotate_z(v: RVec3, angle: f32) -> RVec3 {
1053 let (s, c) = angle.sin_cos();
1054 RVec3::new(v.x * c - v.y * s, v.x * s + v.y * c, v.z)
1055}
1056
1057fn sprite_camera_basis(sprite: &crate::layer::Sprite) -> (RVec3, RVec3, RVec3, RVec3) {
1058 let eye = RVec3::new(
1059 sprite.camera_eye[0],
1060 sprite.camera_eye[1],
1061 sprite.camera_eye[2],
1062 );
1063 let target = RVec3::new(
1064 sprite.camera_target[0],
1065 sprite.camera_target[1],
1066 sprite.camera_target[2],
1067 );
1068 let up = RVec3::new(
1069 sprite.camera_up[0],
1070 sprite.camera_up[1],
1071 sprite.camera_up[2],
1072 );
1073 let forward = target.sub(eye).normalize();
1074 let right = up.cross(forward).normalize();
1075 let up2 = forward.cross(right).normalize();
1076 (eye, forward, right, up2)
1077}
1078
1079fn transform_model_point_world(
1080 sprite: &crate::layer::Sprite,
1081 local: [f32; 3],
1082 anchor_x: f32,
1083 anchor_y: f32,
1084) -> [f32; 3] {
1085 let mut p = RVec3::new(
1086 local[0] - sprite.pivot_x,
1087 local[1] - sprite.pivot_y,
1088 local[2] - sprite.pivot_z,
1089 );
1090 p.x *= sprite.scale_x;
1091 p.y *= sprite.scale_y;
1092 p.z *= sprite.scale_z;
1093 if sprite.billboard {
1094 let (_, _, right, up) = sprite_camera_basis(sprite);
1095 let (s, c) = sprite.rotate.sin_cos();
1096 let rx = p.x * c - p.y * s;
1097 let ry = p.x * s + p.y * c;
1098 let anchor = RVec3::new(
1099 anchor_x + sprite.pivot_x,
1100 anchor_y + sprite.pivot_y,
1101 sprite.z + sprite.pivot_z,
1102 );
1103 let out = anchor.add(RVec3::new(
1104 right.x * rx + up.x * ry,
1105 right.y * rx + up.y * ry,
1106 right.z * rx + up.z * ry,
1107 ));
1108 return [out.x, out.y, out.z];
1109 }
1110 p = rrotate_x(p, sprite.rotate_x);
1111 p = rrotate_y(p, sprite.rotate_y);
1112 p = rrotate_z(p, sprite.rotate);
1113 p = p.add(RVec3::new(
1114 anchor_x + sprite.pivot_x,
1115 anchor_y + sprite.pivot_y,
1116 sprite.z + sprite.pivot_z,
1117 ));
1118 [p.x, p.y, p.z]
1119}
1120
1121fn transform_model_normal_world(sprite: &crate::layer::Sprite, normal: [f32; 3]) -> [f32; 3] {
1122 let mut n = RVec3::new(normal[0], normal[1], normal[2]);
1123 if sprite.billboard {
1124 let (_, forward, right, up) = sprite_camera_basis(sprite);
1125 let basis_z = forward.normalize();
1126 let basis_x = right.normalize();
1127 let basis_y = up.normalize();
1128 let out = RVec3::new(
1129 basis_x.x * n.x + basis_y.x * n.y + basis_z.x * n.z,
1130 basis_x.y * n.x + basis_y.y * n.y + basis_z.y * n.z,
1131 basis_x.z * n.x + basis_y.z * n.y + basis_z.z * n.z,
1132 )
1133 .normalize();
1134 return [out.x, out.y, out.z];
1135 }
1136 n = rrotate_x(n, sprite.rotate_x);
1137 n = rrotate_y(n, sprite.rotate_y);
1138 n = rrotate_z(n, sprite.rotate);
1139 n = n.normalize();
1140 [n.x, n.y, n.z]
1141}
1142
1143fn project_shadow_point(sprite: &crate::layer::Sprite, world: [f32; 3]) -> Option<[f32; 4]> {
1144 if sprite.light_kind < 2 {
1145 return None;
1146 }
1147 let eye = RVec3::new(
1148 sprite.light_pos[0],
1149 sprite.light_pos[1],
1150 sprite.light_pos[2],
1151 );
1152 let light_dir = RVec3::new(
1153 sprite.light_dir[0],
1154 sprite.light_dir[1],
1155 sprite.light_dir[2],
1156 )
1157 .normalize();
1158 let target = eye.add(light_dir);
1159 let mut up = RVec3::new(0.0, 1.0, 0.0);
1160 if up.cross(light_dir).dot(up.cross(light_dir)) <= 1e-6 {
1161 up = RVec3::new(1.0, 0.0, 0.0);
1162 }
1163 let forward = target.sub(eye).normalize();
1164 let right = up.cross(forward).normalize();
1165 let up2 = forward.cross(right).normalize();
1166 let rel = RVec3::new(world[0], world[1], world[2]).sub(eye);
1167 let cx = rel.dot(right);
1168 let cy = rel.dot(up2);
1169 let cz = rel.dot(forward);
1170 if cz <= 1e-3 {
1171 return None;
1172 }
1173 let fov_deg = if sprite.light_cone[0] > 0.0 {
1174 (2.0 * sprite.light_cone[0].acos()).to_degrees().max(1.0)
1175 } else {
1176 45.0
1177 };
1178 let tan_half = (fov_deg.to_radians() * 0.5).tan().max(1e-3);
1179 let x_ndc = cx / (cz * tan_half);
1180 let y_ndc = cy / (cz * tan_half);
1181 if x_ndc.abs() > 1.5 || y_ndc.abs() > 1.5 {
1182 return None;
1183 }
1184 let depth = (cz / sprite.light_atten[3].max(1.0)).clamp(0.0, 1.0);
1185 Some([x_ndc, y_ndc, depth, 1.0])
1186}
1187
1188fn sprite_model_cols(
1189 sprite: &crate::layer::Sprite,
1190 anchor_x: f32,
1191 anchor_y: f32,
1192) -> ([[f32; 4]; 4], [[f32; 4]; 3]) {
1193 let origin = transform_model_point_world(sprite, [0.0, 0.0, 0.0], anchor_x, anchor_y);
1194 let px = transform_model_point_world(sprite, [1.0, 0.0, 0.0], anchor_x, anchor_y);
1195 let py = transform_model_point_world(sprite, [0.0, 1.0, 0.0], anchor_x, anchor_y);
1196 let pz = transform_model_point_world(sprite, [0.0, 0.0, 1.0], anchor_x, anchor_y);
1197 let nx = transform_model_normal_world(sprite, [1.0, 0.0, 0.0]);
1198 let ny = transform_model_normal_world(sprite, [0.0, 1.0, 0.0]);
1199 let nz = transform_model_normal_world(sprite, [0.0, 0.0, 1.0]);
1200 (
1201 [
1202 [px[0] - origin[0], px[1] - origin[1], px[2] - origin[2], 0.0],
1203 [py[0] - origin[0], py[1] - origin[1], py[2] - origin[2], 0.0],
1204 [pz[0] - origin[0], pz[1] - origin[1], pz[2] - origin[2], 0.0],
1205 [origin[0], origin[1], origin[2], 1.0],
1206 ],
1207 [
1208 [nx[0], nx[1], nx[2], 0.0],
1209 [ny[0], ny[1], ny[2], 0.0],
1210 [nz[0], nz[1], nz[2], 0.0],
1211 ],
1212 )
1213}
1214
1215fn shadow_uniform_data(
1216 sprite: &crate::layer::Sprite,
1217) -> ([f32; 4], [f32; 4], [f32; 4], [f32; 4], [f32; 4]) {
1218 if sprite.light_kind < 2 {
1219 return (
1220 [0.0, 0.0, 0.0, 0.0],
1221 [0.0, 0.0, 1.0, 0.0],
1222 [1.0, 0.0, 0.0, 0.0],
1223 [0.0, 1.0, 0.0, 0.0],
1224 [1.0, 1.0, 0.0, 0.0],
1225 );
1226 }
1227 let eye = RVec3::new(
1228 sprite.light_pos[0],
1229 sprite.light_pos[1],
1230 sprite.light_pos[2],
1231 );
1232 let light_dir = RVec3::new(
1233 sprite.light_dir[0],
1234 sprite.light_dir[1],
1235 sprite.light_dir[2],
1236 )
1237 .normalize();
1238 let mut up = RVec3::new(0.0, 1.0, 0.0);
1239 if up.cross(light_dir).dot(up.cross(light_dir)) <= 1e-6 {
1240 up = RVec3::new(1.0, 0.0, 0.0);
1241 }
1242 let forward = light_dir;
1243 let right = up.cross(forward).normalize();
1244 let up2 = forward.cross(right).normalize();
1245 let fov_deg = if sprite.light_cone[0] > 0.0 {
1246 (2.0 * sprite.light_cone[0].acos()).to_degrees().max(1.0)
1247 } else {
1248 45.0
1249 };
1250 let tan_half = (fov_deg.to_radians() * 0.5).tan().max(1e-3);
1251 (
1252 [eye.x, eye.y, eye.z, 0.0],
1253 [forward.x, forward.y, forward.z, 0.0],
1254 [right.x, right.y, right.z, 0.0],
1255 [up2.x, up2.y, up2.z, 0.0],
1256 [tan_half, sprite.light_atten[3].max(1.0), 1.0, 0.0],
1257 )
1258}
1259
1260fn normalize_col3(v: [f32; 4]) -> [f32; 4] {
1261 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
1262 if len <= 1e-6 {
1263 [0.0, 0.0, 1.0, 0.0]
1264 } else {
1265 [v[0] / len, v[1] / len, v[2] / len, 0.0]
1266 }
1267}
1268
1269fn light_id_selected(ids: &[i32], light_id: i32) -> bool {
1270 ids.is_empty() || ids.iter().any(|&id| id == light_id)
1271}
1272
1273fn fill_mesh_light_uniforms(
1274 sprite: &crate::layer::Sprite,
1275 material: &crate::mesh3d::MeshMaterial,
1276 u: &mut VsUniform,
1277) {
1278 let mut dir_count = 0usize;
1279 let mut point_count = 0usize;
1280 let mut spot_count = 0usize;
1281 for lt in &sprite.mesh_runtime_lights {
1282 match lt.kind {
1283 0 if dir_count < MAX_BATCH_LIGHTS
1284 && light_id_selected(&material.directional_light_ids, lt.id) =>
1285 {
1286 u.dir_light_diffuse[dir_count] = lt.diffuse;
1287 u.dir_light_ambient[dir_count] = lt.ambient;
1288 u.dir_light_specular[dir_count] = lt.specular;
1289 u.dir_light_dir[dir_count] = lt.dir;
1290 dir_count += 1;
1291 }
1292 1 if point_count < MAX_BATCH_LIGHTS
1293 && light_id_selected(&material.point_light_ids, lt.id) =>
1294 {
1295 u.point_light_diffuse[point_count] = lt.diffuse;
1296 u.point_light_ambient[point_count] = lt.ambient;
1297 u.point_light_specular[point_count] = lt.specular;
1298 u.point_light_pos[point_count] = lt.pos;
1299 u.point_light_atten[point_count] = lt.atten;
1300 point_count += 1;
1301 }
1302 2 | 3
1303 if spot_count < MAX_BATCH_LIGHTS
1304 && light_id_selected(&material.spot_light_ids, lt.id) =>
1305 {
1306 u.spot_light_diffuse[spot_count] = lt.diffuse;
1307 u.spot_light_ambient[spot_count] = lt.ambient;
1308 u.spot_light_specular[spot_count] = lt.specular;
1309 u.spot_light_pos[spot_count] = lt.pos;
1310 u.spot_light_dir[spot_count] = lt.dir;
1311 u.spot_light_atten[spot_count] = lt.atten;
1312 u.spot_light_cone[spot_count] = lt.cone;
1313 spot_count += 1;
1314 }
1315 _ => {}
1316 }
1317 }
1318 u.mesh_light_counts = [dir_count as f32, point_count as f32, spot_count as f32, 0.0];
1319}
1320
1321fn render_sprite_frame_per_mesh_set_effect_constant_common(
1322 sprite: &crate::layer::Sprite,
1323 anchor_x: f32,
1324 anchor_y: f32,
1325 win_w: f32,
1326 win_h: f32,
1327 frame_cols: [[f32; 4]; 4],
1328 material: &crate::mesh3d::MeshMaterial,
1329) -> VsUniform {
1330 let mut u = vertex_uniform_for_mesh(sprite, anchor_x, anchor_y, win_w, win_h);
1331 u.frame_col0 = frame_cols[0];
1332 u.frame_col1 = frame_cols[1];
1333 u.frame_col2 = frame_cols[2];
1334 u.frame_col3 = frame_cols[3];
1335 u.frame_normal0 = normalize_col3(frame_cols[0]);
1336 u.frame_normal1 = normalize_col3(frame_cols[1]);
1337 u.frame_normal2 = normalize_col3(frame_cols[2]);
1338 u.mtrl_diffuse = material.diffuse;
1339 u.mtrl_ambient = material.ambient;
1340 u.mtrl_specular = material.specular;
1341 u.mtrl_emissive = material.emissive;
1342 u.mtrl_params = [
1343 material.power.max(1.0),
1344 material.lighting_type as i32 as f32,
1345 material.shading_type as i32 as f32,
1346 material.rim_light_power.max(0.0),
1347 ];
1348 u.mtrl_rim = material.rim_light_color;
1349 u.mtrl_extra = [
1350 material.parallax_max_height.max(0.0),
1351 material.alpha_ref.clamp(0.0, 1.0),
1352 material.shader_option as f32,
1353 0.0,
1354 ];
1355 u.light_diffuse_u = sprite.light_diffuse;
1356 u.light_ambient_u = sprite.light_ambient;
1357 u.light_specular_u = sprite.light_specular;
1358 u.mesh_flags = [
1359 if material.use_mesh_tex { 1.0 } else { 0.0 },
1360 if material.use_mrbd { 1.0 } else { 0.0 },
1361 if material.use_rgb { 1.0 } else { 0.0 },
1362 if material.use_mul_vertex_color {
1363 1.0
1364 } else {
1365 0.0
1366 },
1367 ];
1368 u.mesh_mrbd = material.mrbd;
1369 u.mesh_rgb_rate = material.rgb_rate;
1370 u.mesh_add_rgb = material.add_rgb;
1371 u.mesh_misc = [
1372 material.mul_vertex_color_rate.max(0.0),
1373 material.depth_buffer_shadow_bias,
1374 0.0,
1375 0.0,
1376 ];
1377 fill_mesh_light_uniforms(sprite, material, &mut u);
1378 u
1379}
1380
1381fn render_sprite_frame_per_mesh_set_effect_constant_mesh(
1382 sprite: &crate::layer::Sprite,
1383 anchor_x: f32,
1384 anchor_y: f32,
1385 win_w: f32,
1386 win_h: f32,
1387 frame_cols: [[f32; 4]; 4],
1388 material: &crate::mesh3d::MeshMaterial,
1389) -> VsUniform {
1390 let mut u = render_sprite_frame_per_mesh_set_effect_constant_common(
1391 sprite, anchor_x, anchor_y, win_w, win_h, frame_cols, material,
1392 );
1393 u.flags[3] = 0.0;
1394 u
1395}
1396
1397fn render_sprite_frame_per_mesh_set_effect_constant_skinned_mesh(
1398 sprite: &crate::layer::Sprite,
1399 anchor_x: f32,
1400 anchor_y: f32,
1401 win_w: f32,
1402 win_h: f32,
1403 frame_cols: [[f32; 4]; 4],
1404 material: &crate::mesh3d::MeshMaterial,
1405) -> VsUniform {
1406 let mut u = render_sprite_frame_per_mesh_set_effect_constant_common(
1407 sprite, anchor_x, anchor_y, win_w, win_h, frame_cols, material,
1408 );
1409 u.flags[3] = 1.0;
1410 u
1411}
1412
1413fn vertex_uniform_for_mesh(
1414 sprite: &crate::layer::Sprite,
1415 anchor_x: f32,
1416 anchor_y: f32,
1417 win_w: f32,
1418 win_h: f32,
1419) -> VsUniform {
1420 let (model_cols, normal_cols) = sprite_model_cols(sprite, anchor_x, anchor_y);
1421 let (eye, forward, right, up) = sprite_camera_basis(sprite);
1422 let aspect = if win_h.abs() > f32::EPSILON {
1423 win_w / win_h
1424 } else {
1425 1.0
1426 };
1427 let hfov = sprite
1428 .camera_view_angle_deg
1429 .to_radians()
1430 .clamp(1e-3, std::f32::consts::PI - 1e-3);
1431 let tan_half_h = (hfov * 0.5).tan().max(1e-3);
1432 let tan_half_v = (tan_half_h / aspect.max(1e-3)).max(1e-3);
1433 let (shadow_eye, shadow_forward, shadow_right, shadow_up, shadow_params) =
1434 shadow_uniform_data(sprite);
1435 VsUniform {
1436 model_col0: model_cols[0],
1437 model_col1: model_cols[1],
1438 model_col2: model_cols[2],
1439 model_col3: model_cols[3],
1440 normal_col0: normal_cols[0],
1441 normal_col1: normal_cols[1],
1442 normal_col2: normal_cols[2],
1443 frame_col0: [1.0, 0.0, 0.0, 0.0],
1444 frame_col1: [0.0, 1.0, 0.0, 0.0],
1445 frame_col2: [0.0, 0.0, 1.0, 0.0],
1446 frame_col3: [0.0, 0.0, 0.0, 1.0],
1447 frame_normal0: [1.0, 0.0, 0.0, 0.0],
1448 frame_normal1: [0.0, 1.0, 0.0, 0.0],
1449 frame_normal2: [0.0, 0.0, 1.0, 0.0],
1450 camera_eye: [eye.x, eye.y, eye.z, 0.0],
1451 camera_forward: [forward.x, forward.y, forward.z, 0.0],
1452 camera_right: [right.x, right.y, right.z, 0.0],
1453 camera_up: [up.x, up.y, up.z, 0.0],
1454 camera_params: [tan_half_h, tan_half_v, win_w.max(1.0), win_h.max(1.0)],
1455 shadow_eye,
1456 shadow_forward,
1457 shadow_right,
1458 shadow_up,
1459 shadow_params,
1460 mtrl_diffuse: [1.0, 1.0, 1.0, 1.0],
1461 mtrl_ambient: [1.0, 1.0, 1.0, 1.0],
1462 mtrl_specular: [0.0, 0.0, 0.0, 1.0],
1463 mtrl_emissive: [0.0, 0.0, 0.0, 1.0],
1464 mtrl_params: [16.0, 0.0, 0.0, 0.0],
1465 mtrl_rim: [1.0, 1.0, 1.0, 1.0],
1466 mtrl_extra: [0.016, 0.001, 0.0, 0.0],
1467 light_diffuse_u: sprite.light_diffuse,
1468 light_ambient_u: sprite.light_ambient,
1469 light_specular_u: sprite.light_specular,
1470 mesh_flags: [1.0, 0.0, 0.0, 0.0],
1471 mesh_mrbd: [0.0, 0.0, 0.0, 0.0],
1472 mesh_rgb_rate: [0.0, 0.0, 0.0, 0.0],
1473 mesh_add_rgb: [0.0, 0.0, 0.0, 0.0],
1474 mesh_misc: [1.0, 0.03, 0.0, 0.0],
1475 mesh_light_counts: [0.0, 0.0, 0.0, 0.0],
1476 dir_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
1477 dir_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
1478 dir_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
1479 dir_light_dir: [[0.0; 4]; MAX_BATCH_LIGHTS],
1480 point_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
1481 point_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
1482 point_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
1483 point_light_pos: [[0.0; 4]; MAX_BATCH_LIGHTS],
1484 point_light_atten: [[0.0; 4]; MAX_BATCH_LIGHTS],
1485 spot_light_diffuse: [[0.0; 4]; MAX_BATCH_LIGHTS],
1486 spot_light_ambient: [[0.0; 4]; MAX_BATCH_LIGHTS],
1487 spot_light_specular: [[0.0; 4]; MAX_BATCH_LIGHTS],
1488 spot_light_pos: [[0.0; 4]; MAX_BATCH_LIGHTS],
1489 spot_light_dir: [[0.0; 4]; MAX_BATCH_LIGHTS],
1490 spot_light_atten: [[0.0; 4]; MAX_BATCH_LIGHTS],
1491 spot_light_cone: [[0.0; 4]; MAX_BATCH_LIGHTS],
1492 flags: [
1493 1.0,
1494 if sprite.camera_enabled { 1.0 } else { 0.0 },
1495 if sprite.light_kind >= 2 { 1.0 } else { 0.0 },
1496 0.0,
1497 ],
1498 }
1499}
1500
1501fn mesh_animation_state_for_sprite(
1502 sprite: &crate::layer::Sprite,
1503) -> crate::mesh3d::MeshAnimationState {
1504 sprite.mesh_animation.sanitized()
1505}
1506
1507fn resolved_mesh_pipeline_name_from_runtime_desc(
1508 desc: &crate::mesh3d::MeshPrimitiveRuntimeDesc,
1509 technique: TechniqueKey,
1510) -> String {
1511 let mut technique_name = desc.technique_name.clone();
1512 if technique.light {
1513 technique_name.push_str("_light");
1514 } else if technique.fog {
1515 technique_name.push_str("_fog");
1516 }
1517 if technique.d3 {
1518 technique_name.push_str("_d3");
1519 }
1520 format!("{}::{}", desc.effect_key, technique_name)
1521}
1522
1523fn resolved_shadow_pipeline_name_from_runtime_desc(
1524 desc: &crate::mesh3d::MeshPrimitiveRuntimeDesc,
1525) -> String {
1526 format!("{}::{}", desc.shadow_effect_key, desc.shadow_technique_name)
1527}
1528
1529fn pipeline_program_for_special(special: TechniqueSpecial) -> EffectProgram {
1530 match special {
1531 TechniqueSpecial::Overlay => EffectProgram::OverlayGpu,
1532 TechniqueSpecial::WipeMosaic => EffectProgram::WipeMosaic,
1533 TechniqueSpecial::WipeRasterH => EffectProgram::WipeRasterH,
1534 TechniqueSpecial::WipeRasterV => EffectProgram::WipeRasterV,
1535 TechniqueSpecial::WipeExplosionBlur => EffectProgram::WipeExplosionBlur,
1536 TechniqueSpecial::WipeShimi => EffectProgram::WipeShimi,
1537 TechniqueSpecial::WipeShimiInv => EffectProgram::WipeShimiInv,
1538 TechniqueSpecial::WipeCrossMosaic => EffectProgram::WipeCrossMosaic,
1539 TechniqueSpecial::WipeCrossRasterH => EffectProgram::WipeCrossRasterH,
1540 TechniqueSpecial::WipeCrossRasterV => EffectProgram::WipeCrossRasterV,
1541 TechniqueSpecial::WipeCrossExplosionBlur => EffectProgram::WipeCrossExplosionBlur,
1542 TechniqueSpecial::None => EffectProgram::Sprite2D,
1543 TechniqueSpecial::Mesh | TechniqueSpecial::SkinnedMesh | TechniqueSpecial::Shadow => {
1544 unreachable!("mesh/shadow techniques must resolve through MeshPrimitiveRuntimeDesc")
1545 }
1546 }
1547}
1548
1549fn mesh_effect_program_from_runtime_desc(
1550 desc: &crate::mesh3d::MeshPrimitiveRuntimeDesc,
1551) -> EffectProgram {
1552 let skinned = matches!(
1553 desc.effect_profile,
1554 crate::mesh3d::MeshEffectProfile::SkinnedMesh
1555 ) || desc.material_key.skinned;
1556 match (skinned, desc.material_key.lighting_type) {
1557 (false, crate::mesh3d::MeshLightingType::None) => EffectProgram::MeshStaticUnlit,
1558 (false, crate::mesh3d::MeshLightingType::Lambert) => EffectProgram::MeshStaticLambert,
1559 (false, crate::mesh3d::MeshLightingType::BlinnPhong) => EffectProgram::MeshStaticBlinnPhong,
1560 (false, crate::mesh3d::MeshLightingType::PerPixelBlinnPhong) => {
1561 EffectProgram::MeshStaticPerPixelBlinnPhong
1562 }
1563 (false, crate::mesh3d::MeshLightingType::PerPixelHalfLambert) => {
1564 EffectProgram::MeshStaticPerPixelHalfLambert
1565 }
1566 (false, crate::mesh3d::MeshLightingType::Toon) => EffectProgram::MeshStaticToon,
1567 (false, crate::mesh3d::MeshLightingType::FixedFunction) => {
1568 EffectProgram::MeshStaticFixedFunction
1569 }
1570 (false, crate::mesh3d::MeshLightingType::PerPixelFixedFunction) => {
1571 EffectProgram::MeshStaticPerPixelFixedFunction
1572 }
1573 (false, crate::mesh3d::MeshLightingType::Bump) => EffectProgram::MeshStaticBump,
1574 (false, crate::mesh3d::MeshLightingType::Parallax) => EffectProgram::MeshStaticParallax,
1575 (true, crate::mesh3d::MeshLightingType::None) => EffectProgram::MeshSkinnedUnlit,
1576 (true, crate::mesh3d::MeshLightingType::Lambert) => EffectProgram::MeshSkinnedLambert,
1577 (true, crate::mesh3d::MeshLightingType::BlinnPhong) => EffectProgram::MeshSkinnedBlinnPhong,
1578 (true, crate::mesh3d::MeshLightingType::PerPixelBlinnPhong) => {
1579 EffectProgram::MeshSkinnedPerPixelBlinnPhong
1580 }
1581 (true, crate::mesh3d::MeshLightingType::PerPixelHalfLambert) => {
1582 EffectProgram::MeshSkinnedPerPixelHalfLambert
1583 }
1584 (true, crate::mesh3d::MeshLightingType::Toon) => EffectProgram::MeshSkinnedToon,
1585 (true, crate::mesh3d::MeshLightingType::FixedFunction) => {
1586 EffectProgram::MeshSkinnedFixedFunction
1587 }
1588 (true, crate::mesh3d::MeshLightingType::PerPixelFixedFunction) => {
1589 EffectProgram::MeshSkinnedPerPixelFixedFunction
1590 }
1591 (true, crate::mesh3d::MeshLightingType::Bump) => EffectProgram::MeshSkinnedBump,
1592 (true, crate::mesh3d::MeshLightingType::Parallax) => EffectProgram::MeshSkinnedParallax,
1593 }
1594}
1595
1596fn shadow_effect_program_from_source(src: EffectProgram) -> EffectProgram {
1597 match src {
1598 EffectProgram::MeshSkinnedUnlit
1599 | EffectProgram::MeshSkinnedLambert
1600 | EffectProgram::MeshSkinnedBlinnPhong
1601 | EffectProgram::MeshSkinnedPerPixelBlinnPhong
1602 | EffectProgram::MeshSkinnedPerPixelHalfLambert
1603 | EffectProgram::MeshSkinnedToon
1604 | EffectProgram::MeshSkinnedFixedFunction
1605 | EffectProgram::MeshSkinnedPerPixelFixedFunction
1606 | EffectProgram::MeshSkinnedBump
1607 | EffectProgram::MeshSkinnedParallax
1608 | EffectProgram::ShadowSkinned => EffectProgram::ShadowSkinned,
1609 _ => EffectProgram::ShadowStatic,
1610 }
1611}
1612
1613fn technique_name_for_pipeline(key: &PipelineKey) -> String {
1614 let base = if !key.pipeline_name.is_empty() {
1615 key.pipeline_name.clone()
1616 } else {
1617 match key.technique.special {
1618 TechniqueSpecial::Overlay => "tec_overlay_gpu".to_string(),
1619 TechniqueSpecial::WipeMosaic => "tec_tex1_mosaic".to_string(),
1620 TechniqueSpecial::WipeRasterH => "tec_tex1_raster_h".to_string(),
1621 TechniqueSpecial::WipeRasterV => "tec_tex1_raster_v".to_string(),
1622 TechniqueSpecial::WipeExplosionBlur => "tec_tex1_explosion_blur".to_string(),
1623 TechniqueSpecial::WipeShimi => "tec_tex1_shimi".to_string(),
1624 TechniqueSpecial::WipeShimiInv => "tec_tex1_shimi_inv".to_string(),
1625 TechniqueSpecial::WipeCrossMosaic => "tec_tex2_mosaic".to_string(),
1626 TechniqueSpecial::WipeCrossRasterH => "tec_tex2_raster_h".to_string(),
1627 TechniqueSpecial::WipeCrossRasterV => "tec_tex2_raster_v".to_string(),
1628 TechniqueSpecial::WipeCrossExplosionBlur => "tec_tex2_explosion_blur".to_string(),
1629 TechniqueSpecial::Mesh | TechniqueSpecial::SkinnedMesh => {
1630 let mut name = crate::mesh3d::mesh_effect_key_from_variant(key.mesh_fx_variant);
1631 if key.technique.light {
1632 name.push_str("::tech_light");
1633 } else if key.technique.fog {
1634 name.push_str("::tech_fog");
1635 } else {
1636 name.push_str("::tech");
1637 }
1638 if key.technique.d3 {
1639 name.push_str("_d3");
1640 }
1641 name
1642 }
1643 TechniqueSpecial::Shadow => {
1644 let base_key = crate::mesh3d::MeshRuntimeMaterialKey {
1645 use_mesh_tex: false,
1646 use_shadow_tex: false,
1647 use_toon_tex: false,
1648 use_normal_tex: false,
1649 use_mul_vertex_color: false,
1650 use_mrbd: false,
1651 use_rgb: false,
1652 lighting_type: crate::mesh3d::MeshLightingType::None,
1653 shading_type: crate::mesh3d::MeshShadingType::None,
1654 shader_option: crate::mesh3d::MESH_SHADER_OPTION_NONE,
1655 skinned: matches!(key.program, EffectProgram::ShadowSkinned),
1656 alpha_test_enable: false,
1657 cull_disable: false,
1658 shadow_map_enable: true,
1659 };
1660 format!(
1661 "{}::tech",
1662 crate::mesh3d::mesh_effect_filename_from_runtime_key(
1663 crate::mesh3d::MeshEffectProfile::ShadowMap,
1664 base_key,
1665 )
1666 )
1667 }
1668 TechniqueSpecial::None => {
1669 let vertex_name = format!(
1670 "{}{}",
1671 if key.technique.d3 { "_d3" } else { "" },
1672 if key.technique.light {
1673 "_light"
1674 } else if key.technique.fog {
1675 "_fog"
1676 } else {
1677 ""
1678 }
1679 );
1680 let pixel_name = format!(
1681 "{}{}{}{}{}{}{}{}",
1682 if key.technique.light {
1683 "_v2"
1684 } else if key.technique.fog {
1685 "_v1"
1686 } else {
1687 "_v0"
1688 },
1689 if key.technique.tex != 0 { "_tex" } else { "" },
1690 if key.technique.diffuse {
1691 "_diffuse"
1692 } else {
1693 ""
1694 },
1695 if key.technique.mrbd { "_mrbd" } else { "" },
1696 if key.technique.rgb { "_rgb" } else { "" },
1697 if key.technique.tonecurve {
1698 "_tonecurve"
1699 } else {
1700 ""
1701 },
1702 if key.technique.mask { "_mask" } else { "" },
1703 match key.blend {
1704 SpriteBlend::Normal => "",
1705 SpriteBlend::Add => "_add",
1706 SpriteBlend::Sub => "_sub",
1707 SpriteBlend::Mul => "_mul",
1708 SpriteBlend::Screen => "_screen",
1709 SpriteBlend::Overlay => "_overlay",
1710 }
1711 );
1712 format!("tec{}{}", vertex_name, pixel_name)
1713 }
1714 }
1715 };
1716 format!("{}#{}", base, key.program.short_name())
1717}
1718
1719impl Renderer {
1720 pub async fn new(window: &'static Window) -> Result<Self> {
1721 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1722 let backends = wgpu::Backends::GL;
1723 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1724 let backends = wgpu::Backends::all();
1725
1726 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
1727 backends,
1728 ..Default::default()
1729 });
1730
1731 let surface = instance.create_surface(window).context("create_surface")?;
1732 let size = window.inner_size();
1733 let scale_factor = window.scale_factor() as f32;
1734 Self::new_from_instance_surface(instance, surface, size.width, size.height, scale_factor).await
1735 }
1736
1737 #[cfg(any(target_os = "android", target_os = "ios"))]
1738 pub async unsafe fn new_from_raw_handles(
1739 raw_display_handle: raw_window_handle::RawDisplayHandle,
1740 raw_window_handle: raw_window_handle::RawWindowHandle,
1741 width: u32,
1742 height: u32,
1743 scale_factor: f32,
1744 ) -> Result<Self> {
1745 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
1746 backends: wgpu::Backends::all(),
1747 ..Default::default()
1748 });
1749 let surface = instance
1750 .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle {
1751 raw_display_handle,
1752 raw_window_handle,
1753 })
1754 .context("create_surface_unsafe")?;
1755 Self::new_from_instance_surface(instance, surface, width, height, scale_factor).await
1756 }
1757
1758 async fn new_from_instance_surface(
1759 instance: wgpu::Instance,
1760 surface: wgpu::Surface<'static>,
1761 width: u32,
1762 height: u32,
1763 scale_factor: f32,
1764 ) -> Result<Self> {
1765 let adapter = instance
1766 .request_adapter(&wgpu::RequestAdapterOptions {
1767 power_preference: wgpu::PowerPreference::HighPerformance,
1768 compatible_surface: Some(&surface),
1769 force_fallback_adapter: false,
1770 })
1771 .await
1772 .context("request_adapter")?;
1773
1774 let (device, queue) = adapter
1775 .request_device(
1776 &wgpu::DeviceDescriptor {
1777 label: Some("siglus-bg-device"),
1778 required_features: wgpu::Features::empty(),
1779 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1780 required_limits: wgpu::Limits::downlevel_webgl2_defaults(),
1781 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1782 required_limits: wgpu::Limits::default(),
1783 },
1784 None,
1785 )
1786 .await
1787 .context("request_device")?;
1788
1789 let surface_caps = surface.get_capabilities(&adapter);
1790 let format = surface_caps
1791 .formats
1792 .iter()
1793 .copied()
1794 .find(|f| f.is_srgb())
1795 .unwrap_or(surface_caps.formats[0]);
1796
1797 let scale_factor = if scale_factor.is_finite() && scale_factor > 0.0 {
1798 scale_factor
1799 } else {
1800 1.0
1801 };
1802 let width = width.max(1);
1803 let height = height.max(1);
1804 let logical_width = ((width as f32) / scale_factor).max(1.0);
1805 let logical_height = ((height as f32) / scale_factor).max(1.0);
1806 let alpha_mode = surface_caps
1807 .alpha_modes
1808 .iter()
1809 .copied()
1810 .find(|m| *m == wgpu::CompositeAlphaMode::Opaque)
1811 .unwrap_or(surface_caps.alpha_modes[0]);
1812 let present_mode = surface_caps
1813 .present_modes
1814 .iter()
1815 .copied()
1816 .find(|m| *m == wgpu::PresentMode::Fifo)
1817 .unwrap_or(surface_caps.present_modes[0]);
1818 let config = wgpu::SurfaceConfiguration {
1819 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1820 format,
1821 width,
1822 height,
1823 present_mode,
1824 alpha_mode,
1825 view_formats: vec![],
1826 desired_maximum_frame_latency: 2,
1827 };
1828 surface.configure(&device, &config);
1829
1830 let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1831 label: Some("siglus-sprite-bgl"),
1832 entries: &[
1833 wgpu::BindGroupLayoutEntry {
1834 binding: 0,
1835 visibility: wgpu::ShaderStages::FRAGMENT,
1836 ty: wgpu::BindingType::Texture {
1837 multisampled: false,
1838 view_dimension: wgpu::TextureViewDimension::D2,
1839 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1840 },
1841 count: None,
1842 },
1843 wgpu::BindGroupLayoutEntry {
1844 binding: 1,
1845 visibility: wgpu::ShaderStages::FRAGMENT,
1846 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1847 count: None,
1848 },
1849 wgpu::BindGroupLayoutEntry {
1850 binding: 2,
1851 visibility: wgpu::ShaderStages::FRAGMENT,
1852 ty: wgpu::BindingType::Texture {
1853 multisampled: false,
1854 view_dimension: wgpu::TextureViewDimension::D2,
1855 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1856 },
1857 count: None,
1858 },
1859 wgpu::BindGroupLayoutEntry {
1860 binding: 3,
1861 visibility: wgpu::ShaderStages::FRAGMENT,
1862 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1863 count: None,
1864 },
1865 wgpu::BindGroupLayoutEntry {
1866 binding: 4,
1867 visibility: wgpu::ShaderStages::FRAGMENT,
1868 ty: wgpu::BindingType::Texture {
1869 multisampled: false,
1870 view_dimension: wgpu::TextureViewDimension::D2,
1871 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1872 },
1873 count: None,
1874 },
1875 wgpu::BindGroupLayoutEntry {
1876 binding: 5,
1877 visibility: wgpu::ShaderStages::FRAGMENT,
1878 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1879 count: None,
1880 },
1881 wgpu::BindGroupLayoutEntry {
1882 binding: 6,
1883 visibility: wgpu::ShaderStages::FRAGMENT,
1884 ty: wgpu::BindingType::Texture {
1885 multisampled: false,
1886 view_dimension: wgpu::TextureViewDimension::D2,
1887 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1888 },
1889 count: None,
1890 },
1891 wgpu::BindGroupLayoutEntry {
1892 binding: 7,
1893 visibility: wgpu::ShaderStages::FRAGMENT,
1894 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1895 count: None,
1896 },
1897 wgpu::BindGroupLayoutEntry {
1898 binding: 8,
1899 visibility: wgpu::ShaderStages::FRAGMENT,
1900 ty: wgpu::BindingType::Texture {
1901 multisampled: false,
1902 view_dimension: wgpu::TextureViewDimension::D2,
1903 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1904 },
1905 count: None,
1906 },
1907 wgpu::BindGroupLayoutEntry {
1908 binding: 9,
1909 visibility: wgpu::ShaderStages::FRAGMENT,
1910 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1911 count: None,
1912 },
1913 wgpu::BindGroupLayoutEntry {
1914 binding: 10,
1915 visibility: wgpu::ShaderStages::FRAGMENT,
1916 ty: wgpu::BindingType::Texture {
1917 multisampled: false,
1918 view_dimension: wgpu::TextureViewDimension::D2,
1919 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1920 },
1921 count: None,
1922 },
1923 wgpu::BindGroupLayoutEntry {
1924 binding: 11,
1925 visibility: wgpu::ShaderStages::FRAGMENT,
1926 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1927 count: None,
1928 },
1929 wgpu::BindGroupLayoutEntry {
1930 binding: 12,
1931 visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
1932 ty: wgpu::BindingType::Buffer {
1933 ty: wgpu::BufferBindingType::Uniform,
1934 has_dynamic_offset: false,
1935 min_binding_size: None,
1936 },
1937 count: None,
1938 },
1939 wgpu::BindGroupLayoutEntry {
1940 binding: 13,
1941 visibility: wgpu::ShaderStages::VERTEX,
1942 ty: wgpu::BindingType::Buffer {
1943 ty: wgpu::BufferBindingType::Uniform,
1944 has_dynamic_offset: false,
1945 min_binding_size: None,
1946 },
1947 count: None,
1948 },
1949 wgpu::BindGroupLayoutEntry {
1950 binding: 14,
1951 visibility: wgpu::ShaderStages::FRAGMENT,
1952 ty: wgpu::BindingType::Texture {
1953 multisampled: false,
1954 view_dimension: wgpu::TextureViewDimension::D2,
1955 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1956 },
1957 count: None,
1958 },
1959 wgpu::BindGroupLayoutEntry {
1960 binding: 15,
1961 visibility: wgpu::ShaderStages::FRAGMENT,
1962 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1963 count: None,
1964 },
1965 wgpu::BindGroupLayoutEntry {
1966 binding: 16,
1967 visibility: wgpu::ShaderStages::FRAGMENT,
1968 ty: wgpu::BindingType::Texture {
1969 multisampled: false,
1970 view_dimension: wgpu::TextureViewDimension::D2,
1971 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1972 },
1973 count: None,
1974 },
1975 wgpu::BindGroupLayoutEntry {
1976 binding: 17,
1977 visibility: wgpu::ShaderStages::FRAGMENT,
1978 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1979 count: None,
1980 },
1981 ],
1982 });
1983
1984 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1985 let shader_source = wgpu::ShaderSource::Wgsl(wasm_shader_source().into());
1986 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1987 let shader_source = wgpu::ShaderSource::Wgsl(SHADER.into());
1988 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1989 label: Some("siglus-sprite-shader"),
1990 source: shader_source,
1991 });
1992
1993 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1994 label: Some("siglus-sprite-pipeline-layout"),
1995 bind_group_layouts: &[&bind_group_layout],
1996 push_constant_ranges: &[],
1997 });
1998
1999 let vertex_capacity = 6;
2000 let vertex_buf = device.create_buffer(&wgpu::BufferDescriptor {
2001 label: Some("siglus-sprite-vertex-buf"),
2002 size: (vertex_capacity * std::mem::size_of::<Vertex>()) as wgpu::BufferAddress,
2003 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2004 mapped_at_creation: false,
2005 });
2006 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2007 let vertex_sprite2d_buf = device.create_buffer(&wgpu::BufferDescriptor {
2008 label: Some("siglus-sprite2d-vertex-buf"),
2009 size: (vertex_capacity * std::mem::size_of::<VertexSprite2dData>())
2010 as wgpu::BufferAddress,
2011 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
2012 mapped_at_creation: false,
2013 });
2014 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2015 let vertex_sprite2d_capacity = vertex_capacity;
2016
2017 let default_aux = create_solid_texture(&device, &queue, [255, 255, 255, 255])?;
2018 let depth = create_depth_texture(&device, config.width, config.height);
2019 let scene_a = create_render_target_texture(
2020 &device,
2021 config.width,
2022 config.height,
2023 config.format,
2024 "siglus-scene-a",
2025 );
2026 let scene_b = create_render_target_texture(
2027 &device,
2028 config.width,
2029 config.height,
2030 config.format,
2031 "siglus-scene-b",
2032 );
2033 let shadow_map =
2034 create_render_target_texture(&device, 2048, 2048, config.format, "siglus-shadow-map");
2035 let shadow_depth = create_depth_texture(&device, 2048, 2048);
2036
2037 let surface_viewport = SurfaceViewport::full(config.width, config.height);
2038 Ok(Self {
2039 surface,
2040 device,
2041 queue,
2042 config,
2043 logical_width,
2044 logical_height,
2045 scale_factor: scale_factor.max(1.0),
2046 surface_viewport,
2047 pipelines: HashMap::new(),
2048 bind_group_layout,
2049 shader,
2050 pipeline_layout,
2051 vertex_buf,
2052 vertex_capacity,
2053 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2054 vertex_sprite2d_buf,
2055 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2056 vertex_sprite2d_capacity,
2057 textures: HashMap::new(),
2058 external_textures: HashMap::new(),
2059 mesh_assets: HashMap::new(),
2060 default_aux,
2061 depth,
2062 scene_a,
2063 scene_b,
2064 shadow_map,
2065 shadow_depth,
2066 verts: Vec::new(),
2067 draws: Vec::new(),
2068 debug_frame_serial: 0,
2069 })
2070 }
2071
2072 pub fn scale_factor(&self) -> f32 {
2073 self.scale_factor
2074 }
2075
2076 pub fn resize(&mut self, width: u32, height: u32) {
2077 self.resize_with_scale(width, height, self.scale_factor);
2078 }
2079
2080 pub fn resize_with_scale(&mut self, width: u32, height: u32, scale_factor: f32) {
2081 if width == 0 || height == 0 {
2082 return;
2083 }
2084 let sf = if scale_factor.is_finite() && scale_factor > 0.0 {
2085 scale_factor
2086 } else {
2087 1.0
2088 };
2089 self.scale_factor = sf;
2090 self.logical_width = ((width as f32) / sf).max(1.0);
2091 self.logical_height = ((height as f32) / sf).max(1.0);
2092 self.surface_viewport = SurfaceViewport::full(width, height);
2093 self.config.width = width;
2094 self.config.height = height;
2095 self.surface.configure(&self.device, &self.config);
2096 self.depth = create_depth_texture(&self.device, self.config.width, self.config.height);
2097 self.scene_a = create_render_target_texture(
2098 &self.device,
2099 self.config.width,
2100 self.config.height,
2101 self.config.format,
2102 "siglus-scene-a",
2103 );
2104 self.scene_b = create_render_target_texture(
2105 &self.device,
2106 self.config.width,
2107 self.config.height,
2108 self.config.format,
2109 "siglus-scene-b",
2110 );
2111 }
2112
2113 pub fn resize_with_logical_viewport(
2114 &mut self,
2115 surface_width: u32,
2116 surface_height: u32,
2117 scale_factor: f32,
2118 logical_width: u32,
2119 logical_height: u32,
2120 viewport_x: u32,
2121 viewport_y: u32,
2122 viewport_width: u32,
2123 viewport_height: u32,
2124 ) {
2125 self.resize_with_scale(surface_width, surface_height, scale_factor);
2126 self.logical_width = logical_width.max(1) as f32;
2127 self.logical_height = logical_height.max(1) as f32;
2128 let max_w = self.config.width;
2129 let max_h = self.config.height;
2130 let x = viewport_x.min(max_w.saturating_sub(1));
2131 let y = viewport_y.min(max_h.saturating_sub(1));
2132 let w = viewport_width.max(1).min(max_w.saturating_sub(x).max(1));
2133 let h = viewport_height.max(1).min(max_h.saturating_sub(y).max(1));
2134 self.surface_viewport = SurfaceViewport { x, y, w, h };
2135 }
2136
2137 pub fn logical_size(&self) -> (u32, u32) {
2138 (
2139 self.logical_width.max(1.0).round() as u32,
2140 self.logical_height.max(1.0).round() as u32,
2141 )
2142 }
2143
2144 pub fn render_sprites(
2145 &mut self,
2146 images: &ImageManager,
2147 sprites: &[RenderSprite],
2148 ) -> Result<()> {
2149 let frame = self
2150 .surface
2151 .get_current_texture()
2152 .context("get_current_texture")?;
2153 let view = frame
2154 .texture
2155 .create_view(&wgpu::TextureViewDescriptor::default());
2156
2157 self.debug_frame_serial = self.debug_frame_serial.wrapping_add(1);
2158 self.verts.clear();
2159 self.draws.clear();
2160
2161 let win_w = self.logical_width.max(1.0);
2162 let win_h = self.logical_height.max(1.0);
2163 let surface_w = self.config.width;
2164 let surface_h = self.config.height;
2165 let surface_viewport = self.surface_viewport;
2166
2167 for s in sprites {
2168 let sprite = &s.sprite;
2169 let img_id = sprite.image_id;
2170 let img = img_id.and_then(|id| images.get(id));
2171
2172 let (src_left, src_top, src_right, src_bottom) = if let Some(img) = img {
2173 src_clip_rect(sprite.src_clip, img.width, img.height)?
2174 } else {
2175 (0.0, 0.0, 1.0, 1.0)
2176 };
2177 let src_w = (src_right - src_left).max(1.0);
2178 let src_h = (src_bottom - src_top).max(1.0);
2179 let (dst_x, dst_y, dst_w, dst_h) = match sprite.fit {
2180 SpriteFit::FullScreen => (0.0f32, 0.0f32, win_w, win_h),
2181 SpriteFit::PixelRect => {
2182 let (w, h) = match sprite.size_mode {
2183 SpriteSizeMode::Intrinsic => (src_w, src_h),
2184 SpriteSizeMode::Explicit { width, height } => (width as f32, height as f32),
2185 };
2186 (sprite.x as f32, sprite.y as f32, w, h)
2187 }
2188 };
2189
2190 let scissor = dst_scissor_rect_to_viewport(
2191 sprite.dst_clip,
2192 surface_viewport,
2193 win_w,
2194 win_h,
2195 surface_w,
2196 surface_h,
2197 );
2198 if let Some(sci) = scissor {
2199 if sci.w == 0 || sci.h == 0 {
2200 continue;
2201 }
2202 }
2203
2204 let alpha = (sprite.alpha as f32) / 255.0;
2205 let tr = (sprite.tr as f32) / 255.0;
2206 let mono = (sprite.mono as f32) / 255.0;
2207 let reverse = (sprite.reverse as f32) / 255.0;
2208 let bright = (sprite.bright as f32) / 255.0;
2209 let dark = (sprite.dark as f32) / 255.0;
2210 let color_rate = (sprite.color_rate as f32) / 255.0;
2211 let color_add_r = (sprite.color_add_r as f32) / 255.0;
2212 let color_add_g = (sprite.color_add_g as f32) / 255.0;
2213 let color_add_b = (sprite.color_add_b as f32) / 255.0;
2214 let color_r = (sprite.color_r as f32) / 255.0;
2215 let color_g = (sprite.color_g as f32) / 255.0;
2216 let color_b = (sprite.color_b as f32) / 255.0;
2217 let effects1 = [tr, mono, reverse, bright];
2218 let effects2 = [dark, color_rate, color_add_r, color_add_g];
2219 let effects3 = [color_add_b, color_r, color_g, color_b];
2220
2221 let has_mask = sprite.mask_image_id.and_then(|id| images.get(id)).is_some();
2222 let has_tonecurve = sprite
2223 .tonecurve_image_id
2224 .and_then(|id| images.get(id))
2225 .is_some();
2226 let has_wipe_src = sprite
2227 .wipe_src_image_id
2228 .and_then(|id| images.get(id))
2229 .is_some();
2230 let has_fog_tex = sprite
2231 .fog_texture_image_id
2232 .and_then(|id| images.get(id))
2233 .is_some();
2234
2235 let effects4 = [
2236 sprite.mask_mode as f32,
2237 if sprite.alpha_test { 1.0 } else { 0.0 },
2238 if sprite.light_enabled { 1.0 } else { 0.0 },
2239 if sprite.fog_enabled { 1.0 } else { 0.0 },
2240 ];
2241 let effects5 = [
2242 if has_mask { 1.0 } else { 0.0 },
2243 if has_tonecurve { 1.0 } else { 0.0 },
2244 sprite.tonecurve_row,
2245 sprite.tonecurve_sat,
2246 ];
2247 let effects6 = [
2248 sprite.wipe_fx_mode as f32,
2249 sprite.wipe_fx_params[0],
2250 sprite.wipe_fx_params[1],
2251 sprite.wipe_fx_params[2],
2252 ];
2253 let blend_code = match sprite.blend {
2254 SpriteBlend::Normal => 0.0,
2255 SpriteBlend::Add => 1.0,
2256 SpriteBlend::Sub => 2.0,
2257 SpriteBlend::Mul => 3.0,
2258 SpriteBlend::Screen => 4.0,
2259 SpriteBlend::Overlay => 5.0,
2260 };
2261 let effects7 = if sprite.wipe_fx_mode >= 10 {
2262 [
2263 sprite.wipe_fx_params[3],
2264 if has_wipe_src { 1.0 } else { 0.0 },
2265 blend_code,
2266 sprite.tonecurve_sat,
2267 ]
2268 } else {
2269 [0.0, if has_wipe_src { 1.0 } else { 0.0 }, blend_code, 0.0]
2270 };
2271 let effects8 = [
2272 sprite.light_diffuse[0],
2273 sprite.light_diffuse[1],
2274 sprite.light_diffuse[2],
2275 sprite.light_factor,
2276 ];
2277 let effects9 = [
2278 sprite.light_ambient[0],
2279 sprite.light_ambient[1],
2280 sprite.light_ambient[2],
2281 sprite.fog_scroll_x,
2282 ];
2283 let effects10 = [
2284 sprite.fog_color[0],
2285 sprite.fog_color[1],
2286 sprite.fog_color[2],
2287 sprite.z,
2288 ];
2289 let effects11 = [
2290 sprite.fog_near,
2291 sprite.fog_far,
2292 if has_fog_tex { 1.0 } else { 0.0 },
2293 sprite.camera_eye[2],
2294 ];
2295 let zero4 = [0.0f32; 4];
2296 let light_pos_kind_base = [
2297 sprite.light_pos[0],
2298 sprite.light_pos[1],
2299 sprite.light_pos[2],
2300 sprite.light_kind as f32,
2301 ];
2302 let light_dir_shadow_base = [
2303 sprite.light_dir[0],
2304 sprite.light_dir[1],
2305 sprite.light_dir[2],
2306 if sprite.shadow_receive && sprite.light_cone[3] > 0.5 {
2307 1.0
2308 } else {
2309 0.0
2310 },
2311 ];
2312 let light_atten_base = sprite.light_atten;
2313 let light_cone_base = sprite.light_cone;
2314
2315 let mut special_override = None;
2316 let mut mesh_batches: Option<Vec<crate::mesh3d::MeshGpuPrimitiveBatch>> = None;
2317 if sprite.mesh_kind != 0 {
2318 if let Some(file_name) = sprite.mesh_file_name.as_deref() {
2319 if let Some(asset) = self.ensure_mesh_asset(images, file_name) {
2320 let anim_state = mesh_animation_state_for_sprite(sprite);
2321 let sampled = asset.sample_gpu_primitives_with_state(&anim_state);
2322 if !sampled.is_empty() {
2323 special_override = Some(if asset.is_skinned() {
2324 TechniqueSpecial::SkinnedMesh
2325 } else {
2326 TechniqueSpecial::Mesh
2327 });
2328 mesh_batches = Some(sampled);
2329 }
2330 }
2331 }
2332 }
2333
2334 let use_depth = uses_depth_pipeline(sprite);
2335 let technique = build_technique_key(
2336 sprite,
2337 has_mask,
2338 has_tonecurve,
2339 has_wipe_src,
2340 special_override,
2341 );
2342 let draw_kind = if matches!(technique.special, TechniqueSpecial::Shadow) {
2343 MeshDrawKind::ShadowCaster
2344 } else if matches!(technique.special, TechniqueSpecial::SkinnedMesh) {
2345 MeshDrawKind::SkinnedMesh
2346 } else if matches!(technique.special, TechniqueSpecial::Mesh) {
2347 MeshDrawKind::StaticMesh
2348 } else {
2349 MeshDrawKind::SpriteQuad
2350 };
2351 let requires_alpha_composition = sprite.alpha < 255
2352 || sprite.tr < 255
2353 || has_mask
2354 || has_tonecurve
2355 || has_wipe_src
2356 || sprite.wipe_fx_mode != 0;
2357 let pipeline_key = PipelineKey {
2358 technique,
2359 blend: sprite.blend,
2360 alpha_blend: if matches!(technique.special, TechniqueSpecial::Overlay) {
2361 false
2362 } else {
2363 sprite.alpha_blend || requires_alpha_composition
2364 },
2365 use_depth,
2366 cull_back: pipeline_cull_back(sprite, false),
2367 mesh_fx_variant: 0,
2368 pipeline_name: String::new(),
2369 program: pipeline_program_for_special(technique.special),
2370 };
2371
2372 if let Some(mesh_batches) = mesh_batches {
2373 let technique_special = special_override.unwrap_or(TechniqueSpecial::Mesh);
2374 for batch in mesh_batches {
2375 if batch.vertices.is_empty() {
2376 continue;
2377 }
2378 let batch_special = if batch.skinned {
2379 TechniqueSpecial::SkinnedMesh
2380 } else {
2381 technique_special
2382 };
2383 let mut batch_technique = build_technique_key(
2384 sprite,
2385 has_mask,
2386 has_tonecurve,
2387 has_wipe_src,
2388 Some(batch_special),
2389 );
2390 batch_technique.tex = batch_technique
2391 .tex
2392 .max(u8::from(batch.runtime_desc.material_key.use_mesh_tex));
2393 batch_technique.mrbd =
2394 batch_technique.mrbd || batch.runtime_desc.material_key.use_mrbd;
2395 batch_technique.rgb =
2396 batch_technique.rgb || batch.runtime_desc.material_key.use_rgb;
2397 let batch_draw_kind =
2398 if matches!(batch_technique.special, TechniqueSpecial::Shadow) {
2399 MeshDrawKind::ShadowCaster
2400 } else if matches!(batch_technique.special, TechniqueSpecial::SkinnedMesh) {
2401 MeshDrawKind::SkinnedMesh
2402 } else if matches!(batch_technique.special, TechniqueSpecial::Mesh) {
2403 MeshDrawKind::StaticMesh
2404 } else {
2405 MeshDrawKind::SpriteQuad
2406 };
2407 let batch_pipeline_key = PipelineKey {
2408 technique: batch_technique,
2409 blend: sprite.blend,
2410 alpha_blend: if matches!(batch_technique.special, TechniqueSpecial::Overlay)
2411 {
2412 false
2413 } else {
2414 sprite.alpha_blend || requires_alpha_composition
2415 },
2416 use_depth,
2417 cull_back: pipeline_cull_back(sprite, batch.material.cull_disable),
2418 mesh_fx_variant: crate::mesh3d::mesh_effect_variant_bits_from_runtime_desc(
2419 &batch.runtime_desc,
2420 ),
2421 pipeline_name: resolved_mesh_pipeline_name_from_runtime_desc(
2422 &batch.runtime_desc,
2423 batch_technique,
2424 ),
2425 program: mesh_effect_program_from_runtime_desc(&batch.runtime_desc),
2426 };
2427 let base = self.verts.len() as u32;
2428 let mut added = 0u32;
2429 let vs_uniform = if batch.skinned {
2430 render_sprite_frame_per_mesh_set_effect_constant_skinned_mesh(
2431 sprite,
2432 sprite.x as f32,
2433 sprite.y as f32,
2434 win_w,
2435 win_h,
2436 batch.frame_cols,
2437 &batch.material,
2438 )
2439 } else {
2440 render_sprite_frame_per_mesh_set_effect_constant_mesh(
2441 sprite,
2442 sprite.x as f32,
2443 sprite.y as f32,
2444 win_w,
2445 win_h,
2446 batch.frame_cols,
2447 &batch.material,
2448 )
2449 };
2450 let bone_uniform = BoneUniform::from_cols_list(&batch.bone_cols);
2451 let effects4 = [
2452 sprite.mask_mode as f32,
2453 if sprite.alpha_test || batch.material.alpha_test_enable {
2454 1.0
2455 } else {
2456 0.0
2457 },
2458 if sprite.light_enabled { 1.0 } else { 0.0 },
2459 if sprite.fog_enabled { 1.0 } else { 0.0 },
2460 ];
2461 for tri in batch.vertices.chunks(3) {
2462 if tri.len() != 3 {
2463 continue;
2464 }
2465 let v0_bones = [
2466 tri[0].bone_indices[0] as f32,
2467 tri[0].bone_indices[1] as f32,
2468 tri[0].bone_indices[2] as f32,
2469 tri[0].bone_indices[3] as f32,
2470 ];
2471 let v1_bones = [
2472 tri[1].bone_indices[0] as f32,
2473 tri[1].bone_indices[1] as f32,
2474 tri[1].bone_indices[2] as f32,
2475 tri[1].bone_indices[3] as f32,
2476 ];
2477 let v2_bones = [
2478 tri[2].bone_indices[0] as f32,
2479 tri[2].bone_indices[1] as f32,
2480 tri[2].bone_indices[2] as f32,
2481 tri[2].bone_indices[3] as f32,
2482 ];
2483 let mut v0_effects8 = effects8;
2484 let mut v1_effects8 = effects8;
2485 let mut v2_effects8 = effects8;
2486 let mut v0_effects9 = effects9;
2487 let mut v1_effects9 = effects9;
2488 let mut v2_effects9 = effects9;
2489 v0_effects8[0] = tri[0].color[0];
2490 v0_effects8[1] = tri[0].color[1];
2491 v0_effects8[2] = tri[0].color[2];
2492 v1_effects8[0] = tri[1].color[0];
2493 v1_effects8[1] = tri[1].color[1];
2494 v1_effects8[2] = tri[1].color[2];
2495 v2_effects8[0] = tri[2].color[0];
2496 v2_effects8[1] = tri[2].color[1];
2497 v2_effects8[2] = tri[2].color[2];
2498 v0_effects9[0] = tri[0].color[3];
2499 v1_effects9[0] = tri[1].color[3];
2500 v2_effects9[0] = tri[2].color[3];
2501 self.verts.extend_from_slice(&[
2502 Vertex {
2503 pos: tri[0].pos,
2504 uv: tri[0].uv,
2505 uv_aux: [0.0, 0.0],
2506 alpha,
2507 effects1,
2508 effects2,
2509 effects3,
2510 effects4,
2511 effects5,
2512 effects6,
2513 effects7,
2514 effects8: v0_effects8,
2515 effects9: v0_effects9,
2516 effects10,
2517 effects11,
2518 world_pos: zero4,
2519 world_normal: [
2520 tri[0].normal[0],
2521 tri[0].normal[1],
2522 tri[0].normal[2],
2523 0.0,
2524 ],
2525 world_tangent: [
2526 tri[0].tangent[0],
2527 tri[0].tangent[1],
2528 tri[0].tangent[2],
2529 0.0,
2530 ],
2531 world_binormal: [
2532 tri[0].binormal[0],
2533 tri[0].binormal[1],
2534 tri[0].binormal[2],
2535 0.0,
2536 ],
2537 shadow_pos: zero4,
2538 bone_indices: v0_bones,
2539 bone_weights: tri[0].bone_weights,
2540 light_pos_kind: light_pos_kind_base,
2541 light_dir_shadow: light_dir_shadow_base,
2542 light_atten: light_atten_base,
2543 light_cone: light_cone_base,
2544 },
2545 Vertex {
2546 pos: tri[1].pos,
2547 uv: tri[1].uv,
2548 uv_aux: [0.0, 0.0],
2549 alpha,
2550 effects1,
2551 effects2,
2552 effects3,
2553 effects4,
2554 effects5,
2555 effects6,
2556 effects7,
2557 effects8: v1_effects8,
2558 effects9: v1_effects9,
2559 effects10,
2560 effects11,
2561 world_pos: zero4,
2562 world_normal: [
2563 tri[1].normal[0],
2564 tri[1].normal[1],
2565 tri[1].normal[2],
2566 0.0,
2567 ],
2568 world_tangent: [
2569 tri[1].tangent[0],
2570 tri[1].tangent[1],
2571 tri[1].tangent[2],
2572 0.0,
2573 ],
2574 world_binormal: [
2575 tri[1].binormal[0],
2576 tri[1].binormal[1],
2577 tri[1].binormal[2],
2578 0.0,
2579 ],
2580 shadow_pos: zero4,
2581 bone_indices: v1_bones,
2582 bone_weights: tri[1].bone_weights,
2583 light_pos_kind: light_pos_kind_base,
2584 light_dir_shadow: light_dir_shadow_base,
2585 light_atten: light_atten_base,
2586 light_cone: light_cone_base,
2587 },
2588 Vertex {
2589 pos: tri[2].pos,
2590 uv: tri[2].uv,
2591 uv_aux: [0.0, 0.0],
2592 alpha,
2593 effects1,
2594 effects2,
2595 effects3,
2596 effects4,
2597 effects5,
2598 effects6,
2599 effects7,
2600 effects8: v2_effects8,
2601 effects9: v2_effects9,
2602 effects10,
2603 effects11,
2604 world_pos: zero4,
2605 world_normal: [
2606 tri[2].normal[0],
2607 tri[2].normal[1],
2608 tri[2].normal[2],
2609 0.0,
2610 ],
2611 world_tangent: [
2612 tri[2].tangent[0],
2613 tri[2].tangent[1],
2614 tri[2].tangent[2],
2615 0.0,
2616 ],
2617 world_binormal: [
2618 tri[2].binormal[0],
2619 tri[2].binormal[1],
2620 tri[2].binormal[2],
2621 0.0,
2622 ],
2623 shadow_pos: zero4,
2624 bone_indices: v2_bones,
2625 bone_weights: tri[2].bone_weights,
2626 light_pos_kind: light_pos_kind_base,
2627 light_dir_shadow: light_dir_shadow_base,
2628 light_atten: light_atten_base,
2629 light_cone: light_cone_base,
2630 },
2631 ]);
2632 added += 3;
2633 }
2634 if added != 0 {
2635 self.draws.push(DrawCommand {
2636 image_id: img_id,
2637 mesh_texture_path: batch.texture_path.clone(),
2638 mesh_normal_texture_path: batch.material.normal_texture_path.clone(),
2639 mesh_toon_texture_path: batch.material.toon_texture_path.clone(),
2640 mask_image_id: None,
2641 tonecurve_image_id: if has_tonecurve {
2642 sprite.tonecurve_image_id
2643 } else {
2644 None
2645 },
2646 fog_image_id: if has_fog_tex {
2647 sprite.fog_texture_image_id
2648 } else {
2649 None
2650 },
2651 wipe_src_image_id: if has_wipe_src {
2652 sprite.wipe_src_image_id
2653 } else {
2654 None
2655 },
2656 range: base..base + added,
2657 scissor,
2658 pipeline_key: batch_pipeline_key,
2659 shadow_pipeline_name: Some(
2660 resolved_shadow_pipeline_name_from_runtime_desc(
2661 &batch.runtime_desc,
2662 ),
2663 ),
2664 draw_kind: batch_draw_kind,
2665 mesh_material_key: mesh_material_key_for_batch(
2666 sprite,
2667 batch_technique.special,
2668 &batch,
2669 ),
2670 shadow_cast: sprite.shadow_cast
2671 && use_depth
2672 && sprite.light_cone[3] > 0.5
2673 && batch.material.shadow_map_enable,
2674 vs_uniform,
2675 bone_uniform,
2676 });
2677 }
2678 }
2679 continue;
2680 }
2681 let Some(img) = img else {
2682 continue;
2683 };
2684 let (u0, v0, u1, v1) = (
2685 (src_left / img.width as f32).clamp(0.0, 1.0),
2686 (src_top / img.height as f32).clamp(0.0, 1.0),
2687 (src_right / img.width as f32).clamp(0.0, 1.0),
2688 (src_bottom / img.height as f32).clamp(0.0, 1.0),
2689 );
2690 let mask_uv = if let Some(mask_id) = sprite.mask_image_id {
2691 if let Some(mask_img) = images.get(mask_id) {
2692 let mw = mask_img.width.max(1) as f32;
2693 let mh = mask_img.height.max(1) as f32;
2694 [
2695 [
2696 (src_left + sprite.mask_offset_x as f32) / mw,
2697 (src_top + sprite.mask_offset_y as f32) / mh,
2698 ],
2699 [
2700 (src_right + sprite.mask_offset_x as f32) / mw,
2701 (src_top + sprite.mask_offset_y as f32) / mh,
2702 ],
2703 [
2704 (src_right + sprite.mask_offset_x as f32) / mw,
2705 (src_bottom + sprite.mask_offset_y as f32) / mh,
2706 ],
2707 [
2708 (src_left + sprite.mask_offset_x as f32) / mw,
2709 (src_bottom + sprite.mask_offset_y as f32) / mh,
2710 ],
2711 ]
2712 } else {
2713 [[0.0, 0.0]; 4]
2714 }
2715 } else {
2716 [[0.0, 0.0]; 4]
2717 };
2718
2719 let Some([p0, p1, p2, p3]) =
2720 sprite_quad_points(sprite, dst_x, dst_y, dst_w, dst_h, win_w, win_h)
2721 else {
2722 continue;
2723 };
2724 let base = self.verts.len() as u32;
2725 let (x0, y0, z0) = pixel_to_ndc(p0.x, p0.y, p0.depth, win_w, win_h);
2726 let (x1, y1, z1) = pixel_to_ndc(p1.x, p1.y, p1.depth, win_w, win_h);
2727 let (x2, y2, z2) = pixel_to_ndc(p2.x, p2.y, p2.depth, win_w, win_h);
2728 let (x3, y3, z3) = pixel_to_ndc(p3.x, p3.y, p3.depth, win_w, win_h);
2729 self.verts.extend_from_slice(&[
2730 Vertex {
2731 pos: [x0, y0, z0],
2732 uv: [u0, v0],
2733 uv_aux: mask_uv[0],
2734 alpha,
2735 effects1,
2736 effects2,
2737 effects3,
2738 effects4,
2739 effects5,
2740 effects6,
2741 effects7,
2742 effects8,
2743 effects9,
2744 effects10,
2745 effects11,
2746 world_pos: zero4,
2747 world_normal: zero4,
2748 world_tangent: zero4,
2749 world_binormal: zero4,
2750 shadow_pos: zero4,
2751 bone_indices: zero4,
2752 bone_weights: zero4,
2753 light_pos_kind: light_pos_kind_base,
2754 light_dir_shadow: light_dir_shadow_base,
2755 light_atten: light_atten_base,
2756 light_cone: light_cone_base,
2757 },
2758 Vertex {
2759 pos: [x1, y1, z1],
2760 uv: [u1, v0],
2761 uv_aux: mask_uv[1],
2762 alpha,
2763 effects1,
2764 effects2,
2765 effects3,
2766 effects4,
2767 effects5,
2768 effects6,
2769 effects7,
2770 effects8,
2771 effects9,
2772 effects10,
2773 effects11,
2774 world_pos: zero4,
2775 world_normal: zero4,
2776 world_tangent: zero4,
2777 world_binormal: zero4,
2778 shadow_pos: zero4,
2779 bone_indices: zero4,
2780 bone_weights: zero4,
2781 light_pos_kind: light_pos_kind_base,
2782 light_dir_shadow: light_dir_shadow_base,
2783 light_atten: light_atten_base,
2784 light_cone: light_cone_base,
2785 },
2786 Vertex {
2787 pos: [x2, y2, z2],
2788 uv: [u1, v1],
2789 uv_aux: mask_uv[2],
2790 alpha,
2791 effects1,
2792 effects2,
2793 effects3,
2794 effects4,
2795 effects5,
2796 effects6,
2797 effects7,
2798 effects8,
2799 effects9,
2800 effects10,
2801 effects11,
2802 world_pos: zero4,
2803 world_normal: zero4,
2804 world_tangent: zero4,
2805 world_binormal: zero4,
2806 shadow_pos: zero4,
2807 bone_indices: zero4,
2808 bone_weights: zero4,
2809 light_pos_kind: light_pos_kind_base,
2810 light_dir_shadow: light_dir_shadow_base,
2811 light_atten: light_atten_base,
2812 light_cone: light_cone_base,
2813 },
2814 Vertex {
2815 pos: [x0, y0, z0],
2816 uv: [u0, v0],
2817 uv_aux: mask_uv[0],
2818 alpha,
2819 effects1,
2820 effects2,
2821 effects3,
2822 effects4,
2823 effects5,
2824 effects6,
2825 effects7,
2826 effects8,
2827 effects9,
2828 effects10,
2829 effects11,
2830 world_pos: zero4,
2831 world_normal: zero4,
2832 world_tangent: zero4,
2833 world_binormal: zero4,
2834 shadow_pos: zero4,
2835 bone_indices: zero4,
2836 bone_weights: zero4,
2837 light_pos_kind: light_pos_kind_base,
2838 light_dir_shadow: light_dir_shadow_base,
2839 light_atten: light_atten_base,
2840 light_cone: light_cone_base,
2841 },
2842 Vertex {
2843 pos: [x2, y2, z2],
2844 uv: [u1, v1],
2845 uv_aux: mask_uv[2],
2846 alpha,
2847 effects1,
2848 effects2,
2849 effects3,
2850 effects4,
2851 effects5,
2852 effects6,
2853 effects7,
2854 effects8,
2855 effects9,
2856 effects10,
2857 effects11,
2858 world_pos: zero4,
2859 world_normal: zero4,
2860 world_tangent: zero4,
2861 world_binormal: zero4,
2862 shadow_pos: zero4,
2863 bone_indices: zero4,
2864 bone_weights: zero4,
2865 light_pos_kind: light_pos_kind_base,
2866 light_dir_shadow: light_dir_shadow_base,
2867 light_atten: light_atten_base,
2868 light_cone: light_cone_base,
2869 },
2870 Vertex {
2871 pos: [x3, y3, z3],
2872 uv: [u0, v1],
2873 uv_aux: mask_uv[3],
2874 alpha,
2875 effects1,
2876 effects2,
2877 effects3,
2878 effects4,
2879 effects5,
2880 effects6,
2881 effects7,
2882 effects8,
2883 effects9,
2884 effects10,
2885 effects11,
2886 world_pos: zero4,
2887 world_normal: zero4,
2888 world_tangent: zero4,
2889 world_binormal: zero4,
2890 shadow_pos: zero4,
2891 bone_indices: zero4,
2892 bone_weights: zero4,
2893 light_pos_kind: light_pos_kind_base,
2894 light_dir_shadow: light_dir_shadow_base,
2895 light_atten: light_atten_base,
2896 light_cone: light_cone_base,
2897 },
2898 ]);
2899 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2900 let sprite_vs_uniform = sprite2d_uniform_for_effects(
2901 win_w, win_h, effects1, effects2, effects3, effects4, effects5, effects6,
2902 effects7, effects8, effects9, effects10, effects11,
2903 );
2904 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
2905 let sprite_vs_uniform = VsUniform::for_2d(win_w, win_h);
2906
2907 self.draws.push(DrawCommand {
2908 image_id: img_id,
2909 mesh_texture_path: None,
2910 mesh_normal_texture_path: None,
2911 mesh_toon_texture_path: None,
2912 mask_image_id: if has_mask { sprite.mask_image_id } else { None },
2913 tonecurve_image_id: if has_tonecurve {
2914 sprite.tonecurve_image_id
2915 } else {
2916 None
2917 },
2918 fog_image_id: if has_fog_tex {
2919 sprite.fog_texture_image_id
2920 } else {
2921 None
2922 },
2923 wipe_src_image_id: if has_wipe_src {
2924 sprite.wipe_src_image_id
2925 } else {
2926 None
2927 },
2928 range: base..base + 6,
2929 scissor,
2930 pipeline_key,
2931 shadow_pipeline_name: None,
2932 draw_kind,
2933 mesh_material_key: mesh_material_key_for_sprite(sprite, technique.special),
2934 shadow_cast: sprite.shadow_cast && use_depth,
2935 vs_uniform: sprite_vs_uniform,
2936 bone_uniform: BoneUniform::zero(),
2937 });
2938 }
2939
2940 let blit_range = append_fullscreen_blit_vertices(&mut self.verts);
2941
2942 if self.draws.is_empty() {
2943 let mut encoder = self
2944 .device
2945 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
2946 label: Some("siglus-clear-encoder"),
2947 });
2948 self.render_command_slice(
2949 &mut encoder,
2950 ColorTarget::External(&view),
2951 DepthTarget::Main,
2952 0..0,
2953 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
2954 true,
2955 None,
2956 None,
2957 )?;
2958 self.queue.submit(Some(encoder.finish()));
2959 frame.present();
2960 return Ok(());
2961 }
2962
2963 self.ensure_vertex_capacity(self.verts.len())?;
2964 self.queue
2965 .write_buffer(&self.vertex_buf, 0, bytemuck::cast_slice(&self.verts));
2966 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
2967 {
2968 let sprite2d_verts: Vec<VertexSprite2dData> = self
2969 .verts
2970 .iter()
2971 .copied()
2972 .map(VertexSprite2dData::from)
2973 .collect();
2974 self.queue.write_buffer(
2975 &self.vertex_sprite2d_buf,
2976 0,
2977 bytemuck::cast_slice(&sprite2d_verts),
2978 );
2979 }
2980
2981 let draws_snapshot = self.draws.clone();
2982 let mut live_image_ids = HashSet::new();
2983 for cmd in &draws_snapshot {
2984 if let Some(id) = cmd.image_id {
2985 live_image_ids.insert(id);
2986 self.ensure_texture_uploaded(images, id)?;
2987 }
2988 if let Some(id) = cmd.mask_image_id {
2989 live_image_ids.insert(id);
2990 self.ensure_texture_uploaded(images, id)?;
2991 }
2992 if let Some(id) = cmd.tonecurve_image_id {
2993 live_image_ids.insert(id);
2994 self.ensure_texture_uploaded(images, id)?;
2995 }
2996 if let Some(id) = cmd.fog_image_id {
2997 live_image_ids.insert(id);
2998 self.ensure_texture_uploaded(images, id)?;
2999 }
3000 if let Some(id) = cmd.wipe_src_image_id {
3001 live_image_ids.insert(id);
3002 self.ensure_texture_uploaded(images, id)?;
3003 }
3004 }
3005 self.textures.retain(|id, _| live_image_ids.contains(id));
3006
3007 let has_overlay = draws_snapshot.iter().any(|cmd| {
3008 matches!(
3009 cmd.pipeline_key.technique.special,
3010 TechniqueSpecial::Overlay
3011 )
3012 });
3013 for cmd in draws_snapshot.clone() {
3014 self.ensure_pipeline(cmd.pipeline_key.clone());
3015 if cmd.shadow_cast {
3016 self.ensure_pipeline(shadow_pipeline_key(
3017 cmd.pipeline_key,
3018 cmd.shadow_pipeline_name.as_deref(),
3019 ));
3020 }
3021 }
3022 if has_overlay {
3023 self.ensure_pipeline(PipelineKey {
3024 technique: TechniqueKey {
3025 d3: false,
3026 light: false,
3027 fog: false,
3028 tex: 1,
3029 diffuse: false,
3030 mrbd: false,
3031 rgb: false,
3032 tonecurve: false,
3033 mask: false,
3034 special: TechniqueSpecial::None,
3035 },
3036 blend: SpriteBlend::Normal,
3037 alpha_blend: false,
3038 use_depth: false,
3039 cull_back: false,
3040 mesh_fx_variant: 0,
3041 pipeline_name: String::new(),
3042 program: EffectProgram::Sprite2D,
3043 });
3044 }
3045
3046 let mut encoder = self
3047 .device
3048 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3049 label: Some("siglus-sprite-encoder"),
3050 });
3051 let draws_for_pass = self.draws.clone();
3052
3053 let shadow_indices: Vec<usize> = draws_for_pass
3054 .iter()
3055 .enumerate()
3056 .filter_map(|(idx, cmd)| if cmd.shadow_cast { Some(idx) } else { None })
3057 .collect();
3058 if !shadow_indices.is_empty() {
3059 self.render_command_slice(
3060 &mut encoder,
3061 ColorTarget::Internal(InternalColorTarget::ShadowMap),
3062 DepthTarget::Shadow,
3063 0..0,
3064 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
3065 true,
3066 None,
3067 None,
3068 )?;
3069 for idx in shadow_indices {
3070 self.render_command_slice(
3071 &mut encoder,
3072 ColorTarget::Internal(InternalColorTarget::ShadowMap),
3073 DepthTarget::Shadow,
3074 idx..idx + 1,
3075 wgpu::LoadOp::Load,
3076 false,
3077 None,
3078 Some(TechniqueSpecial::Shadow),
3079 )?;
3080 }
3081 }
3082
3083 if !has_overlay {
3084 self.render_command_slice(
3085 &mut encoder,
3086 ColorTarget::External(&view),
3087 DepthTarget::Main,
3088 0..draws_for_pass.len(),
3089 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
3090 true,
3091 None,
3092 None,
3093 )?;
3094 } else {
3095 self.render_command_slice(
3096 &mut encoder,
3097 ColorTarget::Internal(InternalColorTarget::SceneA),
3098 DepthTarget::Main,
3099 0..0,
3100 wgpu::LoadOp::Clear(wgpu::Color::BLACK),
3101 true,
3102 None,
3103 None,
3104 )?;
3105 let mut current_is_a = true;
3106 let mut index = 0usize;
3107 while index < draws_for_pass.len() {
3108 let is_overlay = matches!(
3109 draws_for_pass[index].pipeline_key.technique.special,
3110 TechniqueSpecial::Overlay
3111 );
3112 let start = index;
3113 while index < draws_for_pass.len()
3114 && matches!(
3115 draws_for_pass[index].pipeline_key.technique.special,
3116 TechniqueSpecial::Overlay
3117 ) == is_overlay
3118 {
3119 index += 1;
3120 }
3121 if is_overlay {
3122 let (src, dst) = if current_is_a {
3123 (BackdropTarget::SceneA, InternalColorTarget::SceneB)
3124 } else {
3125 (BackdropTarget::SceneB, InternalColorTarget::SceneA)
3126 };
3127 self.render_copy_pass(
3128 &mut encoder,
3129 ColorTarget::Internal(dst),
3130 src,
3131 blit_range.clone(),
3132 )?;
3133 self.render_command_slice(
3134 &mut encoder,
3135 ColorTarget::Internal(dst),
3136 DepthTarget::Main,
3137 start..index,
3138 wgpu::LoadOp::Load,
3139 false,
3140 Some(src),
3141 None,
3142 )?;
3143 current_is_a = !current_is_a;
3144 } else {
3145 let color_target = if current_is_a {
3146 ColorTarget::Internal(InternalColorTarget::SceneA)
3147 } else {
3148 ColorTarget::Internal(InternalColorTarget::SceneB)
3149 };
3150 self.render_command_slice(
3151 &mut encoder,
3152 color_target,
3153 DepthTarget::Main,
3154 start..index,
3155 wgpu::LoadOp::Load,
3156 false,
3157 None,
3158 None,
3159 )?;
3160 }
3161 }
3162
3163 let final_src = if current_is_a {
3164 BackdropTarget::SceneA
3165 } else {
3166 BackdropTarget::SceneB
3167 };
3168 self.render_copy_pass(
3169 &mut encoder,
3170 ColorTarget::External(&view),
3171 final_src,
3172 blit_range,
3173 )?;
3174 }
3175
3176 self.queue.submit(Some(encoder.finish()));
3177 frame.present();
3178 Ok(())
3179 }
3180
3181 pub fn debug_read_render_chain_textures(&self) -> Result<Vec<RendererDebugTexture>> {
3182 let mut pending: HashMap<RendererDebugTextureKey, PendingRendererDebugTexture> = HashMap::new();
3183
3184 for (draw_idx, cmd) in self.draws.iter().enumerate() {
3185 let role_prefix = format!("draw[{draw_idx}]");
3186 self.debug_add_base_texture_usage(&mut pending, cmd, &format!("{role_prefix}.base"));
3187 self.debug_add_image_texture_usage(
3188 &mut pending,
3189 cmd.mask_image_id,
3190 "image",
3191 &format!("{role_prefix}.mask"),
3192 );
3193 self.debug_add_image_texture_usage(
3194 &mut pending,
3195 cmd.tonecurve_image_id,
3196 "image",
3197 &format!("{role_prefix}.tonecurve"),
3198 );
3199 self.debug_add_image_texture_usage(
3200 &mut pending,
3201 cmd.fog_image_id,
3202 "image",
3203 &format!("{role_prefix}.fog"),
3204 );
3205 self.debug_add_aux_texture_usage(&mut pending, cmd, &format!("{role_prefix}.aux"));
3206 self.debug_add_external_texture_usage(
3207 &mut pending,
3208 cmd.mesh_normal_texture_path.as_deref(),
3209 "external",
3210 &format!("{role_prefix}.normal"),
3211 );
3212 self.debug_add_external_texture_usage(
3213 &mut pending,
3214 cmd.mesh_toon_texture_path.as_deref(),
3215 "external",
3216 &format!("{role_prefix}.toon"),
3217 );
3218 if cmd.pipeline_key.use_depth
3219 || cmd.shadow_cast
3220 || cmd.mesh_material_key.as_ref().is_some_and(|k| k.shadow)
3221 {
3222 self.debug_add_render_target_usage(
3223 &mut pending,
3224 RendererDebugRenderTarget::ShadowMap,
3225 &format!("{role_prefix}.shadow"),
3226 );
3227 }
3228 }
3229
3230 if self.draws.iter().any(|cmd| {
3231 matches!(
3232 cmd.pipeline_key.technique.special,
3233 TechniqueSpecial::Overlay
3234 )
3235 }) {
3236 self.debug_add_render_target_usage(
3237 &mut pending,
3238 RendererDebugRenderTarget::SceneA,
3239 "overlay.backdrop.scene_a",
3240 );
3241 self.debug_add_render_target_usage(
3242 &mut pending,
3243 RendererDebugRenderTarget::SceneB,
3244 "overlay.backdrop.scene_b",
3245 );
3246 }
3247 if self.draws.is_empty() {
3248 self.debug_add_default_aux_usage(&mut pending, "empty-frame.default_aux");
3249 }
3250
3251 let mut items = Vec::with_capacity(pending.len());
3252 for (key, meta) in pending.into_iter() {
3253 let Some((width, height, version, rgba)) = self.debug_read_texture_by_key(&key)? else {
3254 continue;
3255 };
3256 let key_string = Self::debug_texture_key_string(&key);
3257 items.push((
3258 meta.order,
3259 RendererDebugTexture {
3260 key: key_string,
3261 kind: meta.kind,
3262 label: meta.label,
3263 usage: meta.usage.join("; "),
3264 usage_count: meta.usage.len(),
3265 width,
3266 height,
3267 version,
3268 rgba,
3269 },
3270 ));
3271 }
3272 items.sort_by_key(|(order, _)| *order);
3273 Ok(items.into_iter().map(|(_, item)| item).collect())
3274 }
3275
3276 fn debug_add_pending_texture_usage(
3277 &self,
3278 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3279 key: RendererDebugTextureKey,
3280 kind: &str,
3281 label: String,
3282 width: u32,
3283 height: u32,
3284 version: u64,
3285 usage: &str,
3286 ) {
3287 let order = pending.len();
3288 let entry = pending.entry(key).or_insert_with(|| PendingRendererDebugTexture {
3289 order,
3290 kind: kind.to_string(),
3291 label,
3292 usage: Vec::new(),
3293 width,
3294 height,
3295 version,
3296 });
3297 if !entry.usage.iter().any(|s| s == usage) {
3298 entry.usage.push(usage.to_string());
3299 }
3300 }
3301
3302 fn debug_add_default_aux_usage(
3303 &self,
3304 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3305 usage: &str,
3306 ) {
3307 self.debug_add_pending_texture_usage(
3308 pending,
3309 RendererDebugTextureKey::DefaultAux,
3310 "default",
3311 "default_aux".to_string(),
3312 self.default_aux.width,
3313 self.default_aux.height,
3314 self.default_aux.version,
3315 usage,
3316 );
3317 }
3318
3319 fn debug_add_image_texture_usage(
3320 &self,
3321 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3322 image_id: Option<ImageId>,
3323 kind: &str,
3324 usage: &str,
3325 ) {
3326 if let Some(id) = image_id {
3327 if let Some(tex) = self.textures.get(&id) {
3328 self.debug_add_pending_texture_usage(
3329 pending,
3330 RendererDebugTextureKey::Image(id),
3331 kind,
3332 format!("ImageId({})", id.index()),
3333 tex.width,
3334 tex.height,
3335 tex.version,
3336 usage,
3337 );
3338 return;
3339 }
3340 }
3341 self.debug_add_default_aux_usage(pending, usage);
3342 }
3343
3344 fn debug_add_external_texture_usage(
3345 &self,
3346 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3347 path: Option<&Path>,
3348 kind: &str,
3349 usage: &str,
3350 ) {
3351 if let Some(path) = path {
3352 if let Some(tex) = self.external_textures.get(path) {
3353 self.debug_add_pending_texture_usage(
3354 pending,
3355 RendererDebugTextureKey::External(path.to_path_buf()),
3356 kind,
3357 path.display().to_string(),
3358 tex.width,
3359 tex.height,
3360 tex.version,
3361 usage,
3362 );
3363 return;
3364 }
3365 }
3366 self.debug_add_default_aux_usage(pending, usage);
3367 }
3368
3369 fn debug_add_render_target_usage(
3370 &self,
3371 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3372 target: RendererDebugRenderTarget,
3373 usage: &str,
3374 ) {
3375 let rt = self.debug_render_target_ref(target);
3376 self.debug_add_pending_texture_usage(
3377 pending,
3378 RendererDebugTextureKey::RenderTarget(target),
3379 "render-target",
3380 match target {
3381 RendererDebugRenderTarget::SceneA => "scene_a".to_string(),
3382 RendererDebugRenderTarget::SceneB => "scene_b".to_string(),
3383 RendererDebugRenderTarget::ShadowMap => "shadow_map".to_string(),
3384 },
3385 rt.width,
3386 rt.height,
3387 self.debug_frame_serial,
3388 usage,
3389 );
3390 }
3391
3392 fn debug_add_base_texture_usage(
3393 &self,
3394 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3395 cmd: &DrawCommand,
3396 usage: &str,
3397 ) {
3398 if let Some(path) = cmd.mesh_texture_path.as_deref() {
3399 if let Some(tex) = self.external_textures.get(path) {
3400 self.debug_add_pending_texture_usage(
3401 pending,
3402 RendererDebugTextureKey::External(path.to_path_buf()),
3403 "external",
3404 path.display().to_string(),
3405 tex.width,
3406 tex.height,
3407 tex.version,
3408 usage,
3409 );
3410 return;
3411 }
3412 }
3413 self.debug_add_image_texture_usage(pending, cmd.image_id, "image", usage);
3414 }
3415
3416 fn debug_add_aux_texture_usage(
3417 &self,
3418 pending: &mut HashMap<RendererDebugTextureKey, PendingRendererDebugTexture>,
3419 cmd: &DrawCommand,
3420 usage: &str,
3421 ) {
3422 if matches!(
3423 cmd.pipeline_key.technique.special,
3424 TechniqueSpecial::Overlay
3425 ) {
3426 self.debug_add_render_target_usage(pending, RendererDebugRenderTarget::SceneA, usage);
3427 self.debug_add_render_target_usage(pending, RendererDebugRenderTarget::SceneB, usage);
3428 return;
3429 }
3430 self.debug_add_image_texture_usage(pending, cmd.wipe_src_image_id, "image", usage);
3431 }
3432
3433 fn debug_render_target_ref(&self, target: RendererDebugRenderTarget) -> &RenderTargetTexture {
3434 match target {
3435 RendererDebugRenderTarget::SceneA => &self.scene_a,
3436 RendererDebugRenderTarget::SceneB => &self.scene_b,
3437 RendererDebugRenderTarget::ShadowMap => &self.shadow_map,
3438 }
3439 }
3440
3441 fn debug_texture_key_string(key: &RendererDebugTextureKey) -> String {
3442 match key {
3443 RendererDebugTextureKey::DefaultAux => "default_aux".to_string(),
3444 RendererDebugTextureKey::Image(id) => format!("image:{}", id.index()),
3445 RendererDebugTextureKey::External(path) => format!("external:{}", path.display()),
3446 RendererDebugTextureKey::RenderTarget(RendererDebugRenderTarget::SceneA) => {
3447 "render-target:scene_a".to_string()
3448 }
3449 RendererDebugTextureKey::RenderTarget(RendererDebugRenderTarget::SceneB) => {
3450 "render-target:scene_b".to_string()
3451 }
3452 RendererDebugTextureKey::RenderTarget(RendererDebugRenderTarget::ShadowMap) => {
3453 "render-target:shadow_map".to_string()
3454 }
3455 }
3456 }
3457
3458 fn debug_read_texture_by_key(
3459 &self,
3460 key: &RendererDebugTextureKey,
3461 ) -> Result<Option<(u32, u32, u64, Vec<u8>)>> {
3462 match key {
3463 RendererDebugTextureKey::DefaultAux => Ok(Some((
3464 self.default_aux.width,
3465 self.default_aux.height,
3466 self.default_aux.version,
3467 self.debug_read_texture_rgba(
3468 &self.default_aux._tex,
3469 self.default_aux.width,
3470 self.default_aux.height,
3471 wgpu::TextureFormat::Rgba8UnormSrgb,
3472 )?,
3473 ))),
3474 RendererDebugTextureKey::Image(id) => {
3475 let Some(tex) = self.textures.get(id) else {
3476 return Ok(None);
3477 };
3478 Ok(Some((
3479 tex.width,
3480 tex.height,
3481 tex.version,
3482 self.debug_read_texture_rgba(
3483 &tex._tex,
3484 tex.width,
3485 tex.height,
3486 wgpu::TextureFormat::Rgba8UnormSrgb,
3487 )?,
3488 )))
3489 }
3490 RendererDebugTextureKey::External(path) => {
3491 let Some(tex) = self.external_textures.get(path) else {
3492 return Ok(None);
3493 };
3494 Ok(Some((
3495 tex.width,
3496 tex.height,
3497 tex.version,
3498 self.debug_read_texture_rgba(
3499 &tex._tex,
3500 tex.width,
3501 tex.height,
3502 wgpu::TextureFormat::Rgba8UnormSrgb,
3503 )?,
3504 )))
3505 }
3506 RendererDebugTextureKey::RenderTarget(target) => {
3507 let rt = self.debug_render_target_ref(*target);
3508 Ok(Some((
3509 rt.width,
3510 rt.height,
3511 self.debug_frame_serial,
3512 self.debug_read_texture_rgba(&rt._tex, rt.width, rt.height, rt.format)?,
3513 )))
3514 }
3515 }
3516 }
3517
3518 fn debug_read_texture_rgba(
3519 &self,
3520 texture: &wgpu::Texture,
3521 width: u32,
3522 height: u32,
3523 format: wgpu::TextureFormat,
3524 ) -> Result<Vec<u8>> {
3525 if width == 0 || height == 0 {
3526 return Ok(Vec::new());
3527 }
3528 let bytes_per_pixel = 4u32;
3529 let unpadded_bytes_per_row = width.saturating_mul(bytes_per_pixel);
3530 let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
3531 let padded_bytes_per_row = ((unpadded_bytes_per_row + align - 1) / align) * align;
3532 let output_buffer_size = padded_bytes_per_row as u64 * height as u64;
3533 let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
3534 label: Some("siglus-debug-texture-readback"),
3535 size: output_buffer_size,
3536 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
3537 mapped_at_creation: false,
3538 });
3539 let mut encoder = self
3540 .device
3541 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
3542 label: Some("siglus-debug-texture-readback-encoder"),
3543 });
3544 encoder.copy_texture_to_buffer(
3545 wgpu::ImageCopyTexture {
3546 texture,
3547 mip_level: 0,
3548 origin: wgpu::Origin3d::ZERO,
3549 aspect: wgpu::TextureAspect::All,
3550 },
3551 wgpu::ImageCopyBuffer {
3552 buffer: &output_buffer,
3553 layout: wgpu::ImageDataLayout {
3554 offset: 0,
3555 bytes_per_row: Some(padded_bytes_per_row),
3556 rows_per_image: Some(height),
3557 },
3558 },
3559 wgpu::Extent3d {
3560 width,
3561 height,
3562 depth_or_array_layers: 1,
3563 },
3564 );
3565 self.queue.submit(Some(encoder.finish()));
3566
3567 let buffer_slice = output_buffer.slice(..);
3568 let (tx, rx) = std::sync::mpsc::channel();
3569 buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
3570 let _ = tx.send(result);
3571 });
3572 self.device.poll(wgpu::Maintain::Wait);
3573 rx.recv()
3574 .context("wait for debug texture readback")?
3575 .context("map debug texture readback")?;
3576 let data = buffer_slice.get_mapped_range();
3577 let mut rgba = vec![0u8; (width as usize) * (height as usize) * 4];
3578 for y in 0..height as usize {
3579 let src_offset = y * padded_bytes_per_row as usize;
3580 let dst_offset = y * unpadded_bytes_per_row as usize;
3581 let src = &data[src_offset..src_offset + unpadded_bytes_per_row as usize];
3582 let dst = &mut rgba[dst_offset..dst_offset + unpadded_bytes_per_row as usize];
3583 match format {
3584 wgpu::TextureFormat::Bgra8Unorm | wgpu::TextureFormat::Bgra8UnormSrgb => {
3585 for (src_px, dst_px) in src.chunks_exact(4).zip(dst.chunks_exact_mut(4)) {
3586 dst_px[0] = src_px[2];
3587 dst_px[1] = src_px[1];
3588 dst_px[2] = src_px[0];
3589 dst_px[3] = src_px[3];
3590 }
3591 }
3592 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Rgba8UnormSrgb => {
3593 dst.copy_from_slice(src);
3594 }
3595 other => {
3596 anyhow::bail!("unsupported debug texture readback format: {other:?}");
3597 }
3598 }
3599 }
3600 drop(data);
3601 output_buffer.unmap();
3602 Ok(rgba)
3603 }
3604
3605 fn ensure_mesh_asset(&mut self, images: &ImageManager, file_name: &str) -> Option<MeshAsset> {
3606 if let Some(asset) = self.mesh_assets.get(file_name) {
3607 return Some(asset.clone());
3608 }
3609 let asset =
3610 load_mesh_asset(images.project_dir(), images.current_append_dir(), file_name).ok()?;
3611 self.mesh_assets
3612 .insert(file_name.to_string(), asset.clone());
3613 Some(asset)
3614 }
3615
3616 fn ensure_external_texture(&mut self, path: &Path) -> Option<()> {
3617 if self.external_textures.contains_key(path) {
3618 return Some(());
3619 }
3620 let img = load_image_any(path, 0).ok()?;
3621 let tex = create_gpu_texture(
3622 &self.device,
3623 &self.queue,
3624 &format!("siglus-external-texture-{}", self.external_textures.len()),
3625 &img,
3626 0,
3627 )
3628 .ok()?;
3629 self.external_textures.insert(path.to_path_buf(), tex);
3630 Some(())
3631 }
3632
3633 fn ensure_pipeline(&mut self, key: PipelineKey) {
3634 if self.pipelines.contains_key(&key) {
3635 return;
3636 }
3637 let blend_state = if !key.alpha_blend {
3638 None
3639 } else {
3640 Some(match key.blend {
3641 SpriteBlend::Normal => wgpu::BlendState {
3642 color: wgpu::BlendComponent {
3643 src_factor: wgpu::BlendFactor::SrcAlpha,
3644 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
3645 operation: wgpu::BlendOperation::Add,
3646 },
3647 alpha: wgpu::BlendComponent {
3648 src_factor: wgpu::BlendFactor::One,
3649 dst_factor: wgpu::BlendFactor::One,
3650 operation: wgpu::BlendOperation::Add,
3651 },
3652 },
3653 SpriteBlend::Add => wgpu::BlendState {
3654 color: wgpu::BlendComponent {
3655 src_factor: wgpu::BlendFactor::SrcAlpha,
3656 dst_factor: wgpu::BlendFactor::One,
3657 operation: wgpu::BlendOperation::Add,
3658 },
3659 alpha: wgpu::BlendComponent::OVER,
3660 },
3661 SpriteBlend::Sub => wgpu::BlendState {
3662 color: wgpu::BlendComponent {
3663 src_factor: wgpu::BlendFactor::SrcAlpha,
3664 dst_factor: wgpu::BlendFactor::One,
3665 operation: wgpu::BlendOperation::ReverseSubtract,
3666 },
3667 alpha: wgpu::BlendComponent::OVER,
3668 },
3669 SpriteBlend::Mul => wgpu::BlendState {
3670 color: wgpu::BlendComponent {
3671 src_factor: wgpu::BlendFactor::Zero,
3672 dst_factor: wgpu::BlendFactor::Src,
3673 operation: wgpu::BlendOperation::Add,
3674 },
3675 alpha: wgpu::BlendComponent::OVER,
3676 },
3677 SpriteBlend::Screen => wgpu::BlendState {
3678 color: wgpu::BlendComponent {
3679 src_factor: wgpu::BlendFactor::One,
3680 dst_factor: wgpu::BlendFactor::OneMinusSrc,
3681 operation: wgpu::BlendOperation::Add,
3682 },
3683 alpha: wgpu::BlendComponent::OVER,
3684 },
3685 SpriteBlend::Overlay => wgpu::BlendState {
3686 color: wgpu::BlendComponent {
3687 src_factor: wgpu::BlendFactor::One,
3688 dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
3689 operation: wgpu::BlendOperation::Add,
3690 },
3691 alpha: wgpu::BlendComponent::OVER,
3692 },
3693 })
3694 };
3695
3696 let pipeline_label = format!("siglus-{}", technique_name_for_pipeline(&key));
3697 let pipeline = self
3698 .device
3699 .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
3700 label: Some(pipeline_label.as_str()),
3701 layout: Some(&self.pipeline_layout),
3702 vertex: wgpu::VertexState {
3703 module: &self.shader,
3704 entry_point: key.program.vertex_entry(),
3705 buffers: &[if key.program.uses_sprite2d_layout() {
3706 VertexSprite2d::layout()
3707 } else {
3708 Vertex::layout()
3709 }],
3710 compilation_options: Default::default(),
3711 },
3712 fragment: Some(wgpu::FragmentState {
3713 module: &self.shader,
3714 entry_point: key.program.fragment_entry(),
3715 targets: &[Some(wgpu::ColorTargetState {
3716 format: self.config.format,
3717 blend: blend_state,
3718 write_mask: wgpu::ColorWrites::ALL,
3719 })],
3720 compilation_options: Default::default(),
3721 }),
3722 primitive: wgpu::PrimitiveState {
3723 topology: wgpu::PrimitiveTopology::TriangleList,
3724 strip_index_format: None,
3725 front_face: wgpu::FrontFace::Ccw,
3726 cull_mode: if key.cull_back {
3727 Some(wgpu::Face::Back)
3728 } else {
3729 None
3730 },
3731 polygon_mode: wgpu::PolygonMode::Fill,
3732 unclipped_depth: false,
3733 conservative: false,
3734 },
3735 depth_stencil: Some(wgpu::DepthStencilState {
3736 format: wgpu::TextureFormat::Depth32Float,
3737 depth_write_enabled: key.use_depth,
3738 depth_compare: if key.use_depth {
3739 wgpu::CompareFunction::LessEqual
3740 } else {
3741 wgpu::CompareFunction::Always
3742 },
3743 stencil: wgpu::StencilState::default(),
3744 bias: wgpu::DepthBiasState::default(),
3745 }),
3746 multisample: wgpu::MultisampleState::default(),
3747 multiview: None,
3748 });
3749 self.pipelines.insert(key, pipeline);
3750 }
3751
3752 fn color_target_view<'a>(&'a self, target: ColorTarget<'a>) -> &'a wgpu::TextureView {
3753 match target {
3754 ColorTarget::External(view) => view,
3755 ColorTarget::Internal(InternalColorTarget::SceneA) => &self.scene_a.view,
3756 ColorTarget::Internal(InternalColorTarget::SceneB) => &self.scene_b.view,
3757 ColorTarget::Internal(InternalColorTarget::ShadowMap) => &self.shadow_map.view,
3758 }
3759 }
3760
3761 fn depth_target_view(&self, target: DepthTarget) -> Option<&wgpu::TextureView> {
3762 match target {
3763 DepthTarget::None => None,
3764 DepthTarget::Main => Some(&self.depth.view),
3765 DepthTarget::Shadow => Some(&self.shadow_depth.view),
3766 }
3767 }
3768
3769 fn backdrop_target_ref(&self, target: BackdropTarget) -> &RenderTargetTexture {
3770 match target {
3771 BackdropTarget::SceneA => &self.scene_a,
3772 BackdropTarget::SceneB => &self.scene_b,
3773 }
3774 }
3775
3776 fn render_command_slice(
3777 &mut self,
3778 encoder: &mut wgpu::CommandEncoder,
3779 color_target: ColorTarget<'_>,
3780 depth_target: DepthTarget,
3781 range: std::ops::Range<usize>,
3782 color_load: wgpu::LoadOp<wgpu::Color>,
3783 clear_depth: bool,
3784 overlay_backdrop: Option<BackdropTarget>,
3785 force_special: Option<TechniqueSpecial>,
3786 ) -> Result<()> {
3787 let commands: Vec<DrawCommand> = range.clone().map(|idx| self.draws[idx].clone()).collect();
3788 for cmd in &commands {
3789 if let Some(path) = cmd.mesh_texture_path.as_deref() {
3790 let _ = self.ensure_external_texture(path);
3791 }
3792 if let Some(path) = cmd.mesh_normal_texture_path.as_deref() {
3793 let _ = self.ensure_external_texture(path);
3794 }
3795 if let Some(path) = cmd.mesh_toon_texture_path.as_deref() {
3796 let _ = self.ensure_external_texture(path);
3797 }
3798 }
3799
3800 let mut keep_vs_uniform_bufs: Vec<wgpu::Buffer> = Vec::new();
3801 let mut keep_bone_uniform_bufs: Vec<wgpu::Buffer> = Vec::new();
3802 let mut keep_bind_groups: Vec<wgpu::BindGroup> = Vec::new();
3803 let config_width = self.config.width;
3804 let config_height = self.config.height;
3805 let viewport = match color_target {
3806 ColorTarget::External(_) | ColorTarget::Internal(InternalColorTarget::SceneA) | ColorTarget::Internal(InternalColorTarget::SceneB) => {
3807 self.surface_viewport
3808 }
3809 ColorTarget::Internal(InternalColorTarget::ShadowMap) => SurfaceViewport::full(config_width, config_height),
3810 };
3811 let overlay_backdrop = overlay_backdrop.map(|target| self.backdrop_target_ref(target));
3812 let color_view = self.color_target_view(color_target);
3813 let depth_view = self.depth_target_view(depth_target);
3814 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
3815 label: Some("siglus-sprite-pass"),
3816 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
3817 view: color_view,
3818 resolve_target: None,
3819 ops: wgpu::Operations {
3820 load: color_load,
3821 store: wgpu::StoreOp::Store,
3822 },
3823 })],
3824 depth_stencil_attachment: depth_view.map(|view| {
3825 wgpu::RenderPassDepthStencilAttachment {
3826 view,
3827 depth_ops: Some(wgpu::Operations {
3828 load: if clear_depth {
3829 wgpu::LoadOp::Clear(1.0)
3830 } else {
3831 wgpu::LoadOp::Load
3832 },
3833 store: wgpu::StoreOp::Store,
3834 }),
3835 stencil_ops: None,
3836 }
3837 }),
3838 timestamp_writes: None,
3839 occlusion_query_set: None,
3840 });
3841 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
3842 rp.set_vertex_buffer(0, self.vertex_buf.slice(..));
3843 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
3844 rp.set_vertex_buffer(0, self.vertex_buf.slice(..));
3845 rp.set_viewport(
3846 viewport.x as f32,
3847 viewport.y as f32,
3848 viewport.w as f32,
3849 viewport.h as f32,
3850 0.0,
3851 1.0,
3852 );
3853
3854 for cmd in commands {
3855 let semantics = self.resolve_effect_resources_for_draw(&cmd, overlay_backdrop);
3856 let vs_uniform_buf =
3857 self.device
3858 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
3859 label: Some("siglus-vs-uniform"),
3860 contents: bytemuck::bytes_of(&cmd.vs_uniform),
3861 usage: wgpu::BufferUsages::UNIFORM,
3862 });
3863 let bone_uniform_buf =
3864 self.device
3865 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
3866 label: Some("siglus-bone-uniform"),
3867 contents: bytemuck::bytes_of(&cmd.bone_uniform),
3868 usage: wgpu::BufferUsages::UNIFORM,
3869 });
3870
3871 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
3872 label: Some("siglus-sprite-bg"),
3873 layout: &self.bind_group_layout,
3874 entries: &[
3875 wgpu::BindGroupEntry {
3876 binding: 0,
3877 resource: wgpu::BindingResource::TextureView(&semantics.base.view),
3878 },
3879 wgpu::BindGroupEntry {
3880 binding: 1,
3881 resource: wgpu::BindingResource::Sampler(&semantics.base.sampler),
3882 },
3883 wgpu::BindGroupEntry {
3884 binding: 2,
3885 resource: wgpu::BindingResource::TextureView(&semantics.mask.view),
3886 },
3887 wgpu::BindGroupEntry {
3888 binding: 3,
3889 resource: wgpu::BindingResource::Sampler(&semantics.mask.sampler),
3890 },
3891 wgpu::BindGroupEntry {
3892 binding: 4,
3893 resource: wgpu::BindingResource::TextureView(&semantics.tone.view),
3894 },
3895 wgpu::BindGroupEntry {
3896 binding: 5,
3897 resource: wgpu::BindingResource::Sampler(&semantics.tone.sampler),
3898 },
3899 wgpu::BindGroupEntry {
3900 binding: 6,
3901 resource: wgpu::BindingResource::TextureView(semantics.aux_view),
3902 },
3903 wgpu::BindGroupEntry {
3904 binding: 7,
3905 resource: wgpu::BindingResource::Sampler(semantics.aux_sampler),
3906 },
3907 wgpu::BindGroupEntry {
3908 binding: 8,
3909 resource: wgpu::BindingResource::TextureView(&semantics.fog.view),
3910 },
3911 wgpu::BindGroupEntry {
3912 binding: 9,
3913 resource: wgpu::BindingResource::Sampler(&semantics.fog.sampler),
3914 },
3915 wgpu::BindGroupEntry {
3916 binding: 10,
3917 resource: wgpu::BindingResource::TextureView(semantics.shadow_view),
3918 },
3919 wgpu::BindGroupEntry {
3920 binding: 11,
3921 resource: wgpu::BindingResource::Sampler(semantics.shadow_sampler),
3922 },
3923 wgpu::BindGroupEntry {
3924 binding: 12,
3925 resource: vs_uniform_buf.as_entire_binding(),
3926 },
3927 wgpu::BindGroupEntry {
3928 binding: 13,
3929 resource: bone_uniform_buf.as_entire_binding(),
3930 },
3931 wgpu::BindGroupEntry {
3932 binding: 14,
3933 resource: wgpu::BindingResource::TextureView(&semantics.normal.view),
3934 },
3935 wgpu::BindGroupEntry {
3936 binding: 15,
3937 resource: wgpu::BindingResource::Sampler(&semantics.normal.sampler),
3938 },
3939 wgpu::BindGroupEntry {
3940 binding: 16,
3941 resource: wgpu::BindingResource::TextureView(&semantics.toon.view),
3942 },
3943 wgpu::BindGroupEntry {
3944 binding: 17,
3945 resource: wgpu::BindingResource::Sampler(&semantics.toon.sampler),
3946 },
3947 ],
3948 });
3949
3950 let mut effective_key = cmd.pipeline_key.clone();
3951 if let Some(special) = force_special {
3952 effective_key = shadow_pipeline_key(
3953 cmd.pipeline_key.clone(),
3954 cmd.shadow_pipeline_name.as_deref(),
3955 );
3956 effective_key.technique.special = special;
3957 }
3958 if let Some(pipeline) = self.pipelines.get(&effective_key) {
3959 rp.set_pipeline(pipeline);
3960 }
3961 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
3962 {
3963 if effective_key.program.uses_sprite2d_layout() {
3964 rp.set_vertex_buffer(0, self.vertex_sprite2d_buf.slice(..));
3965 } else {
3966 rp.set_vertex_buffer(0, self.vertex_buf.slice(..));
3967 }
3968 }
3969 keep_vs_uniform_bufs.push(vs_uniform_buf);
3970 keep_bone_uniform_bufs.push(bone_uniform_buf);
3971 keep_bind_groups.push(bind_group);
3972 let bind_group_ptr = keep_bind_groups.last().unwrap() as *const wgpu::BindGroup;
3973 unsafe {
3974 rp.set_bind_group(0, &*bind_group_ptr, &[]);
3975 }
3976 if let Some(sci) = cmd.scissor {
3977 rp.set_scissor_rect(sci.x, sci.y, sci.w, sci.h);
3978 } else {
3979 rp.set_scissor_rect(viewport.x, viewport.y, viewport.w, viewport.h);
3980 }
3981 rp.draw(cmd.range.clone(), 0..1);
3982 }
3983 Ok(())
3984 }
3985
3986 fn resolve_effect_resources_for_draw<'a>(
3987 &'a self,
3988 cmd: &'a DrawCommand,
3989 overlay_backdrop: Option<&'a RenderTargetTexture>,
3990 ) -> EffectResolvedResources<'a> {
3991 let base = if let Some(path) = cmd.mesh_texture_path.as_deref() {
3992 self.external_textures
3993 .get(path)
3994 .or_else(|| cmd.image_id.and_then(|id| self.textures.get(&id)))
3995 .unwrap_or(&self.default_aux)
3996 } else {
3997 cmd.image_id
3998 .and_then(|id| self.textures.get(&id))
3999 .unwrap_or(&self.default_aux)
4000 };
4001 let mask = cmd
4002 .mask_image_id
4003 .and_then(|id| self.textures.get(&id))
4004 .unwrap_or(&self.default_aux);
4005 let tone = cmd
4006 .tonecurve_image_id
4007 .and_then(|id| self.textures.get(&id))
4008 .unwrap_or(&self.default_aux);
4009 let fog = cmd
4010 .fog_image_id
4011 .and_then(|id| self.textures.get(&id))
4012 .unwrap_or(&self.default_aux);
4013 let normal = cmd
4014 .mesh_normal_texture_path
4015 .as_deref()
4016 .and_then(|p| self.external_textures.get(p))
4017 .unwrap_or(&self.default_aux);
4018 let toon = cmd
4019 .mesh_toon_texture_path
4020 .as_deref()
4021 .and_then(|p| self.external_textures.get(p))
4022 .unwrap_or(&self.default_aux);
4023 let (aux_view, aux_sampler) = if matches!(
4024 cmd.pipeline_key.technique.special,
4025 TechniqueSpecial::Overlay
4026 ) {
4027 if let Some(backdrop) = overlay_backdrop {
4028 (&backdrop.view, &backdrop.sampler)
4029 } else {
4030 (&self.default_aux.view, &self.default_aux.sampler)
4031 }
4032 } else if let Some(id) = cmd.wipe_src_image_id {
4033 if let Some(tex) = self.textures.get(&id) {
4034 (&tex.view, &tex.sampler)
4035 } else {
4036 (&self.default_aux.view, &self.default_aux.sampler)
4037 }
4038 } else {
4039 (&self.default_aux.view, &self.default_aux.sampler)
4040 };
4041 let global_vals = EffectGlobalValPackSemantic {
4042 use_bone_uniform: matches!(
4043 cmd.draw_kind,
4044 MeshDrawKind::SkinnedMesh | MeshDrawKind::ShadowCaster
4045 ) && cmd.mesh_material_key.as_ref().is_some_and(|k| k.skinned),
4046 use_shadow_tex: cmd.pipeline_key.use_depth
4047 || cmd.shadow_cast
4048 || cmd.mesh_material_key.as_ref().is_some_and(|k| k.shadow),
4049 use_normal_tex: cmd
4050 .mesh_material_key
4051 .as_ref()
4052 .is_some_and(|k| k.use_normal_tex),
4053 use_toon_tex: cmd
4054 .mesh_material_key
4055 .as_ref()
4056 .is_some_and(|k| k.use_toon_tex),
4057 };
4058 EffectResolvedResources {
4059 base,
4060 mask,
4061 tone,
4062 fog,
4063 normal,
4064 toon,
4065 aux_view,
4066 aux_sampler,
4067 shadow_view: &self.shadow_map.view,
4068 shadow_sampler: &self.shadow_map.sampler,
4069 global_vals,
4070 }
4071 }
4072
4073 fn render_copy_pass(
4074 &self,
4075 encoder: &mut wgpu::CommandEncoder,
4076 color_target: ColorTarget<'_>,
4077 src: BackdropTarget,
4078 blit_range: std::ops::Range<u32>,
4079 ) -> Result<()> {
4080 let color_view = self.color_target_view(color_target);
4081 let src = self.backdrop_target_ref(src);
4082 let key = PipelineKey {
4083 technique: TechniqueKey {
4084 d3: false,
4085 light: false,
4086 fog: false,
4087 tex: 1,
4088 diffuse: false,
4089 mrbd: false,
4090 rgb: false,
4091 tonecurve: false,
4092 mask: false,
4093 special: TechniqueSpecial::None,
4094 },
4095 blend: SpriteBlend::Normal,
4096 alpha_blend: false,
4097 use_depth: false,
4098 cull_back: false,
4099 mesh_fx_variant: 0,
4100 pipeline_name: String::new(),
4101 program: EffectProgram::Sprite2D,
4102 };
4103 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4104 let vs_uniform = plain_sprite2d_uniform(self.config.width as f32, self.config.height as f32);
4105 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
4106 let vs_uniform = VsUniform::for_2d(self.config.width as f32, self.config.height as f32);
4107 let vs_uniform_buf = self
4108 .device
4109 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4110 label: Some("siglus-copy-vs-uniform"),
4111 contents: bytemuck::bytes_of(&vs_uniform),
4112 usage: wgpu::BufferUsages::UNIFORM,
4113 });
4114 let bone_uniform = BoneUniform::zero();
4115 let bone_uniform_buf = self
4116 .device
4117 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
4118 label: Some("siglus-copy-bone-uniform"),
4119 contents: bytemuck::bytes_of(&bone_uniform),
4120 usage: wgpu::BufferUsages::UNIFORM,
4121 });
4122 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
4123 label: Some("siglus-copy-bg"),
4124 layout: &self.bind_group_layout,
4125 entries: &[
4126 wgpu::BindGroupEntry {
4127 binding: 0,
4128 resource: wgpu::BindingResource::TextureView(&src.view),
4129 },
4130 wgpu::BindGroupEntry {
4131 binding: 1,
4132 resource: wgpu::BindingResource::Sampler(&src.sampler),
4133 },
4134 wgpu::BindGroupEntry {
4135 binding: 2,
4136 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4137 },
4138 wgpu::BindGroupEntry {
4139 binding: 3,
4140 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4141 },
4142 wgpu::BindGroupEntry {
4143 binding: 4,
4144 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4145 },
4146 wgpu::BindGroupEntry {
4147 binding: 5,
4148 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4149 },
4150 wgpu::BindGroupEntry {
4151 binding: 6,
4152 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4153 },
4154 wgpu::BindGroupEntry {
4155 binding: 7,
4156 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4157 },
4158 wgpu::BindGroupEntry {
4159 binding: 8,
4160 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4161 },
4162 wgpu::BindGroupEntry {
4163 binding: 9,
4164 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4165 },
4166 wgpu::BindGroupEntry {
4167 binding: 10,
4168 resource: wgpu::BindingResource::TextureView(&self.shadow_map.view),
4169 },
4170 wgpu::BindGroupEntry {
4171 binding: 11,
4172 resource: wgpu::BindingResource::Sampler(&self.shadow_map.sampler),
4173 },
4174 wgpu::BindGroupEntry {
4175 binding: 12,
4176 resource: vs_uniform_buf.as_entire_binding(),
4177 },
4178 wgpu::BindGroupEntry {
4179 binding: 13,
4180 resource: bone_uniform_buf.as_entire_binding(),
4181 },
4182 wgpu::BindGroupEntry {
4183 binding: 14,
4184 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4185 },
4186 wgpu::BindGroupEntry {
4187 binding: 15,
4188 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4189 },
4190 wgpu::BindGroupEntry {
4191 binding: 16,
4192 resource: wgpu::BindingResource::TextureView(&self.default_aux.view),
4193 },
4194 wgpu::BindGroupEntry {
4195 binding: 17,
4196 resource: wgpu::BindingResource::Sampler(&self.default_aux.sampler),
4197 },
4198 ],
4199 });
4200 let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
4201 label: Some("siglus-copy-pass"),
4202 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
4203 view: color_view,
4204 resolve_target: None,
4205 ops: wgpu::Operations {
4206 load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
4207 store: wgpu::StoreOp::Store,
4208 },
4209 })],
4210 depth_stencil_attachment: None,
4211 timestamp_writes: None,
4212 occlusion_query_set: None,
4213 });
4214 if let Some(pipeline) = self.pipelines.get(&key) {
4215 rp.set_pipeline(pipeline);
4216 }
4217 let viewport = self.surface_viewport;
4218 rp.set_viewport(
4219 viewport.x as f32,
4220 viewport.y as f32,
4221 viewport.w as f32,
4222 viewport.h as f32,
4223 0.0,
4224 1.0,
4225 );
4226 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
4227 rp.set_vertex_buffer(0, self.vertex_buf.slice(..));
4228 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4229 rp.set_vertex_buffer(0, self.vertex_sprite2d_buf.slice(..));
4230 rp.set_bind_group(0, &bind_group, &[]);
4231 rp.set_scissor_rect(viewport.x, viewport.y, viewport.w, viewport.h);
4232 rp.draw(blit_range, 0..1);
4233 Ok(())
4234 }
4235
4236 fn ensure_vertex_capacity(&mut self, needed: usize) -> Result<()> {
4237 if needed <= self.vertex_capacity {
4238 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4239 {
4240 if needed > self.vertex_sprite2d_capacity {
4241 let new_cap = ((needed + 5) / 6) * 6;
4242 self.vertex_sprite2d_capacity = new_cap;
4243 self.vertex_sprite2d_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4244 label: Some("siglus-sprite2d-vertex-buf"),
4245 size: (new_cap * std::mem::size_of::<VertexSprite2dData>())
4246 as wgpu::BufferAddress,
4247 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
4248 mapped_at_creation: false,
4249 });
4250 }
4251 }
4252 return Ok(());
4253 }
4254 let new_cap = ((needed + 5) / 6) * 6;
4255 self.vertex_capacity = new_cap;
4256
4257 self.vertex_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4258 label: Some("siglus-sprite-vertex-buf"),
4259 size: (new_cap * std::mem::size_of::<Vertex>()) as wgpu::BufferAddress,
4260 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
4261 mapped_at_creation: false,
4262 });
4263 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4264 {
4265 self.vertex_sprite2d_capacity = new_cap;
4266 self.vertex_sprite2d_buf = self.device.create_buffer(&wgpu::BufferDescriptor {
4267 label: Some("siglus-sprite2d-vertex-buf"),
4268 size: (new_cap * std::mem::size_of::<VertexSprite2dData>())
4269 as wgpu::BufferAddress,
4270 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
4271 mapped_at_creation: false,
4272 });
4273 }
4274 Ok(())
4275 }
4276
4277 pub fn clear_runtime_image_textures(&mut self) {
4284 self.textures.clear();
4285 }
4286
4287 fn ensure_texture_uploaded(&mut self, images: &ImageManager, id: ImageId) -> Result<()> {
4288 let Some((img, version)) = images.get_entry(id) else {
4289 return Ok(());
4290 };
4291 if let Some(mut tex) = self.textures.remove(&id) {
4292 if tex.version != version {
4293 if tex.width == img.width && tex.height == img.height {
4294 self.update_texture(&mut tex, img)?;
4295 tex.version = version;
4296 } else {
4297 tex = create_gpu_texture(
4298 &self.device,
4299 &self.queue,
4300 &format!("siglus-texture-{}", id.index()),
4301 img,
4302 version,
4303 )?;
4304 }
4305 }
4306 self.textures.insert(id, tex);
4307 } else {
4308 let tex = create_gpu_texture(
4309 &self.device,
4310 &self.queue,
4311 &format!("siglus-texture-{}", id.index()),
4312 img,
4313 version,
4314 )?;
4315 self.textures.insert(id, tex);
4316 }
4317 Ok(())
4318 }
4319
4320 fn update_texture(&self, tex: &GpuTexture, img: &crate::assets::RgbaImage) -> Result<()> {
4321 if tex.width != img.width || tex.height != img.height {
4322 return Ok(());
4323 }
4324 self.queue.write_texture(
4325 wgpu::ImageCopyTexture {
4326 texture: &tex._tex,
4327 mip_level: 0,
4328 origin: wgpu::Origin3d::ZERO,
4329 aspect: wgpu::TextureAspect::All,
4330 },
4331 &img.rgba,
4332 wgpu::ImageDataLayout {
4333 offset: 0,
4334 bytes_per_row: Some(4 * img.width),
4335 rows_per_image: Some(img.height),
4336 },
4337 wgpu::Extent3d {
4338 width: img.width,
4339 height: img.height,
4340 depth_or_array_layers: 1,
4341 },
4342 );
4343 Ok(())
4344 }
4345}
4346fn create_solid_texture(
4347 device: &wgpu::Device,
4348 queue: &wgpu::Queue,
4349 rgba: [u8; 4],
4350) -> Result<GpuTexture> {
4351 let img = crate::assets::RgbaImage {
4352 width: 1,
4353 height: 1,
4354 center_x: 0,
4355 center_y: 0,
4356 rgba: rgba.to_vec(),
4357 };
4358 create_gpu_texture(device, queue, "siglus-default-aux", &img, 0)
4359}
4360
4361fn create_gpu_texture(
4362 device: &wgpu::Device,
4363 queue: &wgpu::Queue,
4364 label: &str,
4365 img: &crate::assets::RgbaImage,
4366 version: u64,
4367) -> Result<GpuTexture> {
4368 let tex = device.create_texture(&wgpu::TextureDescriptor {
4369 label: Some(label),
4370 size: wgpu::Extent3d {
4371 width: img.width,
4372 height: img.height,
4373 depth_or_array_layers: 1,
4374 },
4375 mip_level_count: 1,
4376 sample_count: 1,
4377 dimension: wgpu::TextureDimension::D2,
4378 format: wgpu::TextureFormat::Rgba8UnormSrgb,
4379 usage: wgpu::TextureUsages::TEXTURE_BINDING
4380 | wgpu::TextureUsages::COPY_DST
4381 | wgpu::TextureUsages::COPY_SRC,
4382 view_formats: &[],
4383 });
4384
4385 queue.write_texture(
4386 wgpu::ImageCopyTexture {
4387 texture: &tex,
4388 mip_level: 0,
4389 origin: wgpu::Origin3d::ZERO,
4390 aspect: wgpu::TextureAspect::All,
4391 },
4392 &img.rgba,
4393 wgpu::ImageDataLayout {
4394 offset: 0,
4395 bytes_per_row: Some(4 * img.width),
4396 rows_per_image: Some(img.height),
4397 },
4398 wgpu::Extent3d {
4399 width: img.width,
4400 height: img.height,
4401 depth_or_array_layers: 1,
4402 },
4403 );
4404
4405 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4406 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4407 label: Some("siglus-sampler"),
4408 address_mode_u: wgpu::AddressMode::ClampToEdge,
4409 address_mode_v: wgpu::AddressMode::ClampToEdge,
4410 address_mode_w: wgpu::AddressMode::ClampToEdge,
4411 mag_filter: wgpu::FilterMode::Linear,
4412 min_filter: wgpu::FilterMode::Linear,
4413 mipmap_filter: wgpu::FilterMode::Nearest,
4414 ..Default::default()
4415 });
4416
4417 Ok(GpuTexture {
4418 _tex: tex,
4419 view,
4420 sampler,
4421 width: img.width,
4422 height: img.height,
4423 version,
4424 })
4425}
4426
4427fn create_render_target_texture(
4428 device: &wgpu::Device,
4429 width: u32,
4430 height: u32,
4431 format: wgpu::TextureFormat,
4432 label: &str,
4433) -> RenderTargetTexture {
4434 let tex = device.create_texture(&wgpu::TextureDescriptor {
4435 label: Some(label),
4436 size: wgpu::Extent3d {
4437 width: width.max(1),
4438 height: height.max(1),
4439 depth_or_array_layers: 1,
4440 },
4441 mip_level_count: 1,
4442 sample_count: 1,
4443 dimension: wgpu::TextureDimension::D2,
4444 format,
4445 usage: wgpu::TextureUsages::TEXTURE_BINDING
4446 | wgpu::TextureUsages::RENDER_ATTACHMENT
4447 | wgpu::TextureUsages::COPY_SRC,
4448 view_formats: &[],
4449 });
4450 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4451 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
4452 label: Some("siglus-render-target-sampler"),
4453 address_mode_u: wgpu::AddressMode::ClampToEdge,
4454 address_mode_v: wgpu::AddressMode::ClampToEdge,
4455 address_mode_w: wgpu::AddressMode::ClampToEdge,
4456 mag_filter: wgpu::FilterMode::Linear,
4457 min_filter: wgpu::FilterMode::Linear,
4458 mipmap_filter: wgpu::FilterMode::Nearest,
4459 ..Default::default()
4460 });
4461 RenderTargetTexture {
4462 _tex: tex,
4463 view,
4464 sampler,
4465 width: width.max(1),
4466 height: height.max(1),
4467 format,
4468 }
4469}
4470
4471fn append_fullscreen_blit_vertices(verts: &mut Vec<Vertex>) -> std::ops::Range<u32> {
4472 let base = verts.len() as u32;
4473 let effects1 = [1.0, 0.0, 0.0, 0.0];
4474 let zero = [0.0; 4];
4475 verts.extend_from_slice(&[
4476 Vertex {
4477 pos: [-1.0, 1.0, 0.0],
4478 uv: [0.0, 0.0],
4479 uv_aux: [0.0, 0.0],
4480 alpha: 1.0,
4481 effects1,
4482 effects2: zero,
4483 effects3: zero,
4484 effects4: zero,
4485 effects5: zero,
4486 effects6: zero,
4487 effects7: zero,
4488 effects8: zero,
4489 effects9: zero,
4490 effects10: zero,
4491 effects11: zero,
4492 world_pos: zero,
4493 world_normal: zero,
4494 world_tangent: zero,
4495 world_binormal: zero,
4496 shadow_pos: zero,
4497 bone_indices: zero,
4498 bone_weights: zero,
4499 light_pos_kind: zero,
4500 light_dir_shadow: zero,
4501 light_atten: zero,
4502 light_cone: zero,
4503 },
4504 Vertex {
4505 pos: [1.0, 1.0, 0.0],
4506 uv: [1.0, 0.0],
4507 uv_aux: [0.0, 0.0],
4508 alpha: 1.0,
4509 effects1,
4510 effects2: zero,
4511 effects3: zero,
4512 effects4: zero,
4513 effects5: zero,
4514 effects6: zero,
4515 effects7: zero,
4516 effects8: zero,
4517 effects9: zero,
4518 effects10: zero,
4519 effects11: zero,
4520 world_pos: zero,
4521 world_normal: zero,
4522 world_tangent: zero,
4523 world_binormal: zero,
4524 shadow_pos: zero,
4525 bone_indices: zero,
4526 bone_weights: zero,
4527 light_pos_kind: zero,
4528 light_dir_shadow: zero,
4529 light_atten: zero,
4530 light_cone: zero,
4531 },
4532 Vertex {
4533 pos: [1.0, -1.0, 0.0],
4534 uv: [1.0, 1.0],
4535 uv_aux: [0.0, 0.0],
4536 alpha: 1.0,
4537 effects1,
4538 effects2: zero,
4539 effects3: zero,
4540 effects4: zero,
4541 effects5: zero,
4542 effects6: zero,
4543 effects7: zero,
4544 effects8: zero,
4545 effects9: zero,
4546 effects10: zero,
4547 effects11: zero,
4548 world_pos: zero,
4549 world_normal: zero,
4550 world_tangent: zero,
4551 world_binormal: zero,
4552 shadow_pos: zero,
4553 bone_indices: zero,
4554 bone_weights: zero,
4555 light_pos_kind: zero,
4556 light_dir_shadow: zero,
4557 light_atten: zero,
4558 light_cone: zero,
4559 },
4560 Vertex {
4561 pos: [-1.0, 1.0, 0.0],
4562 uv: [0.0, 0.0],
4563 uv_aux: [0.0, 0.0],
4564 alpha: 1.0,
4565 effects1,
4566 effects2: zero,
4567 effects3: zero,
4568 effects4: zero,
4569 effects5: zero,
4570 effects6: zero,
4571 effects7: zero,
4572 effects8: zero,
4573 effects9: zero,
4574 effects10: zero,
4575 effects11: zero,
4576 world_pos: zero,
4577 world_normal: zero,
4578 world_tangent: zero,
4579 world_binormal: zero,
4580 shadow_pos: zero,
4581 bone_indices: zero,
4582 bone_weights: zero,
4583 light_pos_kind: zero,
4584 light_dir_shadow: zero,
4585 light_atten: zero,
4586 light_cone: zero,
4587 },
4588 Vertex {
4589 pos: [1.0, -1.0, 0.0],
4590 uv: [1.0, 1.0],
4591 uv_aux: [0.0, 0.0],
4592 alpha: 1.0,
4593 effects1,
4594 effects2: zero,
4595 effects3: zero,
4596 effects4: zero,
4597 effects5: zero,
4598 effects6: zero,
4599 effects7: zero,
4600 effects8: zero,
4601 effects9: zero,
4602 effects10: zero,
4603 effects11: zero,
4604 world_pos: zero,
4605 world_normal: zero,
4606 world_tangent: zero,
4607 world_binormal: zero,
4608 shadow_pos: zero,
4609 bone_indices: zero,
4610 bone_weights: zero,
4611 light_pos_kind: zero,
4612 light_dir_shadow: zero,
4613 light_atten: zero,
4614 light_cone: zero,
4615 },
4616 Vertex {
4617 pos: [-1.0, -1.0, 0.0],
4618 uv: [0.0, 1.0],
4619 uv_aux: [0.0, 0.0],
4620 alpha: 1.0,
4621 effects1,
4622 effects2: zero,
4623 effects3: zero,
4624 effects4: zero,
4625 effects5: zero,
4626 effects6: zero,
4627 effects7: zero,
4628 effects8: zero,
4629 effects9: zero,
4630 effects10: zero,
4631 effects11: zero,
4632 world_pos: zero,
4633 world_normal: zero,
4634 world_tangent: zero,
4635 world_binormal: zero,
4636 shadow_pos: zero,
4637 bone_indices: zero,
4638 bone_weights: zero,
4639 light_pos_kind: zero,
4640 light_dir_shadow: zero,
4641 light_atten: zero,
4642 light_cone: zero,
4643 },
4644 ]);
4645 base..base + 6
4646}
4647
4648fn pixel_to_ndc(x: f32, y: f32, depth: f32, win_w: f32, win_h: f32) -> (f32, f32, f32) {
4649 let nx = (x / win_w) * 2.0 - 1.0;
4650 let ny = 1.0 - (y / win_h) * 2.0;
4651 let nz = depth.clamp(0.0, 1.0);
4656 (nx, ny, nz)
4657}
4658
4659fn create_depth_texture(device: &wgpu::Device, width: u32, height: u32) -> DepthTexture {
4660 let tex = device.create_texture(&wgpu::TextureDescriptor {
4661 label: Some("siglus-depth"),
4662 size: wgpu::Extent3d {
4663 width: width.max(1),
4664 height: height.max(1),
4665 depth_or_array_layers: 1,
4666 },
4667 mip_level_count: 1,
4668 sample_count: 1,
4669 dimension: wgpu::TextureDimension::D2,
4670 format: wgpu::TextureFormat::Depth32Float,
4671 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
4672 view_formats: &[],
4673 });
4674 let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
4675 DepthTexture { _tex: tex, view }
4676}
4677
4678fn src_clip_rect(clip: Option<ClipRect>, img_w: u32, img_h: u32) -> Result<(f32, f32, f32, f32)> {
4679 if let Some(c) = clip {
4680 let mut left = c.left.max(0) as f32;
4681 let mut top = c.top.max(0) as f32;
4682 let mut right = c.right.max(0) as f32;
4683 let mut bottom = c.bottom.max(0) as f32;
4684 let max_w = img_w as f32;
4685 let max_h = img_h as f32;
4686 left = left.min(max_w);
4687 right = right.min(max_w);
4688 top = top.min(max_h);
4689 bottom = bottom.min(max_h);
4690 if right <= left || bottom <= top {
4691 return Ok((0.0, 0.0, max_w, max_h));
4692 }
4693 Ok((left, top, right, bottom))
4694 } else {
4695 Ok((0.0, 0.0, img_w as f32, img_h as f32))
4696 }
4697}
4698
4699fn dst_scissor_rect_to_viewport(
4700 clip: Option<ClipRect>,
4701 viewport: SurfaceViewport,
4702 logical_w: f32,
4703 logical_h: f32,
4704 surface_w: u32,
4705 surface_h: u32,
4706) -> Option<ScissorRect> {
4707 let c = clip?;
4708 let sx = (viewport.w as f32) / logical_w.max(1.0);
4709 let sy = (viewport.h as f32) / logical_h.max(1.0);
4710 let mut left = viewport.x as i64 + ((c.left.max(0) as f32) * sx).floor() as i64;
4711 let mut top = viewport.y as i64 + ((c.top.max(0) as f32) * sy).floor() as i64;
4712 let mut right = viewport.x as i64 + ((c.right.max(0) as f32) * sx).ceil() as i64;
4713 let mut bottom = viewport.y as i64 + ((c.bottom.max(0) as f32) * sy).ceil() as i64;
4714 let max_w = surface_w as i64;
4715 let max_h = surface_h as i64;
4716 left = left.min(max_w);
4717 right = right.min(max_w);
4718 top = top.min(max_h);
4719 bottom = bottom.min(max_h);
4720 if right <= left || bottom <= top {
4721 return Some(ScissorRect {
4722 x: 0,
4723 y: 0,
4724 w: 0,
4725 h: 0,
4726 });
4727 }
4728 Some(ScissorRect {
4729 x: left as u32,
4730 y: top as u32,
4731 w: (right - left) as u32,
4732 h: (bottom - top) as u32,
4733 })
4734}
4735
4736
4737#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
4738fn wasm_shader_source() -> String {
4739 let mut shader = SHADER.to_string();
4740 shader = shader.replace(
4741 r#"struct VsOut2d {
4742 @builtin(position) pos: vec4<f32>,
4743 @location(0) uv: vec2<f32>,
4744 @location(1) uv_aux: vec2<f32>,
4745 @location(2) alpha: f32,
4746 @location(3) effects1: vec4<f32>,
4747 @location(4) effects2: vec4<f32>,
4748 @location(5) effects3: vec4<f32>,
4749 @location(6) effects4: vec4<f32>,
4750 @location(7) effects5: vec4<f32>,
4751 @location(8) effects6: vec4<f32>,
4752 @location(9) effects7: vec4<f32>,
4753 @location(10) effects8: vec4<f32>,
4754 @location(11) effects9: vec4<f32>,
4755 @location(12) effects10: vec4<f32>,
4756 @location(13) effects11: vec4<f32>,
4757};"#,
4758 r#"struct VsOut2d {
4759 @builtin(position) pos: vec4<f32>,
4760 @location(0) uv: vec2<f32>,
4761 @location(1) uv_aux: vec2<f32>,
4762 @location(2) alpha: f32,
4763};"#,
4764 );
4765 shader = shader.replace(
4766 r#"fn vs_common_2d(v: VsIn2d) -> VsOut2d {
4767 var o: VsOut2d;
4768 o.pos = vec4<f32>(v.pos, 1.0);
4769 o.uv = v.uv;
4770 o.uv_aux = v.uv_aux;
4771 o.alpha = v.alpha;
4772 o.effects1 = v.effects1;
4773 o.effects2 = v.effects2;
4774 o.effects3 = v.effects3;
4775 o.effects4 = v.effects4;
4776 o.effects5 = v.effects5;
4777 o.effects6 = v.effects6;
4778 o.effects7 = v.effects7;
4779 o.effects8 = v.effects8;
4780 o.effects9 = v.effects9;
4781 o.effects10 = v.effects10;
4782 o.effects11 = v.effects11;
4783 return o;
4784}"#,
4785 r#"fn vs_common_2d(v: VsIn2d) -> VsOut2d {
4786 var o: VsOut2d;
4787 o.pos = vec4<f32>(v.pos, 1.0);
4788 o.uv = v.uv;
4789 o.uv_aux = v.uv_aux;
4790 o.alpha = v.alpha;
4791 return o;
4792}"#,
4793 );
4794 shader = shader.replace(
4795 r#"fn fs_common_2d(i: VsOut2d) -> vec4<f32> {
4796 let has_mask = i.effects5.x;
4797 let has_tonecurve = i.effects5.y;
4798 let tonecurve_row = i.effects5.z;
4799 let tonecurve_sat = i.effects5.w;
4800 let tr = i.effects1.x;
4801 let mono = i.effects1.y;
4802 let rev = i.effects1.z;
4803 let bright = i.effects1.w;
4804 let dark = i.effects2.x;
4805 let color_rate = i.effects2.y;
4806 let color_add = vec3<f32>(i.effects2.z, i.effects2.w, i.effects3.x);
4807 let color_tgt = vec3<f32>(i.effects3.y, i.effects3.z, i.effects3.w);
4808 let mask_mode = i.effects4.x;
4809 let alpha_test = i.effects4.y;
4810 let light_on = i.effects4.z;
4811 let fog_on = i.effects4.w;
4812 let wipe_mode = i.effects6.x;
4813 let wipe_p0 = i.effects6.y;
4814 let wipe_p1 = i.effects6.z;
4815 let wipe_p2 = i.effects6.w;
4816 let wipe_p3 = i.effects7.x;
4817 let has_wipe_src = i.effects7.y;
4818 let blend_code = i.effects7.z;
4819 let wipe_aux1 = i.effects7.w;
4820 let light_factor = i.effects8.w;
4821 let alpha_ref = max(vs_u.mtrl_extra.y, 0.001);
4822 let fog_scroll_x = i.effects9.w;
4823 let fog_color = i.effects10.xyz;
4824 let sprite_z = i.effects10.w;
4825 let fog_near = i.effects11.x;
4826 let fog_far = i.effects11.y;
4827 let has_fog_tex = i.effects11.z;
4828 let camera_z = i.effects11.w;"#,
4829 r#"fn fs_common_2d(i: VsOut2d) -> vec4<f32> {
4830 let effects1 = vs_u.mesh_mrbd;
4831 let effects2 = vs_u.mesh_rgb_rate;
4832 let effects3 = vs_u.mesh_add_rgb;
4833 let effects4 = vs_u.mesh_flags;
4834 let effects5 = vs_u.mtrl_params;
4835 let effects6 = vs_u.mtrl_rim;
4836 let effects7 = vs_u.mtrl_diffuse;
4837 let effects8 = vs_u.mtrl_ambient;
4838 let effects9 = vs_u.mtrl_specular;
4839 let effects10 = vs_u.dir_light_diffuse[0];
4840 let effects11 = vs_u.dir_light_ambient[0];
4841 let has_mask = effects5.x;
4842 let has_tonecurve = effects5.y;
4843 let tonecurve_row = effects5.z;
4844 let tonecurve_sat = effects5.w;
4845 let tr = effects1.x;
4846 let mono = effects1.y;
4847 let rev = effects1.z;
4848 let bright = effects1.w;
4849 let dark = effects2.x;
4850 let color_rate = effects2.y;
4851 let color_add = vec3<f32>(effects2.z, effects2.w, effects3.x);
4852 let color_tgt = vec3<f32>(effects3.y, effects3.z, effects3.w);
4853 let mask_mode = effects4.x;
4854 let alpha_test = effects4.y;
4855 let light_on = effects4.z;
4856 let fog_on = effects4.w;
4857 let wipe_mode = effects6.x;
4858 let wipe_p0 = effects6.y;
4859 let wipe_p1 = effects6.z;
4860 let wipe_p2 = effects6.w;
4861 let wipe_p3 = effects7.x;
4862 let has_wipe_src = effects7.y;
4863 let blend_code = effects7.z;
4864 let wipe_aux1 = effects7.w;
4865 let light_factor = effects8.w;
4866 let alpha_ref = max(vs_u.mtrl_extra.y, 0.001);
4867 let fog_scroll_x = effects9.w;
4868 let fog_color = effects10.xyz;
4869 let sprite_z = effects10.w;
4870 let fog_near = effects11.x;
4871 let fog_far = effects11.y;
4872 let has_fog_tex = effects11.z;
4873 let camera_z = effects11.w;"#,
4874 );
4875 shader
4876}
4877
4878const SHADER: &str = r#"
4879struct VsIn {
4880 @location(0) pos: vec3<f32>,
4881 @location(1) uv: vec2<f32>,
4882 @location(2) uv_aux: vec2<f32>,
4883 @location(3) alpha: f32,
4884 @location(4) effects1: vec4<f32>,
4885 @location(5) effects2: vec4<f32>,
4886 @location(6) effects3: vec4<f32>,
4887 @location(7) effects4: vec4<f32>,
4888 @location(8) effects5: vec4<f32>,
4889 @location(9) effects6: vec4<f32>,
4890 @location(10) effects7: vec4<f32>,
4891 @location(11) effects8: vec4<f32>,
4892 @location(12) effects9: vec4<f32>,
4893 @location(13) effects10: vec4<f32>,
4894 @location(14) effects11: vec4<f32>,
4895 @location(15) world_pos: vec4<f32>,
4896 @location(16) world_normal: vec4<f32>,
4897 @location(17) world_tangent: vec4<f32>,
4898 @location(18) world_binormal: vec4<f32>,
4899 @location(19) shadow_pos: vec4<f32>,
4900 @location(20) bone_indices: vec4<f32>,
4901 @location(21) bone_weights: vec4<f32>,
4902 @location(22) light_pos_kind: vec4<f32>,
4903 @location(23) light_dir_shadow: vec4<f32>,
4904 @location(24) light_atten: vec4<f32>,
4905 @location(25) light_cone: vec4<f32>,
4906};
4907
4908struct VsIn2d {
4909 @location(0) pos: vec3<f32>,
4910 @location(1) uv: vec2<f32>,
4911 @location(2) uv_aux: vec2<f32>,
4912 @location(3) alpha: f32,
4913 @location(4) effects1: vec4<f32>,
4914 @location(5) effects2: vec4<f32>,
4915 @location(6) effects3: vec4<f32>,
4916 @location(7) effects4: vec4<f32>,
4917 @location(8) effects5: vec4<f32>,
4918 @location(9) effects6: vec4<f32>,
4919 @location(10) effects7: vec4<f32>,
4920 @location(11) effects8: vec4<f32>,
4921 @location(12) effects9: vec4<f32>,
4922 @location(13) effects10: vec4<f32>,
4923 @location(14) effects11: vec4<f32>,
4924};
4925
4926struct VsOut {
4927 @builtin(position) pos: vec4<f32>,
4928 @location(0) uv: vec2<f32>,
4929 @location(1) uv_aux: vec2<f32>,
4930 @location(2) alpha: f32,
4931 @location(3) effects1: vec4<f32>,
4932 @location(4) effects2: vec4<f32>,
4933 @location(5) effects3: vec4<f32>,
4934 @location(6) effects4: vec4<f32>,
4935 @location(7) effects5: vec4<f32>,
4936 @location(8) effects6: vec4<f32>,
4937 @location(9) effects7: vec4<f32>,
4938 @location(10) effects8: vec4<f32>,
4939 @location(11) effects9: vec4<f32>,
4940 @location(12) effects10: vec4<f32>,
4941 @location(13) effects11: vec4<f32>,
4942 @location(14) world_pos: vec4<f32>,
4943 @location(15) world_normal: vec4<f32>,
4944 @location(16) world_tangent: vec4<f32>,
4945 @location(17) world_binormal: vec4<f32>,
4946 @location(18) shadow_pos: vec4<f32>,
4947 @location(19) light_pos_kind: vec4<f32>,
4948 @location(20) light_dir_shadow: vec4<f32>,
4949 @location(21) light_atten: vec4<f32>,
4950 @location(22) light_cone: vec4<f32>,
4951};
4952
4953struct VsOut2d {
4954 @builtin(position) pos: vec4<f32>,
4955 @location(0) uv: vec2<f32>,
4956 @location(1) uv_aux: vec2<f32>,
4957 @location(2) alpha: f32,
4958 @location(3) effects1: vec4<f32>,
4959 @location(4) effects2: vec4<f32>,
4960 @location(5) effects3: vec4<f32>,
4961 @location(6) effects4: vec4<f32>,
4962 @location(7) effects5: vec4<f32>,
4963 @location(8) effects6: vec4<f32>,
4964 @location(9) effects7: vec4<f32>,
4965 @location(10) effects8: vec4<f32>,
4966 @location(11) effects9: vec4<f32>,
4967 @location(12) effects10: vec4<f32>,
4968 @location(13) effects11: vec4<f32>,
4969};
4970
4971struct ShadowVsOut {
4972 @builtin(position) pos: vec4<f32>,
4973 @location(0) depth: f32,
4974 @location(1) uv: vec2<f32>,
4975 @location(2) alpha_test: f32,
4976};
4977
4978struct VsUniform {
4979 model_col0: vec4<f32>,
4980 model_col1: vec4<f32>,
4981 model_col2: vec4<f32>,
4982 model_col3: vec4<f32>,
4983 normal_col0: vec4<f32>,
4984 normal_col1: vec4<f32>,
4985 normal_col2: vec4<f32>,
4986 frame_col0: vec4<f32>,
4987 frame_col1: vec4<f32>,
4988 frame_col2: vec4<f32>,
4989 frame_col3: vec4<f32>,
4990 frame_normal0: vec4<f32>,
4991 frame_normal1: vec4<f32>,
4992 frame_normal2: vec4<f32>,
4993 camera_eye: vec4<f32>,
4994 camera_forward: vec4<f32>,
4995 camera_right: vec4<f32>,
4996 camera_up: vec4<f32>,
4997 camera_params: vec4<f32>,
4998 shadow_eye: vec4<f32>,
4999 shadow_forward: vec4<f32>,
5000 shadow_right: vec4<f32>,
5001 shadow_up: vec4<f32>,
5002 shadow_params: vec4<f32>,
5003 mtrl_diffuse: vec4<f32>,
5004 mtrl_ambient: vec4<f32>,
5005 mtrl_specular: vec4<f32>,
5006 mtrl_emissive: vec4<f32>,
5007 mtrl_params: vec4<f32>,
5008 mtrl_rim: vec4<f32>,
5009 mtrl_extra: vec4<f32>,
5010 light_diffuse_u: vec4<f32>,
5011 light_ambient_u: vec4<f32>,
5012 light_specular_u: vec4<f32>,
5013 mesh_flags: vec4<f32>,
5014 mesh_mrbd: vec4<f32>,
5015 mesh_rgb_rate: vec4<f32>,
5016 mesh_add_rgb: vec4<f32>,
5017 mesh_misc: vec4<f32>,
5018 mesh_light_counts: vec4<f32>,
5019 dir_light_diffuse: array<vec4<f32>, 4>,
5020 dir_light_ambient: array<vec4<f32>, 4>,
5021 dir_light_specular: array<vec4<f32>, 4>,
5022 dir_light_dir: array<vec4<f32>, 4>,
5023 point_light_diffuse: array<vec4<f32>, 4>,
5024 point_light_ambient: array<vec4<f32>, 4>,
5025 point_light_specular: array<vec4<f32>, 4>,
5026 point_light_pos: array<vec4<f32>, 4>,
5027 point_light_atten: array<vec4<f32>, 4>,
5028 spot_light_diffuse: array<vec4<f32>, 4>,
5029 spot_light_ambient: array<vec4<f32>, 4>,
5030 spot_light_specular: array<vec4<f32>, 4>,
5031 spot_light_pos: array<vec4<f32>, 4>,
5032 spot_light_dir: array<vec4<f32>, 4>,
5033 spot_light_atten: array<vec4<f32>, 4>,
5034 spot_light_cone: array<vec4<f32>, 4>,
5035 flags: vec4<f32>,
5036};
5037
5038struct BoneUniform {
5039 matrices: array<mat4x4<f32>, 64>,
5040};
5041
5042@group(0) @binding(10) var shadow_tex: texture_2d<f32>;
5043@group(0) @binding(11) var shadow_smp: sampler;
5044@group(0) @binding(12) var<uniform> vs_u: VsUniform;
5045@group(0) @binding(13) var<uniform> bone_u: BoneUniform;
5046
5047fn apply_model(local: vec3<f32>) -> vec3<f32> {
5048 return vs_u.model_col0.xyz * local.x + vs_u.model_col1.xyz * local.y + vs_u.model_col2.xyz * local.z + vs_u.model_col3.xyz;
5049}
5050
5051fn apply_normal(local: vec3<f32>) -> vec3<f32> {
5052 let n = vs_u.normal_col0.xyz * local.x + vs_u.normal_col1.xyz * local.y + vs_u.normal_col2.xyz * local.z;
5053 if (length(n) <= 1e-6) {
5054 return vec3<f32>(0.0, 0.0, 1.0);
5055 }
5056 return normalize(n);
5057}
5058
5059fn apply_frame(local: vec3<f32>) -> vec3<f32> {
5060 return vs_u.frame_col0.xyz * local.x + vs_u.frame_col1.xyz * local.y + vs_u.frame_col2.xyz * local.z + vs_u.frame_col3.xyz;
5061}
5062
5063fn apply_frame_normal(local: vec3<f32>) -> vec3<f32> {
5064 let n = vs_u.frame_normal0.xyz * local.x + vs_u.frame_normal1.xyz * local.y + vs_u.frame_normal2.xyz * local.z;
5065 if (length(n) <= 1e-6) {
5066 return vec3<f32>(0.0, 0.0, 1.0);
5067 }
5068 return normalize(n);
5069}
5070
5071fn apply_bone_point(m: mat4x4<f32>, local: vec3<f32>) -> vec3<f32> {
5072 return m[0].xyz * local.x + m[1].xyz * local.y + m[2].xyz * local.z + m[3].xyz;
5073}
5074
5075fn skin_local(local: vec3<f32>, bone_indices: vec4<f32>, bone_weights: vec4<f32>) -> vec3<f32> {
5076 let sum_w = bone_weights.x + bone_weights.y + bone_weights.z + bone_weights.w;
5077 if (vs_u.flags.w <= 0.5 || sum_w <= 1e-6) {
5078 return apply_frame(local);
5079 }
5080 var out = vec3<f32>(0.0, 0.0, 0.0);
5081 if (bone_weights.x > 0.0) {
5082 let m = bone_u.matrices[min(u32(max(bone_indices.x, 0.0)), 63u)];
5083 out = out + apply_bone_point(m, local) * bone_weights.x;
5084 }
5085 if (bone_weights.y > 0.0) {
5086 let m = bone_u.matrices[min(u32(max(bone_indices.y, 0.0)), 63u)];
5087 out = out + apply_bone_point(m, local) * bone_weights.y;
5088 }
5089 if (bone_weights.z > 0.0) {
5090 let m = bone_u.matrices[min(u32(max(bone_indices.z, 0.0)), 63u)];
5091 out = out + apply_bone_point(m, local) * bone_weights.z;
5092 }
5093 if (bone_weights.w > 0.0) {
5094 let m = bone_u.matrices[min(u32(max(bone_indices.w, 0.0)), 63u)];
5095 out = out + apply_bone_point(m, local) * bone_weights.w;
5096 }
5097 return out;
5098}
5099
5100fn skin_normal(local: vec3<f32>, bone_indices: vec4<f32>, bone_weights: vec4<f32>) -> vec3<f32> {
5101 let sum_w = bone_weights.x + bone_weights.y + bone_weights.z + bone_weights.w;
5102 if (vs_u.flags.w <= 0.5 || sum_w <= 1e-6) {
5103 return apply_frame_normal(local);
5104 }
5105 var out = vec3<f32>(0.0, 0.0, 0.0);
5106 if (bone_weights.x > 0.0) {
5107 let m = bone_u.matrices[min(u32(max(bone_indices.x, 0.0)), 63u)];
5108 out = out + (m[0].xyz * local.x + m[1].xyz * local.y + m[2].xyz * local.z) * bone_weights.x;
5109 }
5110 if (bone_weights.y > 0.0) {
5111 let m = bone_u.matrices[min(u32(max(bone_indices.y, 0.0)), 63u)];
5112 out = out + (m[0].xyz * local.x + m[1].xyz * local.y + m[2].xyz * local.z) * bone_weights.y;
5113 }
5114 if (bone_weights.z > 0.0) {
5115 let m = bone_u.matrices[min(u32(max(bone_indices.z, 0.0)), 63u)];
5116 out = out + (m[0].xyz * local.x + m[1].xyz * local.y + m[2].xyz * local.z) * bone_weights.z;
5117 }
5118 if (bone_weights.w > 0.0) {
5119 let m = bone_u.matrices[min(u32(max(bone_indices.w, 0.0)), 63u)];
5120 out = out + (m[0].xyz * local.x + m[1].xyz * local.y + m[2].xyz * local.z) * bone_weights.w;
5121 }
5122 if (length(out) <= 1e-6) {
5123 return vec3<f32>(0.0, 0.0, 1.0);
5124 }
5125 return normalize(out);
5126}
5127
5128fn project_main(world: vec3<f32>) -> vec4<f32> {
5129 if (vs_u.flags.y > 0.5) {
5130 let rel = world - vs_u.camera_eye.xyz;
5131 let cx = dot(rel, vs_u.camera_right.xyz);
5132 let cy = dot(rel, vs_u.camera_up.xyz);
5133 let cz = dot(rel, vs_u.camera_forward.xyz);
5134 if (cz <= 1e-3) {
5135 return vec4<f32>(2.0, 2.0, 2.0, 1.0);
5136 }
5137 let x_ndc = cx / (cz * max(vs_u.camera_params.x, 1e-3));
5138 let y_ndc = cy / (cz * max(vs_u.camera_params.y, 1e-3));
5139 let z_ndc = clamp((cz - 1.0) / 10000.0, 0.0, 1.0);
5140 return vec4<f32>(x_ndc, y_ndc, z_ndc, 1.0);
5141 }
5142 let x_ndc = (world.x / max(vs_u.camera_params.z, 1.0)) * 2.0 - 1.0;
5143 let y_ndc = 1.0 - (world.y / max(vs_u.camera_params.w, 1.0)) * 2.0;
5144 let z_ndc = clamp(-world.z / 50000.0, 0.0, 1.0);
5145 return vec4<f32>(x_ndc, y_ndc, z_ndc, 1.0);
5146}
5147
5148fn project_shadow(world: vec3<f32>) -> vec4<f32> {
5149 if (vs_u.shadow_params.z <= 0.5) {
5150 return vec4<f32>(0.0, 0.0, 1.0, 1.0);
5151 }
5152 let rel = world - vs_u.shadow_eye.xyz;
5153 let cx = dot(rel, vs_u.shadow_right.xyz);
5154 let cy = dot(rel, vs_u.shadow_up.xyz);
5155 let cz = dot(rel, vs_u.shadow_forward.xyz);
5156 if (cz <= 1e-3) {
5157 return vec4<f32>(0.0, 0.0, 1.0, 1.0);
5158 }
5159 let x_ndc = cx / (cz * max(vs_u.shadow_params.x, 1e-3));
5160 let y_ndc = cy / (cz * max(vs_u.shadow_params.x, 1e-3));
5161 let depth = clamp(cz / max(vs_u.shadow_params.y, 1.0), 0.0, 1.0);
5162 return vec4<f32>(x_ndc, y_ndc, depth, 1.0);
5163}
5164
5165fn vs_common(v: VsIn) -> VsOut {
5166 var o: VsOut;
5167 if (vs_u.flags.x > 0.5) {
5168 let local_world = skin_local(v.pos, v.bone_indices, v.bone_weights);
5169 let local_normal = skin_normal(v.world_normal.xyz, v.bone_indices, v.bone_weights);
5170 let local_tangent = skin_normal(v.world_tangent.xyz, v.bone_indices, v.bone_weights);
5171 let local_binormal = skin_normal(v.world_binormal.xyz, v.bone_indices, v.bone_weights);
5172 let world = apply_model(local_world);
5173 let normal = apply_normal(local_normal);
5174 let tangent = apply_normal(local_tangent);
5175 let binormal = apply_normal(local_binormal);
5176 let shadow = project_shadow(world);
5177 o.pos = project_main(world);
5178 o.world_pos = vec4<f32>(world, 1.0);
5179 o.world_normal = vec4<f32>(normal, 0.0);
5180 o.world_tangent = vec4<f32>(tangent, 0.0);
5181 o.world_binormal = vec4<f32>(binormal, 0.0);
5182 o.shadow_pos = shadow;
5183 } else {
5184 o.pos = vec4<f32>(v.pos, 1.0);
5185 o.world_pos = v.world_pos;
5186 o.world_normal = v.world_normal;
5187 o.world_tangent = v.world_tangent;
5188 o.world_binormal = v.world_binormal;
5189 o.shadow_pos = v.shadow_pos;
5190 }
5191 o.uv = v.uv;
5192 o.uv_aux = v.uv_aux;
5193 o.alpha = v.alpha;
5194 o.effects1 = v.effects1;
5195 o.effects2 = v.effects2;
5196 o.effects3 = v.effects3;
5197 o.effects4 = v.effects4;
5198 o.effects5 = v.effects5;
5199 o.effects6 = v.effects6;
5200 o.effects7 = v.effects7;
5201 o.effects8 = v.effects8;
5202 o.effects9 = v.effects9;
5203 o.effects10 = v.effects10;
5204 o.effects11 = v.effects11;
5205 o.light_pos_kind = v.light_pos_kind;
5206 o.light_dir_shadow = v.light_dir_shadow;
5207 o.light_atten = v.light_atten;
5208 o.light_cone = v.light_cone;
5209 return o;
5210}
5211
5212fn vs_shadow_common(v: VsIn) -> ShadowVsOut {
5213 var o: ShadowVsOut;
5214 if (vs_u.flags.x > 0.5) {
5215 let local_world = skin_local(v.pos, v.bone_indices, v.bone_weights);
5216 let world = apply_model(local_world);
5217 let shadow = project_shadow(world);
5218 o.pos = vec4<f32>(shadow.xyz, 1.0);
5219 o.depth = clamp(shadow.z, 0.0, 1.0);
5220 } else {
5221 o.pos = vec4<f32>(v.shadow_pos.xyz, 1.0);
5222 o.depth = clamp(v.shadow_pos.z, 0.0, 1.0);
5223 }
5224 o.uv = v.uv;
5225 o.alpha_test = v.effects4.y;
5226 return o;
5227}
5228
5229fn vs_common_2d(v: VsIn2d) -> VsOut2d {
5230 var o: VsOut2d;
5231 o.pos = vec4<f32>(v.pos, 1.0);
5232 o.uv = v.uv;
5233 o.uv_aux = v.uv_aux;
5234 o.alpha = v.alpha;
5235 o.effects1 = v.effects1;
5236 o.effects2 = v.effects2;
5237 o.effects3 = v.effects3;
5238 o.effects4 = v.effects4;
5239 o.effects5 = v.effects5;
5240 o.effects6 = v.effects6;
5241 o.effects7 = v.effects7;
5242 o.effects8 = v.effects8;
5243 o.effects9 = v.effects9;
5244 o.effects10 = v.effects10;
5245 o.effects11 = v.effects11;
5246 return o;
5247}
5248
5249@group(0) @binding(0) var tex0: texture_2d<f32>;
5250@group(0) @binding(1) var smp0: sampler;
5251@group(0) @binding(2) var tex1: texture_2d<f32>;
5252@group(0) @binding(3) var smp1: sampler;
5253@group(0) @binding(4) var tex2: texture_2d<f32>;
5254@group(0) @binding(5) var smp2: sampler;
5255@group(0) @binding(6) var tex3: texture_2d<f32>;
5256@group(0) @binding(7) var smp3: sampler;
5257@group(0) @binding(8) var tex4: texture_2d<f32>;
5258@group(0) @binding(9) var smp4: sampler;
5259@group(0) @binding(14) var tex5: texture_2d<f32>;
5260@group(0) @binding(15) var smp5: sampler;
5261@group(0) @binding(16) var tex6: texture_2d<f32>;
5262@group(0) @binding(17) var smp6: sampler;
5263fn sample_mask(uv: vec2<f32>) -> vec4<f32> {
5264 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5265 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
5266 }
5267 return textureSample(tex1, smp1, uv);
5268}
5269
5270fn apply_tonecurve(color_in: vec3<f32>, row: f32, sat: f32) -> vec3<f32> {
5271 var color = color_in;
5272 let gray = dot(color, vec3<f32>(0.2989, 0.5886, 0.1145));
5273 color = mix(color, vec3<f32>(gray, gray, gray), clamp(sat, 0.0, 1.0));
5274 let y = clamp(row, 0.0, 1.0);
5275 let r = textureSample(tex2, smp2, vec2<f32>(clamp(color.r, 0.0, 1.0), y)).r;
5276 let g = textureSample(tex2, smp2, vec2<f32>(clamp(color.g, 0.0, 1.0), y)).g;
5277 let b = textureSample(tex2, smp2, vec2<f32>(clamp(color.b, 0.0, 1.0), y)).b;
5278 return vec3<f32>(r, g, b);
5279}
5280
5281fn sample_tex0_safe(uv: vec2<f32>) -> vec4<f32> {
5282 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5283 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
5284 }
5285 return textureSample(tex0, smp0, uv);
5286}
5287
5288fn sample_tex3_safe(uv: vec2<f32>) -> vec4<f32> {
5289 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5290 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
5291 }
5292 return textureSample(tex3, smp3, uv);
5293}
5294
5295fn sample_tex4_safe(uv: vec2<f32>) -> vec4<f32> {
5296 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5297 return vec4<f32>(0.0, 0.0, 0.0, 0.0);
5298 }
5299 return textureSample(tex4, smp4, uv);
5300}
5301
5302fn sample_mosaic_tex3(uv: vec2<f32>, cut_u: f32, tex_rate_for_square: f32) -> vec4<f32> {
5303 let cu = max(cut_u, 1e-5);
5304 let cv = max(cut_u * max(tex_rate_for_square, 1e-5), 1e-5);
5305 let tc = vec2<f32>(floor(uv.x / cu) * cu, floor(uv.y / cv) * cv);
5306 return sample_tex3_safe(tc);
5307}
5308
5309fn raster_amp(progress: f32) -> f32 {
5310 let rp = clamp(1.0 - progress, 1e-4, 1.0);
5311 let lv = max((1.0 - rp) * 100.0, 1e-4);
5312 return 1.0 - ((log(lv) / log(10.0)) + 1.0) / 3.0;
5313}
5314
5315fn sample_raster_h_tex3(uv: vec2<f32>, fraction_num: f32, wave_num: f32, power: f32, progress: f32) -> vec4<f32> {
5316 let fnn = max(fraction_num, 1.0);
5317 var tex_coord_for_sin = uv.y * fnn;
5318 tex_coord_for_sin = fract(tex_coord_for_sin);
5319 tex_coord_for_sin = (tex_coord_for_sin - fnn * 0.1) / fnn;
5320 let dx = sin(3.14159265 * progress * power + tex_coord_for_sin * 3.14159265 * wave_num) * raster_amp(progress);
5321 return sample_tex3_safe(vec2<f32>(uv.x + dx, uv.y));
5322}
5323
5324fn sample_raster_v_tex3(uv: vec2<f32>, fraction_num: f32, wave_num: f32, power: f32, progress: f32) -> vec4<f32> {
5325 let fnn = max(fraction_num, 1.0);
5326 var tex_coord_for_sin = uv.x * fnn;
5327 tex_coord_for_sin = fract(tex_coord_for_sin);
5328 tex_coord_for_sin = (tex_coord_for_sin - fnn * 0.1) / fnn;
5329 let dy = sin(3.14159265 * progress * power + tex_coord_for_sin * 3.14159265 * wave_num) * raster_amp(progress);
5330 return sample_tex3_safe(vec2<f32>(uv.x, uv.y + dy));
5331}
5332
5333fn sample_explosion_blur_tex3(uv: vec2<f32>, center_uv: vec2<f32>, blur_power: f32, blur_coeff: f32) -> vec4<f32> {
5334 let dims_u = textureDimensions(tex3, 0);
5335 let dims = vec2<f32>(f32(dims_u.x), f32(dims_u.y));
5336 let texel = 1.0 / max(max(dims.x, dims.y), 1.0);
5337 var dir = center_uv - uv;
5338 let len = length(dir);
5339 if (len <= 1e-5 || blur_power <= 1e-5) {
5340 return sample_tex3_safe(uv);
5341 }
5342 dir = normalize(dir) * texel * blur_power * len * max(blur_coeff, 0.0);
5343 return
5344 sample_tex3_safe(uv) * 0.19 +
5345 sample_tex3_safe(uv + dir * 1.0) * 0.17 +
5346 sample_tex3_safe(uv + dir * 2.0) * 0.15 +
5347 sample_tex3_safe(uv + dir * 3.0) * 0.13 +
5348 sample_tex3_safe(uv + dir * 4.0) * 0.11 +
5349 sample_tex3_safe(uv + dir * 5.0) * 0.09 +
5350 sample_tex3_safe(uv + dir * 6.0) * 0.07 +
5351 sample_tex3_safe(uv + dir * 7.0) * 0.05 +
5352 sample_tex3_safe(uv + dir * 8.0) * 0.03 +
5353 sample_tex3_safe(uv + dir * 9.0) * 0.01;
5354}
5355
5356fn sample_mosaic(uv: vec2<f32>, cut_u: f32, tex_rate_for_square: f32) -> vec4<f32> {
5357 let cu = max(cut_u, 1e-5);
5358 let cv = max(cut_u * max(tex_rate_for_square, 1e-5), 1e-5);
5359 let tc = vec2<f32>(floor(uv.x / cu) * cu, floor(uv.y / cv) * cv);
5360 return sample_tex0_safe(tc);
5361}
5362
5363fn sample_raster_h(uv: vec2<f32>, fraction_num: f32, wave_num: f32, power: f32, progress: f32) -> vec4<f32> {
5364 let fnn = max(fraction_num, 1.0);
5365 var tex_coord_for_sin = uv.y * fnn;
5366 tex_coord_for_sin = fract(tex_coord_for_sin);
5367 tex_coord_for_sin = (tex_coord_for_sin - fnn * 0.1) / fnn;
5368 let dx = sin(3.14159265 * progress * power + tex_coord_for_sin * 3.14159265 * wave_num) * raster_amp(progress);
5369 return sample_tex0_safe(vec2<f32>(uv.x + dx, uv.y));
5370}
5371
5372fn sample_raster_v(uv: vec2<f32>, fraction_num: f32, wave_num: f32, power: f32, progress: f32) -> vec4<f32> {
5373 let fnn = max(fraction_num, 1.0);
5374 var tex_coord_for_sin = uv.x * fnn;
5375 tex_coord_for_sin = fract(tex_coord_for_sin);
5376 tex_coord_for_sin = (tex_coord_for_sin - fnn * 0.1) / fnn;
5377 let dy = sin(3.14159265 * progress * power + tex_coord_for_sin * 3.14159265 * wave_num) * raster_amp(progress);
5378 return sample_tex0_safe(vec2<f32>(uv.x, uv.y + dy));
5379}
5380
5381fn sample_explosion_blur(uv: vec2<f32>, center_uv: vec2<f32>, blur_power: f32, blur_coeff: f32) -> vec4<f32> {
5382 let dims_u = textureDimensions(tex0, 0);
5383 let dims = vec2<f32>(f32(dims_u.x), f32(dims_u.y));
5384 let texel = 1.0 / max(max(dims.x, dims.y), 1.0);
5385 var dir = center_uv - uv;
5386 let len = length(dir);
5387 if (len <= 1e-5 || blur_power <= 1e-5) {
5388 return sample_tex0_safe(uv);
5389 }
5390 dir = normalize(dir) * texel * blur_power * len * max(blur_coeff, 0.0);
5391 return
5392 sample_tex0_safe(uv) * 0.19 +
5393 sample_tex0_safe(uv + dir * 1.0) * 0.17 +
5394 sample_tex0_safe(uv + dir * 2.0) * 0.15 +
5395 sample_tex0_safe(uv + dir * 3.0) * 0.13 +
5396 sample_tex0_safe(uv + dir * 4.0) * 0.11 +
5397 sample_tex0_safe(uv + dir * 5.0) * 0.09 +
5398 sample_tex0_safe(uv + dir * 6.0) * 0.07 +
5399 sample_tex0_safe(uv + dir * 7.0) * 0.05 +
5400 sample_tex0_safe(uv + dir * 8.0) * 0.03 +
5401 sample_tex0_safe(uv + dir * 9.0) * 0.01;
5402}
5403
5404fn rgb_brightness(color: vec4<f32>) -> f32 {
5405 return dot(vec3<f32>(0.299, 0.587, 0.114), color.rgb);
5406}
5407
5408fn sample_shimi(uv: vec2<f32>, fade: f32, progress: f32) -> vec4<f32> {
5409 var color = sample_tex0_safe(uv);
5410 if (rgb_brightness(color) > progress) {
5411 color.a = color.a * max(fade - mix(fade, 0.0, progress), 0.0);
5412 }
5413 return color;
5414}
5415
5416fn sample_shimi_inv(uv: vec2<f32>, fade: f32, progress: f32) -> vec4<f32> {
5417 var color = sample_tex0_safe(uv);
5418 if (rgb_brightness(color) < 1.0 - progress) {
5419 color.a = color.a * max(fade - mix(fade, 0.0, progress), 0.0);
5420 }
5421 return color;
5422}
5423
5424fn overlay_channel(dst: f32, src: f32) -> f32 {
5425 if (dst <= 0.5) {
5426 return 2.0 * dst * src;
5427 }
5428 return 1.0 - 2.0 * (1.0 - dst) * (1.0 - src);
5429}
5430
5431fn overlay_rgb(dst: vec3<f32>, src: vec3<f32>) -> vec3<f32> {
5432 return vec3<f32>(
5433 overlay_channel(dst.r, src.r),
5434 overlay_channel(dst.g, src.g),
5435 overlay_channel(dst.b, src.b)
5436 );
5437}
5438
5439fn sample_normal_tex(uv: vec2<f32>) -> vec3<f32> {
5440 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5441 return vec3<f32>(0.5, 0.5, 1.0);
5442 }
5443 let dims_u = textureDimensions(tex5, 0);
5444 if (dims_u.x <= 1u && dims_u.y <= 1u) {
5445 return vec3<f32>(0.5, 0.5, 1.0);
5446 }
5447 return textureSample(tex5, smp5, uv).xyz;
5448}
5449
5450fn sample_toon_tex(value: f32) -> vec3<f32> {
5451 let u = clamp(value, 0.0, 1.0);
5452 let dims_u = textureDimensions(tex6, 0);
5453 if (dims_u.x <= 1u && dims_u.y <= 1u) {
5454 let q = floor(u * 4.0) / 3.0;
5455 return vec3<f32>(q, q, q);
5456 }
5457 return textureSample(tex6, smp6, vec2<f32>(u, 0.5)).rgb;
5458}
5459
5460fn apply_parallax_uv(base_n: vec3<f32>, base_t: vec3<f32>, base_b: vec3<f32>, uv: vec2<f32>, view_dir_world: vec3<f32>, max_height: f32) -> vec2<f32> {
5461 let dims_u = textureDimensions(tex5, 0);
5462 if (dims_u.x <= 1u && dims_u.y <= 1u || max_height <= 1e-6) {
5463 return uv;
5464 }
5465 let N = normalize(base_n);
5466 var T = normalize(base_t);
5467 var B = normalize(base_b);
5468 if (length(T) <= 1e-5 || length(B) <= 1e-5) {
5469 let up = select(vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.0, 1.0, 0.0), abs(N.z) > 0.9);
5470 T = normalize(cross(up, N));
5471 B = normalize(cross(N, T));
5472 }
5473 let Vt = vec3<f32>(dot(view_dir_world, T), dot(view_dir_world, B), dot(view_dir_world, N));
5474 let height = textureSample(tex5, smp5, uv).a;
5475 let denom = select(-1e-4, Vt.z, abs(Vt.z) > 1e-4);
5476 let shift = (height - 0.5) * max_height;
5477 return uv + (Vt.xy / denom) * shift;
5478}
5479
5480fn apply_normal_map(base_n: vec3<f32>, base_t: vec3<f32>, base_b: vec3<f32>, uv: vec2<f32>) -> vec3<f32> {
5481 let tex_n = sample_normal_tex(uv) * 2.0 - vec3<f32>(1.0, 1.0, 1.0);
5482 let N = normalize(base_n);
5483 var T = normalize(base_t);
5484 var B = normalize(base_b);
5485 if (length(T) <= 1e-5 || length(B) <= 1e-5) {
5486 let up = select(vec3<f32>(0.0, 0.0, 1.0), vec3<f32>(0.0, 1.0, 0.0), abs(N.z) > 0.9);
5487 T = normalize(cross(up, N));
5488 B = normalize(cross(N, T));
5489 }
5490 let mapped = normalize(T * tex_n.x + B * tex_n.y + N * tex_n.z);
5491 return mapped;
5492}
5493
5494fn sample_shadow_visibility(shadow_pos: vec4<f32>) -> f32 {
5495 let ndc = shadow_pos.xyz / max(abs(shadow_pos.w), 1e-5);
5496 let uv = vec2<f32>(ndc.x * 0.5 + 0.5, 1.0 - (ndc.y * 0.5 + 0.5));
5497 if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) {
5498 return 1.0;
5499 }
5500 let dims_u = textureDimensions(shadow_tex, 0);
5501 let texel = vec2<f32>(1.0 / max(f32(dims_u.x), 1.0), 1.0 / max(f32(dims_u.y), 1.0));
5502 let current = clamp(ndc.z, 0.0, 1.0);
5503 let bias = max(vs_u.mesh_misc.y, 0.0005);
5504 var vis = 0.0;
5505 for (var oy: i32 = -1; oy <= 1; oy = oy + 1) {
5506 for (var ox: i32 = -1; ox <= 1; ox = ox + 1) {
5507 let sample_uv = uv + vec2<f32>(f32(ox), f32(oy)) * texel;
5508 let stored = textureSample(shadow_tex, shadow_smp, sample_uv).r;
5509 vis = vis + select(0.35, 1.0, current <= stored + bias);
5510 }
5511 }
5512 return vis / 9.0;
5513}
5514
5515fn mesh_light_contrib(
5516 base_rgb: vec3<f32>,
5517 world_pos: vec3<f32>,
5518 N: vec3<f32>,
5519 shaded_uv: vec2<f32>,
5520 light_diffuse: vec3<f32>,
5521 light_ambient: vec3<f32>,
5522 light_specular: vec3<f32>,
5523 kind: i32,
5524 light_pos: vec3<f32>,
5525 light_dir: vec3<f32>,
5526 light_atten: vec4<f32>,
5527 light_cone: vec4<f32>,
5528 shadow_pos: vec4<f32>,
5529 shadow_enabled: bool
5530) -> vec3<f32> {
5531 let lighting_type = i32(round(vs_u.mtrl_params.y));
5532 let shading_type = i32(round(vs_u.mtrl_params.z));
5533 let mtrl_ambient = vs_u.mtrl_ambient.rgb;
5534 let mtrl_specular = vs_u.mtrl_specular.rgb;
5535 let mtrl_power = max(vs_u.mtrl_params.x, 1.0);
5536 var L = vec3<f32>(0.0, 0.0, 1.0);
5537 var attenuation = 1.0;
5538 if (kind == 0) {
5539 L = normalize(-light_dir);
5540 } else {
5541 let dir_point = light_pos - world_pos;
5542 let distance_point = max(length(dir_point), 1e-5);
5543 L = dir_point / distance_point;
5544 attenuation = 1.0 / max(light_atten.x + light_atten.y * distance_point + light_atten.z * distance_point * distance_point, 1e-5);
5545 attenuation = attenuation * clamp(1.0 - distance_point / max(light_atten.w, 1.0), 0.0, 1.0);
5546 if (kind >= 2) {
5547 let rho = dot(normalize(dir_point), normalize(-light_dir));
5548 if (rho <= light_cone.y) {
5549 attenuation = 0.0;
5550 } else if (rho < light_cone.x) {
5551 attenuation = attenuation * pow((rho - light_cone.y) / max(light_cone.x - light_cone.y, 1e-5), max(light_cone.z, 0.01));
5552 }
5553 }
5554 }
5555 let V = normalize(vs_u.camera_eye.xyz - world_pos);
5556 let H = normalize(L + V);
5557 let ndotl_raw = dot(N, L);
5558 let ndotl = max(ndotl_raw, 0.0);
5559 let half_lambert = clamp(ndotl_raw * 0.5 + 0.5, 0.0, 1.0);
5560 let ndoth = max(dot(N, H), 0.0);
5561 let rdotv = max(dot(reflect(-L, N), V), 0.0);
5562 var visibility = 1.0;
5563 if (shadow_enabled && (shading_type == 1 || kind == 3)) {
5564 visibility = sample_shadow_visibility(shadow_pos);
5565 }
5566 let ambient_term = base_rgb * mtrl_ambient * light_ambient;
5567 var diffuse_strength = ndotl;
5568 if (lighting_type == 4) { diffuse_strength = half_lambert; }
5569 var diffuse_color = light_diffuse;
5570 if (lighting_type == 5) { diffuse_color = diffuse_color * sample_toon_tex(diffuse_strength); }
5571 var specular_strength = pow(ndoth, mtrl_power);
5572 if (lighting_type == 6 || lighting_type == 7) { specular_strength = pow(rdotv, mtrl_power); }
5573 if (lighting_type == 1 || lighting_type == 4 || lighting_type == 5 || lighting_type == 0) { specular_strength = 0.0; }
5574 let diffuse_term = base_rgb * diffuse_color * diffuse_strength * attenuation;
5575 let specular_term = mtrl_specular * light_specular * specular_strength * attenuation;
5576 return ambient_term + (diffuse_term + specular_term) * visibility;
5577}
5578
5579fn mesh_lighting(
5580 base_rgb: vec3<f32>,
5581 world_pos: vec3<f32>,
5582 world_normal: vec3<f32>,
5583 world_tangent: vec3<f32>,
5584 world_binormal: vec3<f32>,
5585 shaded_uv: vec2<f32>,
5586 light_pos_kind: vec4<f32>,
5587 light_dir_shadow: vec4<f32>,
5588 light_atten: vec4<f32>,
5589 light_cone: vec4<f32>,
5590 shadow_pos: vec4<f32>
5591) -> vec3<f32> {
5592 let lighting_type = i32(round(vs_u.mtrl_params.y));
5593 let rim_power = max(vs_u.mtrl_params.w, 0.0);
5594 let mtrl_emissive = vs_u.mtrl_emissive.rgb;
5595 var N = normalize(world_normal);
5596 if (lighting_type == 8 || lighting_type == 9) {
5597 N = apply_normal_map(N, world_tangent, world_binormal, shaded_uv);
5598 }
5599 var accum = mtrl_emissive;
5600 let dir_count = i32(round(vs_u.mesh_light_counts.x));
5601 let point_count = i32(round(vs_u.mesh_light_counts.y));
5602 let spot_count = i32(round(vs_u.mesh_light_counts.z));
5603 if (dir_count + point_count + spot_count > 0) {
5604 for (var i: i32 = 0; i < 4; i = i + 1) {
5605 if (i < dir_count) {
5606 accum = accum + mesh_light_contrib(base_rgb, world_pos, N, shaded_uv, vs_u.dir_light_diffuse[i].rgb, vs_u.dir_light_ambient[i].rgb, vs_u.dir_light_specular[i].rgb, 0, vec3<f32>(0.0), vs_u.dir_light_dir[i].xyz, vec4<f32>(1.0, 0.0, 0.0, 0.0), vec4<f32>(0.0), shadow_pos, false);
5607 }
5608 if (i < point_count) {
5609 accum = accum + mesh_light_contrib(base_rgb, world_pos, N, shaded_uv, vs_u.point_light_diffuse[i].rgb, vs_u.point_light_ambient[i].rgb, vs_u.point_light_specular[i].rgb, 1, vs_u.point_light_pos[i].xyz, vec3<f32>(0.0, 0.0, -1.0), vs_u.point_light_atten[i], vec4<f32>(0.0), shadow_pos, false);
5610 }
5611 if (i < spot_count) {
5612 accum = accum + mesh_light_contrib(base_rgb, world_pos, N, shaded_uv, vs_u.spot_light_diffuse[i].rgb, vs_u.spot_light_ambient[i].rgb, vs_u.spot_light_specular[i].rgb, 2 + i32(vs_u.spot_light_cone[i].w > 0.5), vs_u.spot_light_pos[i].xyz, vs_u.spot_light_dir[i].xyz, vs_u.spot_light_atten[i], vs_u.spot_light_cone[i], shadow_pos, vs_u.spot_light_cone[i].w > 0.5);
5613 }
5614 }
5615 } else {
5616 let kind = i32(round(light_pos_kind.w));
5617 accum = accum + mesh_light_contrib(base_rgb, world_pos, N, shaded_uv, vs_u.light_diffuse_u.rgb, vs_u.light_ambient_u.rgb, vs_u.light_specular_u.rgb, kind, light_pos_kind.xyz, light_dir_shadow.xyz, light_atten, light_cone, shadow_pos, kind == 3 && light_dir_shadow.w > 0.5);
5618 }
5619 var rim_term = vec3<f32>(0.0, 0.0, 0.0);
5620 let shader_option_bits = i32(round(vs_u.mtrl_extra.z));
5621 if (rim_power > 0.0 && (shader_option_bits & 1) != 0) {
5622 let V = normalize(vs_u.camera_eye.xyz - world_pos);
5623 let rim = pow(clamp(1.0 - max(dot(N, V), 0.0), 0.0, 1.0), max(rim_power, 1e-3));
5624 rim_term = vs_u.mtrl_rim.rgb * rim;
5625 }
5626 return clamp(accum + rim_term, vec3<f32>(0.0), vec3<f32>(8.0));
5627}
5628
5629fn fs_common_2d(i: VsOut2d) -> vec4<f32> {
5630 let has_mask = i.effects5.x;
5631 let has_tonecurve = i.effects5.y;
5632 let tonecurve_row = i.effects5.z;
5633 let tonecurve_sat = i.effects5.w;
5634 let tr = i.effects1.x;
5635 let mono = i.effects1.y;
5636 let rev = i.effects1.z;
5637 let bright = i.effects1.w;
5638 let dark = i.effects2.x;
5639 let color_rate = i.effects2.y;
5640 let color_add = vec3<f32>(i.effects2.z, i.effects2.w, i.effects3.x);
5641 let color_tgt = vec3<f32>(i.effects3.y, i.effects3.z, i.effects3.w);
5642 let mask_mode = i.effects4.x;
5643 let alpha_test = i.effects4.y;
5644 let light_on = i.effects4.z;
5645 let fog_on = i.effects4.w;
5646 let wipe_mode = i.effects6.x;
5647 let wipe_p0 = i.effects6.y;
5648 let wipe_p1 = i.effects6.z;
5649 let wipe_p2 = i.effects6.w;
5650 let wipe_p3 = i.effects7.x;
5651 let has_wipe_src = i.effects7.y;
5652 let blend_code = i.effects7.z;
5653 let wipe_aux1 = i.effects7.w;
5654 let light_factor = i.effects8.w;
5655 let alpha_ref = max(vs_u.mtrl_extra.y, 0.001);
5656 let fog_scroll_x = i.effects9.w;
5657 let fog_color = i.effects10.xyz;
5658 let sprite_z = i.effects10.w;
5659 let fog_near = i.effects11.x;
5660 let fog_far = i.effects11.y;
5661 let has_fog_tex = i.effects11.z;
5662 let camera_z = i.effects11.w;
5663
5664 var c = textureSample(tex0, smp0, i.uv);
5665 if (wipe_mode > 0.5 && wipe_mode < 1.5) {
5666 c = sample_mosaic(i.uv, wipe_p0, wipe_p1);
5667 } else if (wipe_mode > 1.5 && wipe_mode < 2.5) {
5668 c = sample_raster_h(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5669 } else if (wipe_mode > 2.5 && wipe_mode < 3.5) {
5670 c = sample_raster_v(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5671 } else if (wipe_mode > 3.5 && wipe_mode < 4.5) {
5672 c = sample_explosion_blur(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5673 } else if (wipe_mode > 4.5 && wipe_mode < 5.5) {
5674 c = sample_shimi(i.uv, wipe_p0, wipe_p1);
5675 } else if (wipe_mode > 5.5 && wipe_mode < 6.5) {
5676 c = sample_shimi_inv(i.uv, wipe_p0, wipe_p1);
5677 } else if (wipe_mode > 9.5 && wipe_mode < 10.5 && has_wipe_src > 0.5) {
5678 let oldc = sample_mosaic_tex3(i.uv, wipe_p0, wipe_p1);
5679 let newc = sample_mosaic(i.uv, wipe_p0, wipe_p1);
5680 if (wipe_p3 < 230.5) {
5681 c = select(oldc, newc, wipe_p2 >= 0.5);
5682 } else {
5683 c = mix(select(newc, oldc, wipe_aux1 < 0.5), select(oldc, newc, wipe_aux1 < 0.5), clamp(wipe_p2, 0.0, 1.0));
5684 }
5685 } else if (wipe_mode > 10.5 && wipe_mode < 11.5 && has_wipe_src > 0.5) {
5686 let oldc = sample_raster_h_tex3(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5687 let newc = sample_raster_h(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5688 c = mix(oldc, newc, clamp(wipe_p3, 0.0, 1.0));
5689 } else if (wipe_mode > 11.5 && wipe_mode < 12.5 && has_wipe_src > 0.5) {
5690 let oldc = sample_raster_v_tex3(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5691 let newc = sample_raster_v(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5692 c = mix(oldc, newc, clamp(wipe_p3, 0.0, 1.0));
5693 } else if (wipe_mode > 12.5 && wipe_mode < 13.5 && has_wipe_src > 0.5) {
5694 let oldc = sample_explosion_blur_tex3(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5695 let newc = sample_explosion_blur(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5696 c = mix(oldc, newc, clamp(tonecurve_row, 0.0, 1.0));
5697 }
5698
5699 var rgb = c.rgb;
5700 var alpha = c.a;
5701
5702 if (has_tonecurve > 0.5) {
5703 rgb = apply_tonecurve(rgb, tonecurve_row, tonecurve_sat);
5704 }
5705
5706 if (light_on > 0.5) {
5707 let lit = clamp(vs_u.light_ambient_u.rgb + vs_u.light_diffuse_u.rgb * light_factor, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(2.0, 2.0, 2.0));
5708 rgb = clamp(rgb * lit + vs_u.mtrl_emissive.rgb, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(8.0, 8.0, 8.0));
5709 }
5710
5711 rgb = mix(rgb, vec3<f32>(1.0, 1.0, 1.0) - rgb, rev);
5712 let mono_gray = dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
5713 rgb = mix(rgb, vec3<f32>(mono_gray, mono_gray, mono_gray), mono);
5714 rgb = clamp(rgb + vec3<f32>(bright, bright, bright), vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5715 rgb = clamp(rgb - vec3<f32>(dark, dark, dark), vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5716 rgb = mix(rgb, color_tgt, color_rate);
5717 rgb = clamp(rgb + color_add, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5718 let final_gray = dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
5719
5720 if (has_mask > 0.5) {
5721 let m = sample_mask(i.uv_aux);
5722 let mask_luma = dot(m.rgb, vec3<f32>(0.299, 0.587, 0.114));
5723 alpha = alpha * mask_luma * m.a;
5724 }
5725
5726 if (fog_on > 0.5) {
5727 let depth = abs(sprite_z - camera_z);
5728 let fog_t = clamp((depth - fog_near) / max(fog_far - fog_near, 1.0), 0.0, 1.0);
5729 if (fog_t > 0.0) {
5730 var fog_rgb = fog_color;
5731 if (has_fog_tex > 0.5) {
5732 let dims_u = textureDimensions(tex4, 0);
5733 let fw = max(f32(dims_u.x), 1.0);
5734 let fh = max(f32(dims_u.y), 1.0);
5735 let fog_uv = vec2<f32>(fract((i.pos.x + fog_scroll_x) / fw), fract(i.pos.y / fh));
5736 let fog_sample = sample_tex4_safe(fog_uv);
5737 fog_rgb = mix(fog_rgb, fog_sample.rgb, fog_sample.a);
5738 }
5739 rgb = mix(rgb, fog_rgb, fog_t);
5740 }
5741 }
5742
5743 if (mask_mode > 0.5 && mask_mode < 1.5) {
5744 alpha = final_gray;
5745 }
5746
5747 if (alpha_test > 0.5 && alpha <= alpha_ref) {
5748 discard;
5749 }
5750
5751 let a = alpha * i.alpha * tr;
5752 if (blend_code > 2.5 && blend_code < 3.5) {
5753 let mul_rgb = mix(vec3<f32>(1.0, 1.0, 1.0), rgb, a);
5754 return vec4<f32>(mul_rgb, a);
5755 }
5756 if (blend_code > 3.5 && blend_code < 4.5) {
5757 let screen_rgb = mix(vec3<f32>(0.0, 0.0, 0.0), rgb, a);
5758 return vec4<f32>(screen_rgb, a);
5759 }
5760 if (blend_code > 4.5 && blend_code < 5.5) {
5761 let dims_u = textureDimensions(tex3, 0);
5762 let screen_uv = vec2<f32>(
5763 clamp(i.pos.x / max(f32(dims_u.x), 1.0), 0.0, 1.0),
5764 clamp(i.pos.y / max(f32(dims_u.y), 1.0), 0.0, 1.0)
5765 );
5766 let dst = sample_tex3_safe(screen_uv);
5767 let ov = overlay_rgb(dst.rgb, rgb);
5768 let out_rgb = mix(dst.rgb, ov, a);
5769 return vec4<f32>(out_rgb, 1.0);
5770 }
5771 return vec4<f32>(rgb, a);
5772}
5773
5774fn fs_common(i: VsOut) -> vec4<f32> {
5775 let has_mask = i.effects5.x;
5776 let has_tonecurve = i.effects5.y;
5777 let tonecurve_row = i.effects5.z;
5778 let tonecurve_sat = i.effects5.w;
5779 let tr = i.effects1.x;
5780 let mono = i.effects1.y;
5781 let rev = i.effects1.z;
5782 let bright = i.effects1.w;
5783 let dark = i.effects2.x;
5784 let color_rate = i.effects2.y;
5785 let color_add = vec3<f32>(i.effects2.z, i.effects2.w, i.effects3.x);
5786 let color_tgt = vec3<f32>(i.effects3.y, i.effects3.z, i.effects3.w);
5787 let mask_mode = i.effects4.x;
5788 let alpha_test = i.effects4.y;
5789 let light_on = i.effects4.z;
5790 let fog_on = i.effects4.w;
5791 let wipe_mode = i.effects6.x;
5792 let wipe_p0 = i.effects6.y;
5793 let wipe_p1 = i.effects6.z;
5794 let wipe_p2 = i.effects6.w;
5795 let wipe_p3 = i.effects7.x;
5796 let has_wipe_src = i.effects7.y;
5797 let blend_code = i.effects7.z;
5798 let wipe_aux1 = i.effects7.w;
5799 let light_factor = i.effects8.w;
5800 let world_pos = i.world_pos.xyz;
5801 let alpha_ref = max(vs_u.mtrl_extra.y, 0.001);
5802 let world_has_pos = i.world_pos.w > 0.5;
5803 let world_normal = i.world_normal.xyz;
5804 let world_tangent = i.world_tangent.xyz;
5805 let world_binormal = i.world_binormal.xyz;
5806 let light_pos_kind = i.light_pos_kind;
5807 let light_dir_shadow = i.light_dir_shadow;
5808 let light_atten = i.light_atten;
5809 let light_cone = i.light_cone;
5810 let fog_scroll_x = i.effects9.w;
5811 let fog_color = i.effects10.xyz;
5812 let sprite_z = i.effects10.w;
5813 let fog_near = i.effects11.x;
5814 let fog_far = i.effects11.y;
5815 let has_fog_tex = i.effects11.z;
5816 let camera_z = i.effects11.w;
5817
5818 var shaded_uv = i.uv;
5819 if (vs_u.flags.x > 0.5 && i.world_pos.w > 0.5 && i.world_normal.w > 0.5) {
5820 let lighting_type = i32(round(vs_u.mtrl_params.y));
5821 if (lighting_type == 9) {
5822 let view_dir_world = normalize(vs_u.camera_eye.xyz - world_pos);
5823 shaded_uv = apply_parallax_uv(world_normal, world_tangent, world_binormal, i.uv, view_dir_world, max(vs_u.mtrl_extra.x, 0.0));
5824 }
5825 }
5826 let mesh_use_tex = vs_u.mesh_flags.x > 0.5;
5827 let mesh_use_mrbd = vs_u.mesh_flags.y > 0.5;
5828 let mesh_use_rgb = vs_u.mesh_flags.z > 0.5;
5829 let mesh_use_mul_vertex_color = vs_u.mesh_flags.w > 0.5;
5830 var c = select(vec4<f32>(1.0, 1.0, 1.0, 1.0), textureSample(tex0, smp0, shaded_uv), mesh_use_tex);
5831 if (wipe_mode > 0.5 && wipe_mode < 1.5) {
5832 c = sample_mosaic(i.uv, wipe_p0, wipe_p1);
5833 } else if (wipe_mode > 1.5 && wipe_mode < 2.5) {
5834 c = sample_raster_h(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5835 } else if (wipe_mode > 2.5 && wipe_mode < 3.5) {
5836 c = sample_raster_v(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5837 } else if (wipe_mode > 3.5 && wipe_mode < 4.5) {
5838 c = sample_explosion_blur(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5839 } else if (wipe_mode > 4.5 && wipe_mode < 5.5) {
5840 c = sample_shimi(i.uv, wipe_p0, wipe_p1);
5841 } else if (wipe_mode > 5.5 && wipe_mode < 6.5) {
5842 c = sample_shimi_inv(i.uv, wipe_p0, wipe_p1);
5843 } else if (wipe_mode > 9.5 && wipe_mode < 10.5 && has_wipe_src > 0.5) {
5844 let oldc = sample_mosaic_tex3(i.uv, wipe_p0, wipe_p1);
5845 let newc = sample_mosaic(i.uv, wipe_p0, wipe_p1);
5846 if (wipe_p3 < 230.5) {
5847 c = select(oldc, newc, wipe_p2 >= 0.5);
5848 } else {
5849 c = mix(select(newc, oldc, wipe_aux1 < 0.5), select(oldc, newc, wipe_aux1 < 0.5), clamp(wipe_p2, 0.0, 1.0));
5850 }
5851 } else if (wipe_mode > 10.5 && wipe_mode < 11.5 && has_wipe_src > 0.5) {
5852 let oldc = sample_raster_h_tex3(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5853 let newc = sample_raster_h(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5854 c = mix(oldc, newc, clamp(wipe_p3, 0.0, 1.0));
5855 } else if (wipe_mode > 11.5 && wipe_mode < 12.5 && has_wipe_src > 0.5) {
5856 let oldc = sample_raster_v_tex3(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5857 let newc = sample_raster_v(i.uv, wipe_p0, wipe_p1, wipe_p2, wipe_p3);
5858 c = mix(oldc, newc, clamp(wipe_p3, 0.0, 1.0));
5859 } else if (wipe_mode > 12.5 && wipe_mode < 13.5 && has_wipe_src > 0.5) {
5860 let oldc = sample_explosion_blur_tex3(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5861 let newc = sample_explosion_blur(i.uv, vec2<f32>(wipe_p0, wipe_p1), wipe_p2, wipe_p3);
5862 c = mix(oldc, newc, clamp(tonecurve_row, 0.0, 1.0));
5863 }
5864
5865 var rgb = c.rgb;
5866 var alpha = c.a;
5867 if (mesh_use_mul_vertex_color) {
5868 let vc_rate = clamp(vs_u.mesh_misc.x, 0.0, 1.0);
5869 let vertex_color = vec4<f32>(i.effects8.x, i.effects8.y, i.effects8.z, i.effects9.x);
5870 rgb = mix(rgb, rgb * vertex_color.rgb, vc_rate);
5871 alpha = alpha * mix(1.0, vertex_color.a, vc_rate);
5872 }
5873 if (vs_u.flags.x > 0.5) {
5874 rgb = rgb * vs_u.mtrl_diffuse.rgb;
5875 alpha = alpha * vs_u.mtrl_diffuse.a;
5876 }
5877
5878 if (light_on > 0.5) {
5879 if (world_has_pos && length(world_normal) > 0.25) {
5880 rgb = mesh_lighting(rgb, world_pos, world_normal, world_tangent, world_binormal, shaded_uv, light_pos_kind, light_dir_shadow, light_atten, light_cone, i.shadow_pos);
5881 } else {
5882 let lit = clamp(vs_u.light_ambient_u.rgb + vs_u.light_diffuse_u.rgb * light_factor, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(2.0, 2.0, 2.0));
5883 rgb = clamp(rgb * lit + vs_u.mtrl_emissive.rgb, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(8.0, 8.0, 8.0));
5884 }
5885 } else if (vs_u.flags.x > 0.5) {
5886 rgb = clamp(rgb + vs_u.mtrl_emissive.rgb, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(8.0, 8.0, 8.0));
5887 }
5888
5889 if (mesh_use_mrbd) {
5890 let mesh_mono = clamp(vs_u.mesh_mrbd.x, 0.0, 1.0);
5891 let mesh_rev = clamp(vs_u.mesh_mrbd.y, 0.0, 1.0);
5892 let mesh_bright = max(vs_u.mesh_mrbd.z, 0.0);
5893 let mesh_dark = max(vs_u.mesh_mrbd.w, 0.0);
5894 rgb = mix(rgb, vec3<f32>(1.0, 1.0, 1.0) - rgb, mesh_rev);
5895 let mesh_gray = dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
5896 rgb = mix(rgb, vec3<f32>(mesh_gray, mesh_gray, mesh_gray), mesh_mono);
5897 rgb = clamp(rgb + vec3<f32>(mesh_bright, mesh_bright, mesh_bright), vec3<f32>(0.0), vec3<f32>(1.0));
5898 rgb = clamp(rgb - vec3<f32>(mesh_dark, mesh_dark, mesh_dark), vec3<f32>(0.0), vec3<f32>(1.0));
5899 }
5900
5901 if (mesh_use_rgb) {
5902 let mesh_rgb_tgt = clamp(vs_u.mesh_rgb_rate.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
5903 let mesh_rgb_rate = clamp(vs_u.mesh_rgb_rate.w, 0.0, 1.0);
5904 rgb = mix(rgb, mesh_rgb_tgt, mesh_rgb_rate);
5905 rgb = clamp(rgb + vs_u.mesh_add_rgb.xyz, vec3<f32>(0.0), vec3<f32>(1.0));
5906 }
5907
5908 if (has_tonecurve > 0.5) {
5909 rgb = apply_tonecurve(rgb, tonecurve_row, tonecurve_sat);
5910 }
5911
5912 rgb = mix(rgb, vec3<f32>(1.0, 1.0, 1.0) - rgb, rev);
5913 let mono_gray = dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
5914 rgb = mix(rgb, vec3<f32>(mono_gray, mono_gray, mono_gray), mono);
5915 rgb = clamp(rgb + vec3<f32>(bright, bright, bright), vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5916 rgb = clamp(rgb - vec3<f32>(dark, dark, dark), vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5917 rgb = mix(rgb, color_tgt, color_rate);
5918 rgb = clamp(rgb + color_add, vec3<f32>(0.0, 0.0, 0.0), vec3<f32>(1.0, 1.0, 1.0));
5919 let final_gray = dot(rgb, vec3<f32>(0.299, 0.587, 0.114));
5920
5921 if (has_mask > 0.5) {
5922 let m = sample_mask(i.uv_aux);
5923 let mask_luma = dot(m.rgb, vec3<f32>(0.299, 0.587, 0.114));
5924 alpha = alpha * mask_luma * m.a;
5925 }
5926
5927 if (fog_on > 0.5) {
5928 var depth = abs(sprite_z - camera_z);
5929 if (world_has_pos) {
5930 depth = length(world_pos - vs_u.camera_eye.xyz);
5931 }
5932 let fog_t = clamp((depth - fog_near) / max(fog_far - fog_near, 1.0), 0.0, 1.0);
5933 if (fog_t > 0.0) {
5934 var fog_rgb = fog_color;
5935 if (has_fog_tex > 0.5) {
5936 let dims_u = textureDimensions(tex4, 0);
5937 let fw = max(f32(dims_u.x), 1.0);
5938 let fh = max(f32(dims_u.y), 1.0);
5939 let fog_uv = vec2<f32>(fract((i.pos.x + fog_scroll_x) / fw), fract(i.pos.y / fh));
5940 let fog_sample = sample_tex4_safe(fog_uv);
5941 fog_rgb = mix(fog_rgb, fog_sample.rgb, fog_sample.a);
5942 }
5943 rgb = mix(rgb, fog_rgb, fog_t);
5944 }
5945 }
5946
5947 if (mask_mode > 0.5 && mask_mode < 1.5) {
5948 alpha = final_gray;
5949 }
5950
5951 if (alpha_test > 0.5 && alpha <= alpha_ref) {
5952 discard;
5953 }
5954
5955 let a = alpha * i.alpha * tr;
5956 if (blend_code > 2.5 && blend_code < 3.5) {
5957 let mul_rgb = mix(vec3<f32>(1.0, 1.0, 1.0), rgb, a);
5958 return vec4<f32>(mul_rgb, a);
5959 }
5960 if (blend_code > 3.5 && blend_code < 4.5) {
5961 let screen_rgb = mix(vec3<f32>(0.0, 0.0, 0.0), rgb, a);
5962 return vec4<f32>(screen_rgb, a);
5963 }
5964 if (blend_code > 4.5 && blend_code < 5.5) {
5965 let dims_u = textureDimensions(tex3, 0);
5966 let screen_uv = vec2<f32>(
5967 clamp(i.pos.x / max(f32(dims_u.x), 1.0), 0.0, 1.0),
5968 clamp(i.pos.y / max(f32(dims_u.y), 1.0), 0.0, 1.0)
5969 );
5970 let dst = sample_tex3_safe(screen_uv);
5971 let ov = overlay_rgb(dst.rgb, rgb);
5972 let out_rgb = mix(dst.rgb, ov, a);
5973 return vec4<f32>(out_rgb, 1.0);
5974 }
5975 return vec4<f32>(rgb, a);
5976}
5977
5978fn fs_shadow_common(i: ShadowVsOut) -> vec4<f32> {
5979 let base = textureSample(tex0, smp0, i.uv);
5980 if ((i.alpha_test > 0.5 || base.a < 0.999) && base.a <= max(vs_u.mtrl_extra.y, 0.001)) {
5981 discard;
5982 }
5983 return vec4<f32>(i.depth, i.depth, i.depth, 1.0);
5984}
5985
5986@vertex
5987fn vs_sprite_2d(v: VsIn2d) -> VsOut2d {
5988 return vs_common_2d(v);
5989}
5990
5991@vertex
5992fn vs_mesh_static(v: VsIn) -> VsOut {
5993 return vs_common(v);
5994}
5995
5996@vertex
5997fn vs_mesh_skinned(v: VsIn) -> VsOut {
5998 return vs_common(v);
5999}
6000
6001@vertex
6002fn vs_shadow_static(v: VsIn) -> ShadowVsOut {
6003 return vs_shadow_common(v);
6004}
6005
6006@vertex
6007fn vs_shadow_skinned(v: VsIn) -> ShadowVsOut {
6008 return vs_shadow_common(v);
6009}
6010
6011@fragment
6012fn fs_sprite_2d(i: VsOut2d) -> @location(0) vec4<f32> {
6013 return fs_common_2d(i);
6014}
6015
6016@fragment
6017fn fs_overlay_gpu(i: VsOut2d) -> @location(0) vec4<f32> {
6018 return fs_common_2d(i);
6019}
6020
6021@fragment
6022fn fs_wipe_mosaic(i: VsOut2d) -> @location(0) vec4<f32> {
6023 return fs_common_2d(i);
6024}
6025
6026@fragment
6027fn fs_wipe_raster_h(i: VsOut2d) -> @location(0) vec4<f32> {
6028 return fs_common_2d(i);
6029}
6030
6031@fragment
6032fn fs_wipe_raster_v(i: VsOut2d) -> @location(0) vec4<f32> {
6033 return fs_common_2d(i);
6034}
6035
6036@fragment
6037fn fs_wipe_explosion_blur(i: VsOut2d) -> @location(0) vec4<f32> {
6038 return fs_common_2d(i);
6039}
6040
6041@fragment
6042fn fs_wipe_shimi(i: VsOut2d) -> @location(0) vec4<f32> {
6043 return fs_common_2d(i);
6044}
6045
6046@fragment
6047fn fs_wipe_shimi_inv(i: VsOut2d) -> @location(0) vec4<f32> {
6048 return fs_common_2d(i);
6049}
6050
6051@fragment
6052fn fs_wipe_cross_mosaic(i: VsOut2d) -> @location(0) vec4<f32> {
6053 return fs_common_2d(i);
6054}
6055
6056@fragment
6057fn fs_wipe_cross_raster_h(i: VsOut2d) -> @location(0) vec4<f32> {
6058 return fs_common_2d(i);
6059}
6060
6061@fragment
6062fn fs_wipe_cross_raster_v(i: VsOut2d) -> @location(0) vec4<f32> {
6063 return fs_common_2d(i);
6064}
6065
6066@fragment
6067fn fs_wipe_cross_explosion_blur(i: VsOut2d) -> @location(0) vec4<f32> {
6068 return fs_common_2d(i);
6069}
6070
6071@fragment
6072fn fs_mesh_unlit(i: VsOut) -> @location(0) vec4<f32> {
6073 return fs_common(i);
6074}
6075
6076@fragment
6077fn fs_mesh_lambert(i: VsOut) -> @location(0) vec4<f32> {
6078 return fs_common(i);
6079}
6080
6081@fragment
6082fn fs_mesh_blinn_phong(i: VsOut) -> @location(0) vec4<f32> {
6083 return fs_common(i);
6084}
6085
6086@fragment
6087fn fs_mesh_pp_blinn_phong(i: VsOut) -> @location(0) vec4<f32> {
6088 return fs_common(i);
6089}
6090
6091@fragment
6092fn fs_mesh_pp_half_lambert(i: VsOut) -> @location(0) vec4<f32> {
6093 return fs_common(i);
6094}
6095
6096@fragment
6097fn fs_mesh_toon(i: VsOut) -> @location(0) vec4<f32> {
6098 return fs_common(i);
6099}
6100
6101@fragment
6102fn fs_mesh_ffp(i: VsOut) -> @location(0) vec4<f32> {
6103 return fs_common(i);
6104}
6105
6106@fragment
6107fn fs_mesh_pp_ffp(i: VsOut) -> @location(0) vec4<f32> {
6108 return fs_common(i);
6109}
6110
6111@fragment
6112fn fs_mesh_bump(i: VsOut) -> @location(0) vec4<f32> {
6113 return fs_common(i);
6114}
6115
6116@fragment
6117fn fs_mesh_parallax(i: VsOut) -> @location(0) vec4<f32> {
6118 return fs_common(i);
6119}
6120
6121@fragment
6122fn fs_shadow_map(i: ShadowVsOut) -> @location(0) vec4<f32> {
6123 return fs_shadow_common(i);
6124}
6125"#;