Skip to main content

siglus_scene_vm/render/
mod.rs

1//! WGPU renderer for Siglus stage composition.
2//!
3//! This renderer consumes a painter-ordered list of sprites and draws them
4//! in order. It supports fixed sprite effects, dual-source wipes, and a
5//! depth-backed path for 3D-transformed quads.
6
7use 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    /// Drop GPU textures that are keyed by runtime ImageId.
4278    ///
4279    /// Scene restart reinitializes ImageManager and reuses ImageId indices from 0.
4280    /// Keeping the old GPU cache would make a newly decoded image with the same
4281    /// ImageId/version sample the previous scene's texture. External path based
4282    /// textures are intentionally kept because their keys are stable resource paths.
4283    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    // WGPU follows the Direct3D/Vulkan clip-space convention: z is 0..1.
4652    // The previous OpenGL-style mapping (depth * 2 - 1) put all 2D quads at z=-1,
4653    // which is outside WGPU clip space and makes the game window render black even
4654    // though the VM submits sprites correctly.
4655    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"#;